]>
Commit | Line | Data |
---|---|---|
804d5fe9 ET |
1 | #include "git2/patch.h" |
2 | #include "patch.h" | |
3 | #include "path.h" | |
4 | ||
5 | #define parse_err(...) \ | |
6 | ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) | |
7 | ||
8 | typedef struct { | |
9 | git_patch base; | |
82175084 ET |
10 | |
11 | git_patch_options opts; | |
12 | ||
13 | /* the paths from the `diff --git` header, these will be used if this is not | |
14 | * a rename (and rename paths are specified) or if no `+++`/`---` line specify | |
15 | * the paths. | |
16 | */ | |
17 | char *header_old_path, *header_new_path; | |
18 | ||
19 | /* renamed paths are precise and are not prefixed */ | |
20 | char *rename_old_path, *rename_new_path; | |
21 | ||
22 | /* the paths given in `---` and `+++` lines */ | |
23 | char *old_path, *new_path; | |
24 | ||
25 | /* the prefixes from the old/new paths */ | |
26 | char *old_prefix, *new_prefix; | |
804d5fe9 ET |
27 | } git_patch_parsed; |
28 | ||
29 | typedef struct { | |
30 | const char *content; | |
31 | size_t content_len; | |
32 | ||
33 | const char *line; | |
34 | size_t line_len; | |
35 | size_t line_num; | |
36 | ||
37 | size_t remain; | |
804d5fe9 ET |
38 | } patch_parse_ctx; |
39 | ||
40 | ||
41 | static void parse_advance_line(patch_parse_ctx *ctx) | |
42 | { | |
43 | ctx->line += ctx->line_len; | |
44 | ctx->remain -= ctx->line_len; | |
45 | ctx->line_len = git__linenlen(ctx->line, ctx->remain); | |
46 | ctx->line_num++; | |
47 | } | |
48 | ||
49 | static void parse_advance_chars(patch_parse_ctx *ctx, size_t char_cnt) | |
50 | { | |
51 | ctx->line += char_cnt; | |
52 | ctx->remain -= char_cnt; | |
53 | ctx->line_len -= char_cnt; | |
54 | } | |
55 | ||
56 | static int parse_advance_expected( | |
57 | patch_parse_ctx *ctx, | |
58 | const char *expected, | |
59 | size_t expected_len) | |
60 | { | |
61 | if (ctx->line_len < expected_len) | |
62 | return -1; | |
63 | ||
64 | if (memcmp(ctx->line, expected, expected_len) != 0) | |
65 | return -1; | |
66 | ||
67 | parse_advance_chars(ctx, expected_len); | |
68 | return 0; | |
69 | } | |
70 | ||
71 | static int parse_advance_ws(patch_parse_ctx *ctx) | |
72 | { | |
73 | int ret = -1; | |
74 | ||
75 | while (ctx->line_len > 0 && | |
76 | ctx->line[0] != '\n' && | |
77 | git__isspace(ctx->line[0])) { | |
78 | ctx->line++; | |
79 | ctx->line_len--; | |
80 | ctx->remain--; | |
81 | ret = 0; | |
82 | } | |
83 | ||
84 | return ret; | |
85 | } | |
86 | ||
87 | static int parse_advance_nl(patch_parse_ctx *ctx) | |
88 | { | |
89 | if (ctx->line_len != 1 || ctx->line[0] != '\n') | |
90 | return -1; | |
91 | ||
92 | parse_advance_line(ctx); | |
93 | return 0; | |
94 | } | |
95 | ||
96 | static int header_path_len(patch_parse_ctx *ctx) | |
97 | { | |
98 | bool inquote = 0; | |
99 | bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"'); | |
100 | size_t len; | |
101 | ||
102 | for (len = quoted; len < ctx->line_len; len++) { | |
103 | if (!quoted && git__isspace(ctx->line[len])) | |
104 | break; | |
105 | else if (quoted && !inquote && ctx->line[len] == '"') { | |
106 | len++; | |
107 | break; | |
108 | } | |
109 | ||
110 | inquote = (!inquote && ctx->line[len] == '\\'); | |
111 | } | |
112 | ||
113 | return len; | |
114 | } | |
115 | ||
116 | static int parse_header_path_buf(git_buf *path, patch_parse_ctx *ctx) | |
117 | { | |
118 | int path_len, error = 0; | |
119 | ||
120 | path_len = header_path_len(ctx); | |
121 | ||
122 | if ((error = git_buf_put(path, ctx->line, path_len)) < 0) | |
123 | goto done; | |
124 | ||
125 | parse_advance_chars(ctx, path_len); | |
126 | ||
127 | git_buf_rtrim(path); | |
128 | ||
129 | if (path->size > 0 && path->ptr[0] == '"') | |
130 | error = git_buf_unquote(path); | |
131 | ||
132 | if (error < 0) | |
133 | goto done; | |
134 | ||
135 | git_path_squash_slashes(path); | |
136 | ||
137 | done: | |
138 | return error; | |
139 | } | |
140 | ||
141 | static int parse_header_path(char **out, patch_parse_ctx *ctx) | |
142 | { | |
143 | git_buf path = GIT_BUF_INIT; | |
144 | int error = parse_header_path_buf(&path, ctx); | |
145 | ||
146 | *out = git_buf_detach(&path); | |
147 | ||
148 | return error; | |
149 | } | |
150 | ||
151 | static int parse_header_git_oldpath( | |
152 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
153 | { | |
82175084 | 154 | return parse_header_path(&patch->old_path, ctx); |
804d5fe9 ET |
155 | } |
156 | ||
157 | static int parse_header_git_newpath( | |
158 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
159 | { | |
82175084 | 160 | return parse_header_path(&patch->new_path, ctx); |
804d5fe9 ET |
161 | } |
162 | ||
163 | static int parse_header_mode(uint16_t *mode, patch_parse_ctx *ctx) | |
164 | { | |
165 | const char *end; | |
166 | int32_t m; | |
167 | int ret; | |
168 | ||
169 | if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) | |
170 | return parse_err("invalid file mode at line %d", ctx->line_num); | |
171 | ||
172 | if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0) | |
173 | return ret; | |
174 | ||
175 | if (m > UINT16_MAX) | |
176 | return -1; | |
177 | ||
178 | *mode = (uint16_t)m; | |
179 | ||
180 | parse_advance_chars(ctx, (end - ctx->line)); | |
181 | ||
182 | return ret; | |
183 | } | |
184 | ||
185 | static int parse_header_oid( | |
186 | git_oid *oid, | |
f941f035 | 187 | int *oid_len, |
804d5fe9 ET |
188 | patch_parse_ctx *ctx) |
189 | { | |
190 | size_t len; | |
191 | ||
192 | for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) { | |
193 | if (!git__isxdigit(ctx->line[len])) | |
194 | break; | |
195 | } | |
196 | ||
197 | if (len < GIT_OID_MINPREFIXLEN || | |
198 | git_oid_fromstrn(oid, ctx->line, len) < 0) | |
199 | return parse_err("invalid hex formatted object id at line %d", | |
200 | ctx->line_num); | |
201 | ||
202 | parse_advance_chars(ctx, len); | |
203 | ||
f941f035 | 204 | *oid_len = (int)len; |
804d5fe9 ET |
205 | |
206 | return 0; | |
207 | } | |
208 | ||
209 | static int parse_header_git_index( | |
210 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
211 | { | |
d68cb736 ET |
212 | if (parse_header_oid(&patch->base.delta->old_file.id, |
213 | &patch->base.delta->old_file.id_abbrev, ctx) < 0 || | |
804d5fe9 | 214 | parse_advance_expected(ctx, "..", 2) < 0 || |
d68cb736 ET |
215 | parse_header_oid(&patch->base.delta->new_file.id, |
216 | &patch->base.delta->new_file.id_abbrev, ctx) < 0) | |
804d5fe9 ET |
217 | return -1; |
218 | ||
219 | if (ctx->line_len > 0 && ctx->line[0] == ' ') { | |
220 | uint16_t mode; | |
221 | ||
222 | parse_advance_chars(ctx, 1); | |
223 | ||
224 | if (parse_header_mode(&mode, ctx) < 0) | |
225 | return -1; | |
226 | ||
227 | if (!patch->base.delta->new_file.mode) | |
228 | patch->base.delta->new_file.mode = mode; | |
229 | ||
230 | if (!patch->base.delta->old_file.mode) | |
231 | patch->base.delta->old_file.mode = mode; | |
232 | } | |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
237 | static int parse_header_git_oldmode( | |
238 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
239 | { | |
b85bd8ce | 240 | return parse_header_mode(&patch->base.delta->old_file.mode, ctx); |
804d5fe9 ET |
241 | } |
242 | ||
243 | static int parse_header_git_newmode( | |
244 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
245 | { | |
b85bd8ce | 246 | return parse_header_mode(&patch->base.delta->new_file.mode, ctx); |
804d5fe9 ET |
247 | } |
248 | ||
249 | static int parse_header_git_deletedfilemode( | |
250 | git_patch_parsed *patch, | |
251 | patch_parse_ctx *ctx) | |
252 | { | |
b85bd8ce | 253 | git__free((char *)patch->base.delta->old_file.path); |
804d5fe9 | 254 | |
b85bd8ce | 255 | patch->base.delta->old_file.path = NULL; |
804d5fe9 | 256 | patch->base.delta->status = GIT_DELTA_DELETED; |
bc6a31c9 | 257 | patch->base.delta->nfiles = 1; |
804d5fe9 | 258 | |
b85bd8ce | 259 | return parse_header_mode(&patch->base.delta->old_file.mode, ctx); |
804d5fe9 ET |
260 | } |
261 | ||
262 | static int parse_header_git_newfilemode( | |
263 | git_patch_parsed *patch, | |
264 | patch_parse_ctx *ctx) | |
265 | { | |
b85bd8ce | 266 | git__free((char *)patch->base.delta->new_file.path); |
804d5fe9 | 267 | |
b85bd8ce | 268 | patch->base.delta->new_file.path = NULL; |
804d5fe9 | 269 | patch->base.delta->status = GIT_DELTA_ADDED; |
bc6a31c9 | 270 | patch->base.delta->nfiles = 1; |
804d5fe9 | 271 | |
b85bd8ce | 272 | return parse_header_mode(&patch->base.delta->new_file.mode, ctx); |
804d5fe9 ET |
273 | } |
274 | ||
275 | static int parse_header_rename( | |
276 | char **out, | |
804d5fe9 ET |
277 | patch_parse_ctx *ctx) |
278 | { | |
279 | git_buf path = GIT_BUF_INIT; | |
804d5fe9 ET |
280 | |
281 | if (parse_header_path_buf(&path, ctx) < 0) | |
282 | return -1; | |
283 | ||
82175084 ET |
284 | /* Note: the `rename from` and `rename to` lines include the literal |
285 | * filename. They do *not* include the prefix. (Who needs consistency?) | |
286 | */ | |
287 | *out = git_buf_detach(&path); | |
804d5fe9 ET |
288 | return 0; |
289 | } | |
290 | ||
291 | static int parse_header_renamefrom( | |
292 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
293 | { | |
19e46645 | 294 | patch->base.delta->status = GIT_DELTA_RENAMED; |
82175084 | 295 | return parse_header_rename(&patch->rename_old_path, ctx); |
804d5fe9 ET |
296 | } |
297 | ||
298 | static int parse_header_renameto( | |
299 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
300 | { | |
19e46645 | 301 | patch->base.delta->status = GIT_DELTA_RENAMED; |
82175084 | 302 | return parse_header_rename(&patch->rename_new_path, ctx); |
804d5fe9 ET |
303 | } |
304 | ||
305 | static int parse_header_percent(uint16_t *out, patch_parse_ctx *ctx) | |
306 | { | |
307 | int32_t val; | |
308 | const char *end; | |
309 | ||
310 | if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) || | |
311 | git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0) | |
312 | return -1; | |
313 | ||
314 | parse_advance_chars(ctx, (end - ctx->line)); | |
315 | ||
316 | if (parse_advance_expected(ctx, "%", 1) < 0) | |
317 | return -1; | |
318 | ||
319 | if (val > 100) | |
320 | return -1; | |
321 | ||
322 | *out = val; | |
323 | return 0; | |
324 | } | |
325 | ||
326 | static int parse_header_similarity( | |
327 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
328 | { | |
329 | if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) | |
330 | return parse_err("invalid similarity percentage at line %d", | |
331 | ctx->line_num); | |
332 | ||
333 | return 0; | |
334 | } | |
335 | ||
336 | static int parse_header_dissimilarity( | |
337 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
338 | { | |
339 | uint16_t dissimilarity; | |
340 | ||
341 | if (parse_header_percent(&dissimilarity, ctx) < 0) | |
342 | return parse_err("invalid similarity percentage at line %d", | |
343 | ctx->line_num); | |
344 | ||
345 | patch->base.delta->similarity = 100 - dissimilarity; | |
346 | ||
347 | return 0; | |
348 | } | |
349 | ||
350 | typedef struct { | |
351 | const char *str; | |
352 | int(*fn)(git_patch_parsed *, patch_parse_ctx *); | |
353 | } header_git_op; | |
354 | ||
355 | static const header_git_op header_git_ops[] = { | |
356 | { "@@ -", NULL }, | |
357 | { "GIT binary patch", NULL }, | |
358 | { "--- ", parse_header_git_oldpath }, | |
359 | { "+++ ", parse_header_git_newpath }, | |
360 | { "index ", parse_header_git_index }, | |
361 | { "old mode ", parse_header_git_oldmode }, | |
362 | { "new mode ", parse_header_git_newmode }, | |
363 | { "deleted file mode ", parse_header_git_deletedfilemode }, | |
364 | { "new file mode ", parse_header_git_newfilemode }, | |
365 | { "rename from ", parse_header_renamefrom }, | |
366 | { "rename to ", parse_header_renameto }, | |
367 | { "rename old ", parse_header_renamefrom }, | |
368 | { "rename new ", parse_header_renameto }, | |
369 | { "similarity index ", parse_header_similarity }, | |
370 | { "dissimilarity index ", parse_header_dissimilarity }, | |
371 | }; | |
372 | ||
373 | static int parse_header_git( | |
374 | git_patch_parsed *patch, | |
375 | patch_parse_ctx *ctx) | |
376 | { | |
377 | size_t i; | |
378 | int error = 0; | |
379 | ||
380 | /* Parse the diff --git line */ | |
381 | if (parse_advance_expected(ctx, "diff --git ", 11) < 0) | |
382 | return parse_err("corrupt git diff header at line %d", ctx->line_num); | |
383 | ||
82175084 | 384 | if (parse_header_path(&patch->header_old_path, ctx) < 0) |
804d5fe9 ET |
385 | return parse_err("corrupt old path in git diff header at line %d", |
386 | ctx->line_num); | |
387 | ||
388 | if (parse_advance_ws(ctx) < 0 || | |
82175084 | 389 | parse_header_path(&patch->header_new_path, ctx) < 0) |
804d5fe9 ET |
390 | return parse_err("corrupt new path in git diff header at line %d", |
391 | ctx->line_num); | |
392 | ||
393 | /* Parse remaining header lines */ | |
394 | for (parse_advance_line(ctx); ctx->remain > 0; parse_advance_line(ctx)) { | |
395 | if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') | |
396 | break; | |
397 | ||
398 | for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) { | |
399 | const header_git_op *op = &header_git_ops[i]; | |
400 | size_t op_len = strlen(op->str); | |
401 | ||
402 | if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0) | |
403 | continue; | |
404 | ||
405 | /* Do not advance if this is the patch separator */ | |
406 | if (op->fn == NULL) | |
407 | goto done; | |
408 | ||
409 | parse_advance_chars(ctx, op_len); | |
410 | ||
411 | if ((error = op->fn(patch, ctx)) < 0) | |
412 | goto done; | |
413 | ||
414 | parse_advance_ws(ctx); | |
415 | parse_advance_expected(ctx, "\n", 1); | |
416 | ||
417 | if (ctx->line_len > 0) { | |
418 | error = parse_err("trailing data at line %d", ctx->line_num); | |
419 | goto done; | |
420 | } | |
421 | ||
422 | break; | |
423 | } | |
424 | } | |
425 | ||
426 | done: | |
427 | return error; | |
428 | } | |
429 | ||
430 | static int parse_number(git_off_t *out, patch_parse_ctx *ctx) | |
431 | { | |
432 | const char *end; | |
433 | int64_t num; | |
434 | ||
435 | if (!git__isdigit(ctx->line[0])) | |
436 | return -1; | |
437 | ||
438 | if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0) | |
439 | return -1; | |
440 | ||
441 | if (num < 0) | |
442 | return -1; | |
443 | ||
444 | *out = num; | |
445 | parse_advance_chars(ctx, (end - ctx->line)); | |
446 | ||
447 | return 0; | |
448 | } | |
449 | ||
450 | static int parse_int(int *out, patch_parse_ctx *ctx) | |
451 | { | |
452 | git_off_t num; | |
453 | ||
454 | if (parse_number(&num, ctx) < 0 || !git__is_int(num)) | |
455 | return -1; | |
456 | ||
457 | *out = (int)num; | |
458 | return 0; | |
459 | } | |
460 | ||
461 | static int parse_hunk_header( | |
462 | git_patch_hunk *hunk, | |
463 | patch_parse_ctx *ctx) | |
464 | { | |
465 | const char *header_start = ctx->line; | |
466 | ||
467 | hunk->hunk.old_lines = 1; | |
468 | hunk->hunk.new_lines = 1; | |
469 | ||
470 | if (parse_advance_expected(ctx, "@@ -", 4) < 0 || | |
471 | parse_int(&hunk->hunk.old_start, ctx) < 0) | |
472 | goto fail; | |
473 | ||
474 | if (ctx->line_len > 0 && ctx->line[0] == ',') { | |
475 | if (parse_advance_expected(ctx, ",", 1) < 0 || | |
476 | parse_int(&hunk->hunk.old_lines, ctx) < 0) | |
477 | goto fail; | |
478 | } | |
479 | ||
480 | if (parse_advance_expected(ctx, " +", 2) < 0 || | |
481 | parse_int(&hunk->hunk.new_start, ctx) < 0) | |
482 | goto fail; | |
483 | ||
484 | if (ctx->line_len > 0 && ctx->line[0] == ',') { | |
485 | if (parse_advance_expected(ctx, ",", 1) < 0 || | |
486 | parse_int(&hunk->hunk.new_lines, ctx) < 0) | |
487 | goto fail; | |
488 | } | |
489 | ||
490 | if (parse_advance_expected(ctx, " @@", 3) < 0) | |
491 | goto fail; | |
492 | ||
493 | parse_advance_line(ctx); | |
494 | ||
495 | if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) | |
496 | goto fail; | |
497 | ||
498 | hunk->hunk.header_len = ctx->line - header_start; | |
499 | if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) | |
500 | return parse_err("oversized patch hunk header at line %d", | |
501 | ctx->line_num); | |
502 | ||
503 | memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); | |
504 | hunk->hunk.header[hunk->hunk.header_len] = '\0'; | |
505 | ||
506 | return 0; | |
507 | ||
508 | fail: | |
509 | giterr_set(GITERR_PATCH, "invalid patch hunk header at line %d", | |
510 | ctx->line_num); | |
511 | return -1; | |
512 | } | |
513 | ||
514 | static int parse_hunk_body( | |
515 | git_patch_parsed *patch, | |
516 | git_patch_hunk *hunk, | |
517 | patch_parse_ctx *ctx) | |
518 | { | |
519 | git_diff_line *line; | |
520 | int error = 0; | |
521 | ||
522 | int oldlines = hunk->hunk.old_lines; | |
523 | int newlines = hunk->hunk.new_lines; | |
524 | ||
525 | for (; | |
526 | ctx->remain > 4 && (oldlines || newlines) && | |
527 | memcmp(ctx->line, "@@ -", 4) != 0; | |
528 | parse_advance_line(ctx)) { | |
529 | ||
530 | int origin; | |
531 | int prefix = 1; | |
532 | ||
533 | if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') { | |
534 | error = parse_err("invalid patch instruction at line %d", | |
535 | ctx->line_num); | |
536 | goto done; | |
537 | } | |
538 | ||
539 | switch (ctx->line[0]) { | |
540 | case '\n': | |
541 | prefix = 0; | |
542 | ||
543 | case ' ': | |
544 | origin = GIT_DIFF_LINE_CONTEXT; | |
545 | oldlines--; | |
546 | newlines--; | |
547 | break; | |
548 | ||
549 | case '-': | |
550 | origin = GIT_DIFF_LINE_DELETION; | |
551 | oldlines--; | |
552 | break; | |
553 | ||
554 | case '+': | |
555 | origin = GIT_DIFF_LINE_ADDITION; | |
556 | newlines--; | |
557 | break; | |
558 | ||
559 | default: | |
560 | error = parse_err("invalid patch hunk at line %d", ctx->line_num); | |
561 | goto done; | |
562 | } | |
563 | ||
564 | line = git_array_alloc(patch->base.lines); | |
565 | GITERR_CHECK_ALLOC(line); | |
566 | ||
567 | memset(line, 0x0, sizeof(git_diff_line)); | |
568 | ||
569 | line->content = ctx->line + prefix; | |
570 | line->content_len = ctx->line_len - prefix; | |
571 | line->content_offset = ctx->content_len - ctx->remain; | |
572 | line->origin = origin; | |
573 | ||
574 | hunk->line_count++; | |
575 | } | |
576 | ||
577 | if (oldlines || newlines) { | |
578 | error = parse_err( | |
579 | "invalid patch hunk, expected %d old lines and %d new lines", | |
580 | hunk->hunk.old_lines, hunk->hunk.new_lines); | |
581 | goto done; | |
582 | } | |
583 | ||
584 | /* Handle "\ No newline at end of file". Only expect the leading | |
585 | * backslash, though, because the rest of the string could be | |
586 | * localized. Because `diff` optimizes for the case where you | |
587 | * want to apply the patch by hand. | |
588 | */ | |
589 | if (ctx->line_len >= 2 && memcmp(ctx->line, "\\ ", 2) == 0 && | |
590 | git_array_size(patch->base.lines) > 0) { | |
591 | ||
592 | line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); | |
593 | ||
594 | if (line->content_len < 1) { | |
595 | error = parse_err("cannot trim trailing newline of empty line"); | |
596 | goto done; | |
597 | } | |
598 | ||
599 | line->content_len--; | |
600 | ||
601 | parse_advance_line(ctx); | |
602 | } | |
603 | ||
604 | done: | |
605 | return error; | |
606 | } | |
607 | ||
608 | static int parsed_patch_header( | |
609 | git_patch_parsed *patch, | |
610 | patch_parse_ctx *ctx) | |
611 | { | |
612 | int error = 0; | |
613 | ||
614 | for (ctx->line = ctx->content; ctx->remain > 0; parse_advance_line(ctx)) { | |
615 | /* This line is too short to be a patch header. */ | |
616 | if (ctx->line_len < 6) | |
617 | continue; | |
618 | ||
619 | /* This might be a hunk header without a patch header, provide a | |
620 | * sensible error message. */ | |
621 | if (memcmp(ctx->line, "@@ -", 4) == 0) { | |
622 | size_t line_num = ctx->line_num; | |
623 | git_patch_hunk hunk; | |
624 | ||
625 | /* If this cannot be parsed as a hunk header, it's just leading | |
626 | * noise, continue. | |
627 | */ | |
628 | if (parse_hunk_header(&hunk, ctx) < 0) { | |
629 | giterr_clear(); | |
630 | continue; | |
631 | } | |
632 | ||
633 | error = parse_err("invalid hunk header outside patch at line %d", | |
634 | line_num); | |
635 | goto done; | |
636 | } | |
637 | ||
638 | /* This buffer is too short to contain a patch. */ | |
639 | if (ctx->remain < ctx->line_len + 6) | |
640 | break; | |
641 | ||
642 | /* A proper git patch */ | |
643 | if (ctx->line_len >= 11 && memcmp(ctx->line, "diff --git ", 11) == 0) { | |
82175084 | 644 | error = parse_header_git(patch, ctx); |
804d5fe9 ET |
645 | goto done; |
646 | } | |
647 | ||
648 | error = 0; | |
649 | continue; | |
650 | } | |
651 | ||
652 | error = parse_err("no header in patch file"); | |
653 | ||
654 | done: | |
655 | return error; | |
656 | } | |
657 | ||
658 | static int parsed_patch_binary_side( | |
659 | git_diff_binary_file *binary, | |
660 | patch_parse_ctx *ctx) | |
661 | { | |
662 | git_diff_binary_t type = GIT_DIFF_BINARY_NONE; | |
663 | git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT; | |
664 | git_off_t len; | |
665 | int error = 0; | |
666 | ||
667 | if (ctx->line_len >= 8 && memcmp(ctx->line, "literal ", 8) == 0) { | |
668 | type = GIT_DIFF_BINARY_LITERAL; | |
669 | parse_advance_chars(ctx, 8); | |
670 | } | |
671 | else if (ctx->line_len >= 6 && memcmp(ctx->line, "delta ", 6) == 0) { | |
672 | type = GIT_DIFF_BINARY_DELTA; | |
673 | parse_advance_chars(ctx, 6); | |
674 | } | |
675 | else { | |
676 | error = parse_err("unknown binary delta type at line %d", ctx->line_num); | |
677 | goto done; | |
678 | } | |
679 | ||
680 | if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) { | |
681 | error = parse_err("invalid binary size at line %d", ctx->line_num); | |
682 | goto done; | |
683 | } | |
684 | ||
685 | while (ctx->line_len) { | |
686 | char c = ctx->line[0]; | |
687 | size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; | |
688 | ||
689 | if (c == '\n') | |
690 | break; | |
691 | else if (c >= 'A' && c <= 'Z') | |
692 | decoded_len = c - 'A' + 1; | |
693 | else if (c >= 'a' && c <= 'z') | |
694 | decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; | |
695 | ||
696 | if (!decoded_len) { | |
697 | error = parse_err("invalid binary length at line %d", ctx->line_num); | |
698 | goto done; | |
699 | } | |
700 | ||
701 | parse_advance_chars(ctx, 1); | |
702 | ||
703 | encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; | |
704 | ||
705 | if (encoded_len > ctx->line_len - 1) { | |
706 | error = parse_err("truncated binary data at line %d", ctx->line_num); | |
707 | goto done; | |
708 | } | |
709 | ||
710 | if ((error = git_buf_decode_base85( | |
711 | &decoded, ctx->line, encoded_len, decoded_len)) < 0) | |
712 | goto done; | |
713 | ||
714 | if (decoded.size - decoded_orig != decoded_len) { | |
715 | error = parse_err("truncated binary data at line %d", ctx->line_num); | |
716 | goto done; | |
717 | } | |
718 | ||
719 | parse_advance_chars(ctx, encoded_len); | |
720 | ||
721 | if (parse_advance_nl(ctx) < 0) { | |
722 | error = parse_err("trailing data at line %d", ctx->line_num); | |
723 | goto done; | |
724 | } | |
725 | } | |
726 | ||
727 | binary->type = type; | |
728 | binary->inflatedlen = (size_t)len; | |
729 | binary->datalen = decoded.size; | |
730 | binary->data = git_buf_detach(&decoded); | |
731 | ||
732 | done: | |
733 | git_buf_free(&base85); | |
734 | git_buf_free(&decoded); | |
735 | return error; | |
736 | } | |
737 | ||
738 | static int parsed_patch_binary( | |
739 | git_patch_parsed *patch, | |
740 | patch_parse_ctx *ctx) | |
741 | { | |
742 | int error; | |
743 | ||
744 | if (parse_advance_expected(ctx, "GIT binary patch", 16) < 0 || | |
745 | parse_advance_nl(ctx) < 0) | |
746 | return parse_err("corrupt git binary header at line %d", ctx->line_num); | |
747 | ||
748 | /* parse old->new binary diff */ | |
749 | if ((error = parsed_patch_binary_side( | |
750 | &patch->base.binary.new_file, ctx)) < 0) | |
751 | return error; | |
752 | ||
753 | if (parse_advance_nl(ctx) < 0) | |
754 | return parse_err("corrupt git binary separator at line %d", | |
755 | ctx->line_num); | |
756 | ||
757 | /* parse new->old binary diff */ | |
758 | if ((error = parsed_patch_binary_side( | |
759 | &patch->base.binary.old_file, ctx)) < 0) | |
760 | return error; | |
761 | ||
762 | patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; | |
763 | return 0; | |
764 | } | |
765 | ||
766 | static int parsed_patch_hunks( | |
767 | git_patch_parsed *patch, | |
768 | patch_parse_ctx *ctx) | |
769 | { | |
770 | git_patch_hunk *hunk; | |
771 | int error = 0; | |
772 | ||
773 | for (; ctx->line_len > 4 && memcmp(ctx->line, "@@ -", 4) == 0; ) { | |
774 | ||
775 | hunk = git_array_alloc(patch->base.hunks); | |
776 | GITERR_CHECK_ALLOC(hunk); | |
777 | ||
778 | memset(hunk, 0, sizeof(git_patch_hunk)); | |
779 | ||
780 | hunk->line_start = git_array_size(patch->base.lines); | |
781 | hunk->line_count = 0; | |
782 | ||
783 | if ((error = parse_hunk_header(hunk, ctx)) < 0 || | |
784 | (error = parse_hunk_body(patch, hunk, ctx)) < 0) | |
785 | goto done; | |
786 | } | |
787 | ||
788 | done: | |
789 | return error; | |
790 | } | |
791 | ||
792 | static int parsed_patch_body( | |
793 | git_patch_parsed *patch, patch_parse_ctx *ctx) | |
794 | { | |
795 | if (ctx->line_len >= 16 && memcmp(ctx->line, "GIT binary patch", 16) == 0) | |
796 | return parsed_patch_binary(patch, ctx); | |
797 | ||
798 | else if (ctx->line_len >= 4 && memcmp(ctx->line, "@@ -", 4) == 0) | |
799 | return parsed_patch_hunks(patch, ctx); | |
800 | ||
801 | return 0; | |
802 | } | |
803 | ||
82175084 ET |
804 | int check_header_names( |
805 | const char *one, | |
806 | const char *two, | |
807 | const char *old_or_new, | |
808 | bool two_null) | |
809 | { | |
810 | if (!one || !two) | |
811 | return 0; | |
812 | ||
813 | if (two_null && strcmp(two, "/dev/null") != 0) | |
814 | return parse_err("expected %s path of '/dev/null'", old_or_new); | |
815 | ||
816 | else if (!two_null && strcmp(one, two) != 0) | |
817 | return parse_err("mismatched %s path names", old_or_new); | |
818 | ||
819 | return 0; | |
820 | } | |
821 | ||
822 | static int check_prefix( | |
823 | char **out, | |
824 | size_t *out_len, | |
825 | git_patch_parsed *patch, | |
826 | const char *path_start) | |
827 | { | |
828 | const char *path = path_start; | |
829 | uint32_t remain = patch->opts.prefix_len; | |
830 | ||
831 | *out = NULL; | |
832 | *out_len = 0; | |
833 | ||
834 | if (patch->opts.prefix_len == 0) | |
835 | goto done; | |
836 | ||
837 | /* leading slashes do not count as part of the prefix in git apply */ | |
838 | while (*path == '/') | |
839 | path++; | |
840 | ||
841 | while (*path && remain) { | |
842 | if (*path == '/') | |
843 | remain--; | |
844 | ||
845 | path++; | |
846 | } | |
847 | ||
848 | if (remain || !*path) | |
849 | return parse_err("header filename does not contain %d path components", | |
850 | patch->opts.prefix_len); | |
851 | ||
852 | done: | |
853 | *out_len = (path - path_start); | |
854 | *out = git__strndup(path_start, *out_len); | |
855 | ||
856 | return (out == NULL) ? -1 : 0; | |
857 | } | |
858 | ||
859 | static int check_filenames(git_patch_parsed *patch) | |
804d5fe9 | 860 | { |
82175084 ET |
861 | const char *prefixed_new, *prefixed_old; |
862 | size_t old_prefixlen = 0, new_prefixlen = 0; | |
863 | bool added = (patch->base.delta->status == GIT_DELTA_ADDED); | |
864 | bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); | |
865 | ||
866 | if (patch->old_path && !patch->new_path) | |
867 | return parse_err("missing new path"); | |
868 | ||
869 | if (!patch->old_path && patch->new_path) | |
870 | return parse_err("missing old path"); | |
871 | ||
872 | /* Ensure (non-renamed) paths match */ | |
873 | if (check_header_names( | |
874 | patch->header_old_path, patch->old_path, "old", added) < 0 || | |
875 | check_header_names( | |
876 | patch->header_new_path, patch->new_path, "new", deleted) < 0) | |
877 | return -1; | |
878 | ||
879 | prefixed_old = (!added && patch->old_path) ? patch->old_path : | |
880 | patch->header_old_path; | |
881 | prefixed_new = (!deleted && patch->new_path) ? patch->new_path : | |
882 | patch->header_new_path; | |
883 | ||
884 | if (check_prefix( | |
885 | &patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0 || | |
886 | check_prefix( | |
887 | &patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0) | |
888 | return -1; | |
889 | ||
890 | /* Prefer the rename filenames as they are unambiguous and unprefixed */ | |
891 | if (patch->rename_old_path) | |
892 | patch->base.delta->old_file.path = patch->rename_old_path; | |
893 | else | |
894 | patch->base.delta->old_file.path = prefixed_old + old_prefixlen; | |
895 | ||
896 | if (patch->rename_new_path) | |
897 | patch->base.delta->new_file.path = patch->rename_new_path; | |
898 | else | |
899 | patch->base.delta->new_file.path = prefixed_new + new_prefixlen; | |
900 | ||
b85bd8ce | 901 | if (!patch->base.delta->old_file.path && |
82175084 ET |
902 | !patch->base.delta->new_file.path) |
903 | return parse_err("git diff header lacks old / new paths"); | |
804d5fe9 | 904 | |
82175084 ET |
905 | return 0; |
906 | } | |
907 | ||
908 | static int check_patch(git_patch_parsed *patch) | |
909 | { | |
910 | if (check_filenames(patch) < 0) | |
911 | return -1; | |
804d5fe9 | 912 | |
d536ceac ET |
913 | if (patch->base.delta->old_file.path && |
914 | patch->base.delta->status != GIT_DELTA_DELETED && | |
915 | !patch->base.delta->new_file.mode) | |
916 | patch->base.delta->new_file.mode = patch->base.delta->old_file.mode; | |
804d5fe9 ET |
917 | |
918 | if (patch->base.delta->status == GIT_DELTA_MODIFIED && | |
919 | !(patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) && | |
b85bd8ce | 920 | patch->base.delta->new_file.mode == patch->base.delta->old_file.mode && |
804d5fe9 ET |
921 | git_array_size(patch->base.hunks) == 0) |
922 | return parse_err("patch with no hunks"); | |
923 | ||
924 | return 0; | |
925 | } | |
926 | ||
82175084 ET |
927 | static void patch_parsed__free(git_patch *p) |
928 | { | |
929 | git_patch_parsed *patch = (git_patch_parsed *)p; | |
930 | ||
931 | if (!patch) | |
932 | return; | |
933 | ||
934 | git__free(patch->old_prefix); | |
935 | git__free(patch->new_prefix); | |
936 | git__free(patch->header_old_path); | |
937 | git__free(patch->header_new_path); | |
938 | git__free(patch->rename_old_path); | |
939 | git__free(patch->rename_new_path); | |
940 | git__free(patch->old_path); | |
941 | git__free(patch->new_path); | |
6278fbc5 ET |
942 | git_array_clear(patch->base.hunks); |
943 | git_array_clear(patch->base.lines); | |
944 | git__free(patch->base.delta); | |
945 | git__free(patch); | |
82175084 ET |
946 | } |
947 | ||
804d5fe9 ET |
948 | int git_patch_from_patchfile( |
949 | git_patch **out, | |
950 | const char *content, | |
82175084 ET |
951 | size_t content_len, |
952 | git_patch_options *opts) | |
804d5fe9 ET |
953 | { |
954 | patch_parse_ctx ctx = { 0 }; | |
955 | git_patch_parsed *patch; | |
82175084 | 956 | git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; |
804d5fe9 ET |
957 | int error = 0; |
958 | ||
959 | *out = NULL; | |
960 | ||
961 | patch = git__calloc(1, sizeof(git_patch_parsed)); | |
962 | GITERR_CHECK_ALLOC(patch); | |
963 | ||
82175084 ET |
964 | if (opts) |
965 | memcpy(&patch->opts, opts, sizeof(git_patch_options)); | |
966 | else | |
967 | memcpy(&patch->opts, &default_opts, sizeof(git_patch_options)); | |
968 | ||
969 | patch->base.free_fn = patch_parsed__free; | |
e7ec327d | 970 | |
804d5fe9 ET |
971 | patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); |
972 | patch->base.delta->status = GIT_DELTA_MODIFIED; | |
bc6a31c9 | 973 | patch->base.delta->nfiles = 2; |
804d5fe9 ET |
974 | |
975 | ctx.content = content; | |
976 | ctx.content_len = content_len; | |
977 | ctx.remain = content_len; | |
978 | ||
979 | if ((error = parsed_patch_header(patch, &ctx)) < 0 || | |
980 | (error = parsed_patch_body(patch, &ctx)) < 0 || | |
981 | (error = check_patch(patch)) < 0) | |
982 | goto done; | |
983 | ||
82175084 ET |
984 | patch->base.diff_opts.old_prefix = patch->old_prefix; |
985 | patch->base.diff_opts.new_prefix = patch->new_prefix; | |
986 | patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; | |
987 | ||
804d5fe9 ET |
988 | GIT_REFCOUNT_INC(patch); |
989 | *out = &patch->base; | |
990 | ||
991 | done: | |
6278fbc5 ET |
992 | if (error < 0) |
993 | patch_parsed__free(&patch->base); | |
994 | ||
804d5fe9 ET |
995 | return error; |
996 | } |