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