Skip to content

Commit

Permalink
fast-import: tighten parsing of datarefs
Browse files Browse the repository at this point in the history
The syntax for the use of mark references in fast-import
demands either a SP (space) or LF (end-of-line) after
a mark reference.  Fast-import does not complain when garbage
appears after a mark reference in some cases.

Factor out parsing of mark references and complain if
errant characters are found.  Also be a little more careful
when parsing "inline" and SHA1s, complaining if extra
characters appear or if the form of the dataref is unrecognized.

Buggy input can cause fast-import to produce the wrong output,
silently, without error.  This makes it difficult to track
down buggy generators of fast-import streams.  An example is
seen in the last line of this commit command:

    commit refs/heads/S2
    committer Name <name@example.com> 1112912893 -0400
    data <<COMMIT
    commit message
    COMMIT
    from :1M 100644 :103 hello.c

It is missing a newline and should be:

    [...]
    from :1
    M 100644 :103 hello.c

What fast-import does is to produce a commit with the same
contents for hello.c as in refs/heads/S2^.  What the buggy
program was expecting was the contents of blob :103.  While
the resulting commit graph looked correct, the contents in
some commits were wrong.

Signed-off-by: Pete Wyckoff <pw@padd.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Pete Wyckoff authored and Junio C Hamano committed Apr 10, 2012
1 parent cb2ed32 commit 06454cb
Show file tree
Hide file tree
Showing 2 changed files with 364 additions and 33 deletions.
110 changes: 77 additions & 33 deletions fast-import.c
Original file line number Diff line number Diff line change
Expand Up @@ -2207,6 +2207,59 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
}

/*
* Given a pointer into a string, parse a mark reference:
*
* idnum ::= ':' bigint;
*
* Return the first character after the value in *endptr.
*
* Complain if the following character is not what is expected,
* either a space or end of the string.
*/
static uintmax_t parse_mark_ref(const char *p, char **endptr)
{
uintmax_t mark;

assert(*p == ':');
p++;
mark = strtoumax(p, endptr, 10);
if (*endptr == p)
die("No value after ':' in mark: %s", command_buf.buf);
return mark;
}

/*
* Parse the mark reference, and complain if this is not the end of
* the string.
*/
static uintmax_t parse_mark_ref_eol(const char *p)
{
char *end;
uintmax_t mark;

mark = parse_mark_ref(p, &end);
if (*end != '\0')
die("Garbage after mark: %s", command_buf.buf);
return mark;
}

/*
* Parse the mark reference, demanding a trailing space. Return a
* pointer to the space.
*/
static uintmax_t parse_mark_ref_space(const char **p)
{
uintmax_t mark;
char *end;

mark = parse_mark_ref(*p, &end);
if (*end != ' ')
die("Missing space after mark: %s", command_buf.buf);
*p = end;
return mark;
}

static void file_change_m(struct branch *b)
{
const char *p = command_buf.buf + 2;
Expand Down Expand Up @@ -2235,21 +2288,21 @@ static void file_change_m(struct branch *b)
}

if (*p == ':') {
char *x;
oe = find_mark(strtoumax(p + 1, &x, 10));
oe = find_mark(parse_mark_ref_space(&p));
hashcpy(sha1, oe->idx.sha1);
p = x;
} else if (!prefixcmp(p, "inline")) {
} else if (!prefixcmp(p, "inline ")) {
inline_data = 1;
p += 6;
p += strlen("inline"); /* advance to space */
} else {
if (get_sha1_hex(p, sha1))
die("Invalid SHA1: %s", command_buf.buf);
die("Invalid dataref: %s", command_buf.buf);
oe = find_object(sha1);
p += 40;
if (*p != ' ')
die("Missing space after SHA1: %s", command_buf.buf);
}
if (*p++ != ' ')
die("Missing space after SHA1: %s", command_buf.buf);
assert(*p == ' ');
p++; /* skip space */

strbuf_reset(&uq);
if (!unquote_c_style(&uq, p, &endp)) {
Expand Down Expand Up @@ -2407,21 +2460,21 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
/* Now parse the notemodify command. */
/* <dataref> or 'inline' */
if (*p == ':') {
char *x;
oe = find_mark(strtoumax(p + 1, &x, 10));
oe = find_mark(parse_mark_ref_space(&p));
hashcpy(sha1, oe->idx.sha1);
p = x;
} else if (!prefixcmp(p, "inline")) {
} else if (!prefixcmp(p, "inline ")) {
inline_data = 1;
p += 6;
p += strlen("inline"); /* advance to space */
} else {
if (get_sha1_hex(p, sha1))
die("Invalid SHA1: %s", command_buf.buf);
die("Invalid dataref: %s", command_buf.buf);
oe = find_object(sha1);
p += 40;
if (*p != ' ')
die("Missing space after SHA1: %s", command_buf.buf);
}
if (*p++ != ' ')
die("Missing space after SHA1: %s", command_buf.buf);
assert(*p == ' ');
p++; /* skip space */

/* <committish> */
s = lookup_branch(p);
Expand All @@ -2430,7 +2483,7 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
die("Can't add a note on empty branch.");
hashcpy(commit_sha1, s->sha1);
} else if (*p == ':') {
uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
uintmax_t commit_mark = parse_mark_ref_eol(p);
struct object_entry *commit_oe = find_mark(commit_mark);
if (commit_oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", commit_mark);
Expand Down Expand Up @@ -2537,7 +2590,7 @@ static int parse_from(struct branch *b)
hashcpy(b->branch_tree.versions[0].sha1, t);
hashcpy(b->branch_tree.versions[1].sha1, t);
} else if (*from == ':') {
uintmax_t idnum = strtoumax(from + 1, NULL, 10);
uintmax_t idnum = parse_mark_ref_eol(from);
struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum);
Expand Down Expand Up @@ -2572,7 +2625,7 @@ static struct hash_list *parse_merge(unsigned int *count)
if (s)
hashcpy(n->sha1, s->sha1);
else if (*from == ':') {
uintmax_t idnum = strtoumax(from + 1, NULL, 10);
uintmax_t idnum = parse_mark_ref_eol(from);
struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum);
Expand Down Expand Up @@ -2735,7 +2788,7 @@ static void parse_new_tag(void)
type = OBJ_COMMIT;
} else if (*from == ':') {
struct object_entry *oe;
from_mark = strtoumax(from + 1, NULL, 10);
from_mark = parse_mark_ref_eol(from);
oe = find_mark(from_mark);
type = oe->type;
hashcpy(sha1, oe->idx.sha1);
Expand Down Expand Up @@ -2867,18 +2920,13 @@ static void parse_cat_blob(void)
/* cat-blob SP <object> LF */
p = command_buf.buf + strlen("cat-blob ");
if (*p == ':') {
char *x;
oe = find_mark(strtoumax(p + 1, &x, 10));
if (x == p + 1)
die("Invalid mark: %s", command_buf.buf);
oe = find_mark(parse_mark_ref_eol(p));
if (!oe)
die("Unknown mark: %s", command_buf.buf);
if (*x)
die("Garbage after mark: %s", command_buf.buf);
hashcpy(sha1, oe->idx.sha1);
} else {
if (get_sha1_hex(p, sha1))
die("Invalid SHA1: %s", command_buf.buf);
die("Invalid dataref: %s", command_buf.buf);
if (p[40])
die("Garbage after SHA1: %s", command_buf.buf);
oe = find_object(sha1);
Expand Down Expand Up @@ -2944,17 +2992,13 @@ static struct object_entry *parse_treeish_dataref(const char **p)
struct object_entry *e;

if (**p == ':') { /* <mark> */
char *endptr;
e = find_mark(strtoumax(*p + 1, &endptr, 10));
if (endptr == *p + 1)
die("Invalid mark: %s", command_buf.buf);
e = find_mark(parse_mark_ref_space(p));
if (!e)
die("Unknown mark: %s", command_buf.buf);
*p = endptr;
hashcpy(sha1, e->idx.sha1);
} else { /* <sha1> */
if (get_sha1_hex(*p, sha1))
die("Invalid SHA1: %s", command_buf.buf);
die("Invalid dataref: %s", command_buf.buf);
e = find_object(sha1);
*p += 40;
}
Expand Down
Loading

0 comments on commit 06454cb

Please sign in to comment.