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