summaryrefslogtreecommitdiff
path: root/src/patch_parse.c
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2017-11-11 13:29:27 +0000
committerGitHub <noreply@github.com>2017-11-11 13:29:27 +0000
commit0393ecc6f8bb19e4ba55fd45e7bbde6596f9b334 (patch)
tree958c4fcb59adba4e0f5b7232d3739cd447dd7f4f /src/patch_parse.c
parent5a061a23afa5dd026f130d83ea6d5ed58bce79d4 (diff)
parentcc4c44a98a552b64c281101cbadb91effa5be5dd (diff)
downloadlibgit2-0393ecc6f8bb19e4ba55fd45e7bbde6596f9b334.tar.gz
Merge pull request #4308 from pks-t/pks/header-state-machine
patch_parse: implement state machine for parsing patch headers
Diffstat (limited to 'src/patch_parse.c')
-rw-r--r--src/patch_parse.c128
1 files changed, 82 insertions, 46 deletions
diff --git a/src/patch_parse.c b/src/patch_parse.c
index a531eec4d..fad892d8a 100644
--- a/src/patch_parse.c
+++ b/src/patch_parse.c
@@ -372,31 +372,74 @@ static int parse_header_dissimilarity(
return 0;
}
+static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx)
+{
+ if (parse_header_path(&patch->header_old_path, ctx) < 0)
+ return parse_err("corrupt old path in git diff header at line %"PRIuZ,
+ ctx->line_num);
+
+ if (parse_advance_ws(ctx) < 0 ||
+ parse_header_path(&patch->header_new_path, ctx) < 0)
+ return parse_err("corrupt new path in git diff header at line %"PRIuZ,
+ ctx->line_num);
+
+ return 0;
+}
+
+typedef enum {
+ STATE_START,
+
+ STATE_DIFF,
+ STATE_FILEMODE,
+ STATE_MODE,
+ STATE_INDEX,
+ STATE_PATH,
+
+ STATE_SIMILARITY,
+ STATE_RENAME,
+ STATE_COPY,
+
+ STATE_END,
+} parse_header_state;
+
typedef struct {
const char *str;
+ parse_header_state expected_state;
+ parse_header_state next_state;
int(*fn)(git_patch_parsed *, git_patch_parse_ctx *);
-} header_git_op;
-
-static const header_git_op header_git_ops[] = {
- { "diff --git ", NULL },
- { "@@ -", NULL },
- { "GIT binary patch", NULL },
- { "Binary files ", NULL },
- { "--- ", parse_header_git_oldpath },
- { "+++ ", parse_header_git_newpath },
- { "index ", parse_header_git_index },
- { "old mode ", parse_header_git_oldmode },
- { "new mode ", parse_header_git_newmode },
- { "deleted file mode ", parse_header_git_deletedfilemode },
- { "new file mode ", parse_header_git_newfilemode },
- { "rename from ", parse_header_renamefrom },
- { "rename to ", parse_header_renameto },
- { "rename old ", parse_header_renamefrom },
- { "rename new ", parse_header_renameto },
- { "copy from ", parse_header_copyfrom },
- { "copy to ", parse_header_copyto },
- { "similarity index ", parse_header_similarity },
- { "dissimilarity index ", parse_header_dissimilarity },
+} parse_header_transition;
+
+static const parse_header_transition transitions[] = {
+ /* Start */
+ { "diff --git " , STATE_START, STATE_DIFF, parse_header_start },
+
+ { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode },
+ { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode },
+ { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode },
+ { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode },
+
+ { "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_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 },
+
+ { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity },
+ { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity },
+ { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom },
+ { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom },
+ { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom },
+ { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto },
+ { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto },
+ { "copy to " , STATE_COPY, STATE_END, parse_header_copyto },
+
+ /* Next patch */
+ { "diff --git " , STATE_END, 0, NULL },
+ { "@@ -" , STATE_END, 0, NULL },
+ { "-- " , STATE_END, 0, NULL },
};
static int parse_header_git(
@@ -405,44 +448,32 @@ static int parse_header_git(
{
size_t i;
int error = 0;
-
- /* Parse the diff --git line */
- if (parse_advance_expected_str(ctx, "diff --git ") < 0)
- return parse_err("corrupt git diff header at line %"PRIuZ, ctx->line_num);
-
- if (parse_header_path(&patch->header_old_path, ctx) < 0)
- return parse_err("corrupt old path in git diff header at line %"PRIuZ,
- ctx->line_num);
-
- if (parse_advance_ws(ctx) < 0 ||
- parse_header_path(&patch->header_new_path, ctx) < 0)
- return parse_err("corrupt new path in git diff header at line %"PRIuZ,
- ctx->line_num);
+ parse_header_state state = STATE_START;
/* Parse remaining header lines */
- for (parse_advance_line(ctx);
- ctx->remain_len > 0;
- parse_advance_line(ctx)) {
-
+ for (; ctx->remain_len > 0; parse_advance_line(ctx)) {
bool found = false;
if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n')
break;
- for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) {
- const header_git_op *op = &header_git_ops[i];
- size_t op_len = strlen(op->str);
+ for (i = 0; i < ARRAY_SIZE(transitions); i++) {
+ const parse_header_transition *transition = &transitions[i];
+ size_t op_len = strlen(transition->str);
- if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0)
+ if (transition->expected_state != state ||
+ memcmp(ctx->line, transition->str, min(op_len, ctx->line_len)) != 0)
continue;
+ state = transition->next_state;
+
/* Do not advance if this is the patch separator */
- if (op->fn == NULL)
+ if (transition->fn == NULL)
goto done;
parse_advance_chars(ctx, op_len);
- if ((error = op->fn(patch, ctx)) < 0)
+ if ((error = transition->fn(patch, ctx)) < 0)
goto done;
parse_advance_ws(ctx);
@@ -456,7 +487,7 @@ static int parse_header_git(
found = true;
break;
}
-
+
if (!found) {
error = parse_err("invalid patch header at line %"PRIuZ,
ctx->line_num);
@@ -464,6 +495,11 @@ static int parse_header_git(
}
}
+ if (state != STATE_END) {
+ error = parse_err("unexpected header line %"PRIuZ, ctx->line_num);
+ goto done;
+ }
+
done:
return error;
}