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