File Coverage

deps/libgit2/src/patch_parse.c
Criterion Covered Total %
statement 323 580 55.6
branch 156 376 41.4
condition n/a
subroutine n/a
pod n/a
total 479 956 50.1


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