vendor/libgit2/src/patch_parse.c in rugged-0.28.3.1 vs vendor/libgit2/src/patch_parse.c in rugged-0.28.4

- 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,44 +68,55 @@ 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); @@ -106,13 +129,18 @@ 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_dispose(&new_path); return error; @@ -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); } @@ -375,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 }, @@ -392,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, @@ -459,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; @@ -522,33 +552,48 @@ 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; } @@ -576,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); 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; @@ -685,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); @@ -728,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( @@ -798,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; @@ -1036,18 +1125,22 @@ } 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);