]> git.proxmox.com Git - libgit2.git/blob - src/patch_parse.c
Introduce git_patch_options, handle prefixes
[libgit2.git] / src / patch_parse.c
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;
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;
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;
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 {
154 return parse_header_path(&patch->old_path, ctx);
155 }
156
157 static int parse_header_git_newpath(
158 git_patch_parsed *patch, patch_parse_ctx *ctx)
159 {
160 return parse_header_path(&patch->new_path, ctx);
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,
187 size_t *oid_len,
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
204 *oid_len = len;
205
206 return 0;
207 }
208
209 static int parse_header_git_index(
210 git_patch_parsed *patch, patch_parse_ctx *ctx)
211 {
212 if (parse_header_oid(&patch->base.delta->old_file.id,
213 &patch->base.delta->old_file.id_abbrev, ctx) < 0 ||
214 parse_advance_expected(ctx, "..", 2) < 0 ||
215 parse_header_oid(&patch->base.delta->new_file.id,
216 &patch->base.delta->new_file.id_abbrev, ctx) < 0)
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 {
240 return parse_header_mode(&patch->base.delta->old_file.mode, ctx);
241 }
242
243 static int parse_header_git_newmode(
244 git_patch_parsed *patch, patch_parse_ctx *ctx)
245 {
246 return parse_header_mode(&patch->base.delta->new_file.mode, ctx);
247 }
248
249 static int parse_header_git_deletedfilemode(
250 git_patch_parsed *patch,
251 patch_parse_ctx *ctx)
252 {
253 git__free((char *)patch->base.delta->old_file.path);
254
255 patch->base.delta->old_file.path = NULL;
256 patch->base.delta->status = GIT_DELTA_DELETED;
257 patch->base.delta->nfiles = 1;
258
259 return parse_header_mode(&patch->base.delta->old_file.mode, ctx);
260 }
261
262 static int parse_header_git_newfilemode(
263 git_patch_parsed *patch,
264 patch_parse_ctx *ctx)
265 {
266 git__free((char *)patch->base.delta->new_file.path);
267
268 patch->base.delta->new_file.path = NULL;
269 patch->base.delta->status = GIT_DELTA_ADDED;
270 patch->base.delta->nfiles = 1;
271
272 return parse_header_mode(&patch->base.delta->new_file.mode, ctx);
273 }
274
275 static int parse_header_rename(
276 char **out,
277 patch_parse_ctx *ctx)
278 {
279 git_buf path = GIT_BUF_INIT;
280
281 if (parse_header_path_buf(&path, ctx) < 0)
282 return -1;
283
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);
288 return 0;
289 }
290
291 static int parse_header_renamefrom(
292 git_patch_parsed *patch, patch_parse_ctx *ctx)
293 {
294 patch->base.delta->status = GIT_DELTA_RENAMED;
295 return parse_header_rename(&patch->rename_old_path, ctx);
296 }
297
298 static int parse_header_renameto(
299 git_patch_parsed *patch, patch_parse_ctx *ctx)
300 {
301 patch->base.delta->status = GIT_DELTA_RENAMED;
302 return parse_header_rename(&patch->rename_new_path, ctx);
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
384 if (parse_header_path(&patch->header_old_path, ctx) < 0)
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 ||
389 parse_header_path(&patch->header_new_path, ctx) < 0)
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) {
644 error = parse_header_git(patch, ctx);
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
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)
860 {
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
901 if (!patch->base.delta->old_file.path &&
902 !patch->base.delta->new_file.path)
903 return parse_err("git diff header lacks old / new paths");
904
905 return 0;
906 }
907
908 static int check_patch(git_patch_parsed *patch)
909 {
910 if (check_filenames(patch) < 0)
911 return -1;
912
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;
917
918 if (patch->base.delta->status == GIT_DELTA_MODIFIED &&
919 !(patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) &&
920 patch->base.delta->new_file.mode == patch->base.delta->old_file.mode &&
921 git_array_size(patch->base.hunks) == 0)
922 return parse_err("patch with no hunks");
923
924 return 0;
925 }
926
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);
942 }
943
944 int git_patch_from_patchfile(
945 git_patch **out,
946 const char *content,
947 size_t content_len,
948 git_patch_options *opts)
949 {
950 patch_parse_ctx ctx = { 0 };
951 git_patch_parsed *patch;
952 git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT;
953 int error = 0;
954
955 *out = NULL;
956
957 patch = git__calloc(1, sizeof(git_patch_parsed));
958 GITERR_CHECK_ALLOC(patch);
959
960 if (opts)
961 memcpy(&patch->opts, opts, sizeof(git_patch_options));
962 else
963 memcpy(&patch->opts, &default_opts, sizeof(git_patch_options));
964
965 patch->base.free_fn = patch_parsed__free;
966
967 patch->base.delta = git__calloc(1, sizeof(git_diff_delta));
968 patch->base.delta->status = GIT_DELTA_MODIFIED;
969 patch->base.delta->nfiles = 2;
970
971 ctx.content = content;
972 ctx.content_len = content_len;
973 ctx.remain = content_len;
974
975 if ((error = parsed_patch_header(patch, &ctx)) < 0 ||
976 (error = parsed_patch_body(patch, &ctx)) < 0 ||
977 (error = check_patch(patch)) < 0)
978 goto done;
979
980 patch->base.diff_opts.old_prefix = patch->old_prefix;
981 patch->base.diff_opts.new_prefix = patch->new_prefix;
982 patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY;
983
984 GIT_REFCOUNT_INC(patch);
985 *out = &patch->base;
986
987 done:
988 return error;
989 }