vendor/libgit2/src/patch_parse.c in rugged-0.27.9 vs vendor/libgit2/src/patch_parse.c in rugged-0.27.10

- old
+ new

@@ -31,12 +31,24 @@ /* the prefixes from the old/new paths */ char *old_prefix, *new_prefix; } git_patch_parsed; -static int header_path_len(git_patch_parse_ctx *ctx) +static int git_parse_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); +static int git_parse_err(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + git_error_vset(GIT_ERROR_PATCH, fmt, ap); + va_end(ap); + + return -1; +} + +static size_t header_path_len(git_patch_parse_ctx *ctx) +{ bool inquote = 0; bool quoted = git_parse_ctx_contains_s(&ctx->parse_ctx, "\""); size_t len; for (len = quoted; len < ctx->parse_ctx.line_len; len++) { @@ -56,67 +68,83 @@ static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t path_len) { int error; if ((error = git_buf_put(path, ctx->parse_ctx.line, path_len)) < 0) - goto done; + return error; git_parse_advance_chars(&ctx->parse_ctx, path_len); git_buf_rtrim(path); - if (path->size > 0 && path->ptr[0] == '"') - error = git_buf_unquote(path); + if (path->size > 0 && path->ptr[0] == '"' && + (error = git_buf_unquote(path)) < 0) + return error; - if (error < 0) - goto done; - git_path_squash_slashes(path); -done: - return error; + if (!path->size) + return git_parse_err("patch contains empty path at line %"PRIuZ, + ctx->parse_ctx.line_num); + + return 0; } static int parse_header_path(char **out, git_patch_parse_ctx *ctx) { git_buf path = GIT_BUF_INIT; - int error = parse_header_path_buf(&path, ctx, header_path_len(ctx)); + int error; + if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0) + goto out; *out = git_buf_detach(&path); +out: + git_buf_dispose(&path); return error; } static int parse_header_git_oldpath( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { git_buf old_path = GIT_BUF_INIT; int error; + if (patch->old_path) { + error = git_parse_err("patch contains duplicate old path at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto out; + } + if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) goto out; patch->old_path = git_buf_detach(&old_path); out: - git_buf_free(&old_path); + git_buf_dispose(&old_path); return error; } static int parse_header_git_newpath( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { git_buf new_path = GIT_BUF_INIT; int error; - if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) + if (patch->new_path) { + error = git_parse_err("patch contains duplicate new path at line %"PRIuZ, + ctx->parse_ctx.line_num); goto out; + } + if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) + goto out; patch->new_path = git_buf_detach(&new_path); out: - git_buf_free(&new_path); + git_buf_dispose(&new_path); return error; } static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) { @@ -201,26 +229,26 @@ static int parse_header_git_deletedfilemode( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - git__free((char *)patch->base.delta->old_file.path); + git__free((char *)patch->base.delta->new_file.path); - patch->base.delta->old_file.path = NULL; + patch->base.delta->new_file.path = NULL; patch->base.delta->status = GIT_DELTA_DELETED; patch->base.delta->nfiles = 1; return parse_header_mode(&patch->base.delta->old_file.mode, ctx); } static int parse_header_git_newfilemode( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - git__free((char *)patch->base.delta->new_file.path); + git__free((char *)patch->base.delta->old_file.path); - patch->base.delta->new_file.path = NULL; + patch->base.delta->old_file.path = NULL; patch->base.delta->status = GIT_DELTA_ADDED; patch->base.delta->nfiles = 1; return parse_header_mode(&patch->base.delta->new_file.mode, ctx); } @@ -280,11 +308,11 @@ return -1; if (val < 0 || val > 100) return -1; - *out = val; + *out = (uint16_t)val; return 0; } static int parse_header_similarity( git_patch_parsed *patch, git_patch_parse_ctx *ctx) @@ -326,11 +354,12 @@ * point. Due to the possibility of unquoted names, whitespaces in * filenames and custom prefixes we have to allow that, though, and just * proceeed here. We then hope for the "---" and "+++" lines to fix that * for us. */ - if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1)) { + if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1) && + !git_parse_ctx_contains(&ctx->parse_ctx, "\r\n", 2)) { git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); git__free(patch->header_old_path); patch->header_old_path = NULL; git__free(patch->header_new_path); @@ -374,10 +403,11 @@ { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, { "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index }, { "index " , STATE_END, STATE_INDEX, parse_header_git_index }, + { "--- " , STATE_DIFF, STATE_PATH, parse_header_git_oldpath }, { "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath }, { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, { "Binary files " , STATE_INDEX, STATE_END, NULL }, @@ -391,10 +421,11 @@ { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, /* Next patch */ { "diff --git " , STATE_END, 0, NULL }, { "@@ -" , STATE_END, 0, NULL }, + { "-- " , STATE_INDEX, 0, NULL }, { "-- " , STATE_END, 0, NULL }, }; static int parse_header_git( git_patch_parsed *patch, @@ -458,11 +489,11 @@ return error; } static int parse_int(int *out, git_patch_parse_ctx *ctx) { - git_off_t num; + int64_t num; if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) return -1; *out = (int)num; @@ -516,38 +547,53 @@ hunk->hunk.header[hunk->hunk.header_len] = '\0'; return 0; fail: - giterr_set(GITERR_PATCH, "invalid patch hunk header at line %"PRIuZ, + git_error_set(GIT_ERROR_PATCH, "invalid patch hunk header at line %"PRIuZ, ctx->parse_ctx.line_num); return -1; } +static int eof_for_origin(int origin) { + if (origin == GIT_DIFF_LINE_ADDITION) + return GIT_DIFF_LINE_ADD_EOFNL; + if (origin == GIT_DIFF_LINE_DELETION) + return GIT_DIFF_LINE_DEL_EOFNL; + return GIT_DIFF_LINE_CONTEXT_EOFNL; +} + static int parse_hunk_body( git_patch_parsed *patch, git_patch_hunk *hunk, git_patch_parse_ctx *ctx) { git_diff_line *line; int error = 0; int oldlines = hunk->hunk.old_lines; int newlines = hunk->hunk.new_lines; + int last_origin = 0; for (; ctx->parse_ctx.remain_len > 1 && (oldlines || newlines) && !git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -"); git_parse_advance_line(&ctx->parse_ctx)) { + int old_lineno, new_lineno, origin, prefix = 1; char c; - int origin; - int prefix = 1; - int old_lineno = hunk->hunk.old_start + (hunk->hunk.old_lines - oldlines); - int new_lineno = hunk->hunk.new_start + (hunk->hunk.new_lines - newlines); + if (git__add_int_overflow(&old_lineno, hunk->hunk.old_start, hunk->hunk.old_lines) || + git__sub_int_overflow(&old_lineno, old_lineno, oldlines) || + git__add_int_overflow(&new_lineno, hunk->hunk.new_start, hunk->hunk.new_lines) || + git__sub_int_overflow(&new_lineno, new_lineno, newlines)) { + error = git_parse_err("unrepresentable line count at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto done; + } + if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { error = git_parse_err("invalid patch instruction at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } @@ -575,55 +621,88 @@ origin = GIT_DIFF_LINE_ADDITION; newlines--; old_lineno = -1; break; + case '\\': + /* + * If there are no oldlines left, then this is probably + * the "\ No newline at end of file" marker. Do not + * verify its format, as it may be localized. + */ + if (!oldlines) { + prefix = 0; + origin = eof_for_origin(last_origin); + old_lineno = -1; + new_lineno = -1; + break; + } + /* fall through */ + default: error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } line = git_array_alloc(patch->base.lines); - GITERR_CHECK_ALLOC(line); + GIT_ERROR_CHECK_ALLOC(line); memset(line, 0x0, sizeof(git_diff_line)); - line->content = ctx->parse_ctx.line + prefix; line->content_len = ctx->parse_ctx.line_len - prefix; + line->content = git__strndup(ctx->parse_ctx.line + prefix, line->content_len); + GIT_ERROR_CHECK_ALLOC(line->content); line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; line->origin = origin; line->num_lines = 1; line->old_lineno = old_lineno; line->new_lineno = new_lineno; hunk->line_count++; + + last_origin = origin; } if (oldlines || newlines) { error = git_parse_err( "invalid patch hunk, expected %d old lines and %d new lines", hunk->hunk.old_lines, hunk->hunk.new_lines); goto done; } - /* Handle "\ No newline at end of file". Only expect the leading + /* + * Handle "\ No newline at end of file". Only expect the leading * backslash, though, because the rest of the string could be * localized. Because `diff` optimizes for the case where you * want to apply the patch by hand. */ if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && git_array_size(patch->base.lines) > 0) { line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); if (line->content_len < 1) { - error = git_parse_err("cannot trim trailing newline of empty line"); + error = git_parse_err("last line has no trailing newline"); goto done; } - line->content_len--; + line = git_array_alloc(patch->base.lines); + GIT_ERROR_CHECK_ALLOC(line); + memset(line, 0x0, sizeof(git_diff_line)); + + line->content_len = ctx->parse_ctx.line_len; + line->content = git__strndup(ctx->parse_ctx.line, line->content_len); + GIT_ERROR_CHECK_ALLOC(line->content); + line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; + line->origin = eof_for_origin(last_origin); + line->num_lines = 1; + line->old_lineno = -1; + line->new_lineno = -1; + + hunk->line_count++; + git_parse_advance_line(&ctx->parse_ctx); } done: return error; @@ -648,11 +727,11 @@ /* If this cannot be parsed as a hunk header, it's just leading * noise, continue. */ if (parse_hunk_header(&hunk, ctx) < 0) { - giterr_clear(); + git_error_clear(); continue; } error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, line_num); @@ -671,11 +750,11 @@ error = 0; continue; } - giterr_set(GITERR_PATCH, "no patch found"); + git_error_set(GIT_ERROR_PATCH, "no patch found"); error = GIT_ENOTFOUND; done: return error; } @@ -684,11 +763,11 @@ git_diff_binary_file *binary, git_patch_parse_ctx *ctx) { git_diff_binary_t type = GIT_DIFF_BINARY_NONE; git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT; - git_off_t len; + int64_t len; int error = 0; if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { type = GIT_DIFF_BINARY_LITERAL; git_parse_advance_chars(&ctx->parse_ctx, 8); @@ -727,11 +806,11 @@ git_parse_advance_chars(&ctx->parse_ctx, 1); encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; - if (encoded_len > ctx->parse_ctx.line_len - 1) { + if (!encoded_len || !ctx->parse_ctx.line_len || encoded_len > ctx->parse_ctx.line_len - 1) { error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } if ((error = git_buf_decode_base85( @@ -755,12 +834,12 @@ binary->inflatedlen = (size_t)len; binary->datalen = decoded.size; binary->data = git_buf_detach(&decoded); done: - git_buf_free(&base85); - git_buf_free(&decoded); + git_buf_dispose(&base85); + git_buf_dispose(&decoded); return error; } static int parse_patch_binary( git_patch_parsed *patch, @@ -797,16 +876,27 @@ static int parse_patch_binary_nodata( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { + const char *old = patch->old_path ? patch->old_path : patch->header_old_path; + const char *new = patch->new_path ? patch->new_path : patch->header_new_path; + + if (!old || !new) + return git_parse_err("corrupt binary data without paths at line %"PRIuZ, ctx->parse_ctx.line_num); + + if (patch->base.delta->status == GIT_DELTA_ADDED) + old = "/dev/null"; + else if (patch->base.delta->status == GIT_DELTA_DELETED) + new = "/dev/null"; + if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_old_path) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_new_path) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || - git_parse_advance_nl(&ctx->parse_ctx) < 0) + git_parse_advance_expected_str(&ctx->parse_ctx, old) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, new) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0) return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); patch->base.binary.contains_data = 0; patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; return 0; @@ -819,11 +909,11 @@ git_patch_hunk *hunk; int error = 0; while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { hunk = git_array_alloc(patch->base.hunks); - GITERR_CHECK_ALLOC(hunk); + GIT_ERROR_CHECK_ALLOC(hunk); memset(hunk, 0, sizeof(git_patch_hunk)); hunk->line_start = git_array_size(patch->base.lines); hunk->line_count = 0; @@ -919,25 +1009,19 @@ if (!patch->old_path && patch->new_path) return git_parse_err("missing old path"); /* Ensure (non-renamed) paths match */ - if (check_header_names( - patch->header_old_path, patch->old_path, "old", added) < 0 || - check_header_names( - patch->header_new_path, patch->new_path, "new", deleted) < 0) + if (check_header_names(patch->header_old_path, patch->old_path, "old", added) < 0 || + check_header_names(patch->header_new_path, patch->new_path, "new", deleted) < 0) return -1; - prefixed_old = (!added && patch->old_path) ? patch->old_path : - patch->header_old_path; - prefixed_new = (!deleted && patch->new_path) ? patch->new_path : - patch->header_new_path; + prefixed_old = (!added && patch->old_path) ? patch->old_path : patch->header_old_path; + prefixed_new = (!deleted && patch->new_path) ? patch->new_path : patch->header_new_path; - if (check_prefix( - &patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0 || - check_prefix( - &patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0) + if ((prefixed_old && check_prefix(&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0) || + (prefixed_new && check_prefix(&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)) return -1; /* Prefer the rename filenames as they are unambiguous and unprefixed */ if (patch->rename_old_path) patch->base.delta->old_file.path = patch->rename_old_path; @@ -948,11 +1032,11 @@ patch->base.delta->new_file.path = patch->rename_new_path; else patch->base.delta->new_file.path = prefixed_new + new_prefixlen; if (!patch->base.delta->old_file.path && - !patch->base.delta->new_file.path) + !patch->base.delta->new_file.path) return git_parse_err("git diff header lacks old / new paths"); return 0; } @@ -962,18 +1046,18 @@ if (check_filenames(patch) < 0) return -1; if (delta->old_file.path && - delta->status != GIT_DELTA_DELETED && - !delta->new_file.mode) + delta->status != GIT_DELTA_DELETED && + !delta->new_file.mode) delta->new_file.mode = delta->old_file.mode; if (delta->status == GIT_DELTA_MODIFIED && - !(delta->flags & GIT_DIFF_FLAG_BINARY) && - delta->new_file.mode == delta->old_file.mode && - git_array_size(patch->base.hunks) == 0) + !(delta->flags & GIT_DIFF_FLAG_BINARY) && + delta->new_file.mode == delta->old_file.mode && + git_array_size(patch->base.hunks) == 0) return git_parse_err("patch with no hunks"); if (delta->status == GIT_DELTA_ADDED) { memset(&delta->old_file.id, 0x0, sizeof(git_oid)); delta->old_file.id_abbrev = 0; @@ -1041,19 +1125,23 @@ } static void patch_parsed__free(git_patch *p) { git_patch_parsed *patch = (git_patch_parsed *)p; + git_diff_line *line; + size_t i; if (!patch) return; git_patch_parse_ctx_free(patch->ctx); git__free((char *)patch->base.binary.old_file.data); git__free((char *)patch->base.binary.new_file.data); git_array_clear(patch->base.hunks); + git_array_foreach(patch->base.lines, i, line) + git__free((char *) line->content); git_array_clear(patch->base.lines); git__free(patch->base.delta); git__free(patch->old_prefix); git__free(patch->new_prefix); @@ -1077,19 +1165,19 @@ assert(out && ctx); *out = NULL; patch = git__calloc(1, sizeof(git_patch_parsed)); - GITERR_CHECK_ALLOC(patch); + GIT_ERROR_CHECK_ALLOC(patch); patch->ctx = ctx; GIT_REFCOUNT_INC(patch->ctx); patch->base.free_fn = patch_parsed__free; patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); - GITERR_CHECK_ALLOC(patch->base.delta); + GIT_ERROR_CHECK_ALLOC(patch->base.delta); patch->base.delta->status = GIT_DELTA_MODIFIED; patch->base.delta->nfiles = 2; start = ctx->parse_ctx.remain_len; @@ -1124,10 +1212,10 @@ { git_patch_parse_ctx *ctx; int error; ctx = git_patch_parse_ctx_init(content, content_len, opts); - GITERR_CHECK_ALLOC(ctx); + GIT_ERROR_CHECK_ALLOC(ctx); error = git_patch_parse(out, ctx); git_patch_parse_ctx_free(ctx); return error;