mailinfo: support format=flowed

Add best-effort support for patches sent using format=flowed (RFC 3676). Remove leading spaces ("unstuff"), remove soft line breaks (indicated by space + newline), but leave the signature separator (dash dash space newline) alone. Warn in git am when encountering a format=flowed patch, because any trailing spaces would most probably be lost, as the sending MUA is encouraged to remove them when preparing the email. Provide a test patch formatted by Mozilla Thunderbird 60 using its default configuration. It reuses the contents of the file mailinfo.c before and after this patch. Signed-off-by: Rene Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Aug 25, 2018 at 23:50 UTC 3aa4d81f88d2f09173e65eebe35a385b2a064c87
7 files changed +2646 -2
builtin/am.c
+4
@@ -1243,6 +1243,10 @@ static int parse_mail(struct am_state *state, const char *mail)
1243 fclose(mi.input);
1244 fclose(mi.output);
1245
1246 + if (mi.format_flowed)
1247 + warning(_("Patch sent with format=flowed; "
1248 + "space at the end of lines might be lost."));
1249 +
1250 /* Extract message and author information */
1251 fp = xfopen(am_path(state, "info"), "r");
1252 while (!strbuf_getline_lf(&sb, fp)) {
mailinfo.c
+62 -2
@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
237 return 1;
238 }
239
240 +static int has_attr_value(const char *line, const char *name, const char *value)
241 +{
242 + struct strbuf sb = STRBUF_INIT;
243 + int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
244 + strbuf_release(&sb);
245 + return rc;
246 +}
247 +
248 static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
249 {
250 struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
251 strbuf_init(boundary, line->len);
252
253 + mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
254 + mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
255 +
256 if (slurp_attr(line->buf, "boundary=", boundary)) {
257 strbuf_insert(boundary, 0, "--", 2);
258 if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
@@ -964,6 +975,52 @@ again:
975 return 1;
976 }
977
978 +static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
979 + struct strbuf *prev)
980 +{
981 + size_t len = line->len;
982 + const char *rest;
983 +
984 + if (!mi->format_flowed) {
985 + handle_filter(mi, line);
986 + return;
987 + }
988 +
989 + if (line->buf[len - 1] == '\n') {
990 + len--;
991 + if (len && line->buf[len - 1] == '\r')
992 + len--;
993 + }
994 +
995 + /* Keep signature separator as-is. */
996 + if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
997 + if (prev->len) {
998 + handle_filter(mi, prev);
999 + strbuf_reset(prev);
1000 + }
1001 + handle_filter(mi, line);
1002 + return;
1003 + }
1004 +
1005 + /* Unstuff space-stuffed line. */
1006 + if (len && line->buf[0] == ' ') {
1007 + strbuf_remove(line, 0, 1);
1008 + len--;
1009 + }
1010 +
1011 + /* Save flowed line for later, but without the soft line break. */
1012 + if (len && line->buf[len - 1] == ' ') {
1013 + strbuf_add(prev, line->buf, len - !!mi->delsp);
1014 + return;
1015 + }
1016 +
1017 + /* Prepend any previous partial lines */
1018 + strbuf_insert(line, 0, prev->buf, prev->len);
1019 + strbuf_reset(prev);
1020 +
1021 + handle_filter(mi, line);
1022 +}
1023 +
1024 static void handle_body(struct mailinfo *mi, struct strbuf *line)
1025 {
1026 struct strbuf prev = STRBUF_INIT;
@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
1069 strbuf_addbuf(&prev, sb);
1070 break;
1071 }
1015 - handle_filter(mi, sb);
1072 + handle_filter_flowed(mi, sb, &prev);
1073 }
1074 /*
1075 * The partial chunk is saved in "prev" and will be
@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
1079 break;
1080 }
1081 default:
1025 - handle_filter(mi, line);
1082 + handle_filter_flowed(mi, line, &prev);
1083 }
1084
1085 if (mi->input_error)
1086 break;
1087 } while (!strbuf_getwholeline(line, mi->input, '\n'));
1088
1089 + if (prev.len)
1090 + handle_filter(mi, &prev);
1091 +
1092 flush_inbody_header_accum(mi);
1093
1094 handle_body_out:
mailinfo.h
+2
@@ -20,6 +20,8 @@ struct mailinfo {
20 struct strbuf *content[MAX_BOUNDARIES];
21 struct strbuf **content_top;
22 struct strbuf charset;
23 + unsigned int format_flowed:1;
24 + unsigned int delsp:1;
25 char *message_id;
26 enum {
27 TE_DONTCARE, TE_QP, TE_BASE64
t/t4256-am-format-flowed.sh new
+19
@@ -0,0 +1,19 @@
1 +#!/bin/sh
2 +
3 +test_description='test format=flowed support of git am'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup' '
8 + cp "$TEST_DIRECTORY/t4256/1/mailinfo.c.orig" mailinfo.c &&
9 + git add mailinfo.c &&
10 + git commit -m initial
11 +'
12 +
13 +test_expect_success 'am with format=flowed' '
14 + git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr &&
15 + test_i18ngrep "warning: Patch sent with format=flowed" stderr &&
16 + test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c
17 +'
18 +
19 +test_done
t/t4256/1/mailinfo.c new
+1245
@@ -0,0 +1,1245 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "utf8.h"
4 +#include "strbuf.h"
5 +#include "mailinfo.h"
6 +
7 +static void cleanup_space(struct strbuf *sb)
8 +{
9 + size_t pos, cnt;
10 + for (pos = 0; pos < sb->len; pos++) {
11 + if (isspace(sb->buf[pos])) {
12 + sb->buf[pos] = ' ';
13 + for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
14 + strbuf_remove(sb, pos + 1, cnt);
15 + }
16 + }
17 +}
18 +
19 +static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
20 +{
21 + struct strbuf *src = name;
22 + if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
23 + strchr(name->buf, '<') || strchr(name->buf, '>'))
24 + src = email;
25 + else if (name == out)
26 + return;
27 + strbuf_reset(out);
28 + strbuf_addbuf(out, src);
29 +}
30 +
31 +static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
32 +{
33 + /* John Doe <johndoe> */
34 +
35 + char *bra, *ket;
36 + /* This is fallback, so do not bother if we already have an
37 + * e-mail address.
38 + */
39 + if (mi->email.len)
40 + return;
41 +
42 + bra = strchr(line->buf, '<');
43 + if (!bra)
44 + return;
45 + ket = strchr(bra, '>');
46 + if (!ket)
47 + return;
48 +
49 + strbuf_reset(&mi->email);
50 + strbuf_add(&mi->email, bra + 1, ket - bra - 1);
51 +
52 + strbuf_reset(&mi->name);
53 + strbuf_add(&mi->name, line->buf, bra - line->buf);
54 + strbuf_trim(&mi->name);
55 + get_sane_name(&mi->name, &mi->name, &mi->email);
56 +}
57 +
58 +static const char *unquote_comment(struct strbuf *outbuf, const char *in)
59 +{
60 + int c;
61 + int take_next_literally = 0;
62 +
63 + strbuf_addch(outbuf, '(');
64 +
65 + while ((c = *in++) != 0) {
66 + if (take_next_literally == 1) {
67 + take_next_literally = 0;
68 + } else {
69 + switch (c) {
70 + case '\\':
71 + take_next_literally = 1;
72 + continue;
73 + case '(':
74 + in = unquote_comment(outbuf, in);
75 + continue;
76 + case ')':
77 + strbuf_addch(outbuf, ')');
78 + return in;
79 + }
80 + }
81 +
82 + strbuf_addch(outbuf, c);
83 + }
84 +
85 + return in;
86 +}
87 +
88 +static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
89 +{
90 + int c;
91 + int take_next_literally = 0;
92 +
93 + while ((c = *in++) != 0) {
94 + if (take_next_literally == 1) {
95 + take_next_literally = 0;
96 + } else {
97 + switch (c) {
98 + case '\\':
99 + take_next_literally = 1;
100 + continue;
101 + case '"':
102 + return in;
103 + }
104 + }
105 +
106 + strbuf_addch(outbuf, c);
107 + }
108 +
109 + return in;
110 +}
111 +
112 +static void unquote_quoted_pair(struct strbuf *line)
113 +{
114 + struct strbuf outbuf;
115 + const char *in = line->buf;
116 + int c;
117 +
118 + strbuf_init(&outbuf, line->len);
119 +
120 + while ((c = *in++) != 0) {
121 + switch (c) {
122 + case '"':
123 + in = unquote_quoted_string(&outbuf, in);
124 + continue;
125 + case '(':
126 + in = unquote_comment(&outbuf, in);
127 + continue;
128 + }
129 +
130 + strbuf_addch(&outbuf, c);
131 + }
132 +
133 + strbuf_swap(&outbuf, line);
134 + strbuf_release(&outbuf);
135 +
136 +}
137 +
138 +static void handle_from(struct mailinfo *mi, const struct strbuf *from)
139 +{
140 + char *at;
141 + size_t el;
142 + struct strbuf f;
143 +
144 + strbuf_init(&f, from->len);
145 + strbuf_addbuf(&f, from);
146 +
147 + unquote_quoted_pair(&f);
148 +
149 + at = strchr(f.buf, '@');
150 + if (!at) {
151 + parse_bogus_from(mi, from);
152 + goto out;
153 + }
154 +
155 + /*
156 + * If we already have one email, don't take any confusing lines
157 + */
158 + if (mi->email.len && strchr(at + 1, '@'))
159 + goto out;
160 +
161 + /* Pick up the string around '@', possibly delimited with <>
162 + * pair; that is the email part.
163 + */
164 + while (at > f.buf) {
165 + char c = at[-1];
166 + if (isspace(c))
167 + break;
168 + if (c == '<') {
169 + at[-1] = ' ';
170 + break;
171 + }
172 + at--;
173 + }
174 + el = strcspn(at, " \n\t\r\v\f>");
175 + strbuf_reset(&mi->email);
176 + strbuf_add(&mi->email, at, el);
177 + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
178 +
179 + /* The remainder is name. It could be
180 + *
181 + * - "John Doe <john.doe@xz>" (a), or
182 + * - "john.doe@xz (John Doe)" (b), or
183 + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
184 + *
185 + * but we have removed the email part, so
186 + *
187 + * - remove extra spaces which could stay after email (case 'c'), and
188 + * - trim from both ends, possibly removing the () pair at the end
189 + * (cases 'a' and 'b').
190 + */
191 + cleanup_space(&f);
192 + strbuf_trim(&f);
193 + if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
194 + strbuf_remove(&f, 0, 1);
195 + strbuf_setlen(&f, f.len - 1);
196 + }
197 +
198 + get_sane_name(&mi->name, &f, &mi->email);
199 +out:
200 + strbuf_release(&f);
201 +}
202 +
203 +static void handle_header(struct strbuf **out, const struct strbuf *line)
204 +{
205 + if (!*out) {
206 + *out = xmalloc(sizeof(struct strbuf));
207 + strbuf_init(*out, line->len);
208 + } else
209 + strbuf_reset(*out);
210 +
211 + strbuf_addbuf(*out, line);
212 +}
213 +
214 +/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
215 + * to have enough heuristics to grok MIME encoded patches often found
216 + * on our mailing lists. For example, we do not even treat header lines
217 + * case insensitively.
218 + */
219 +
220 +static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
221 +{
222 + const char *ends, *ap = strcasestr(line, name);
223 + size_t sz;
224 +
225 + strbuf_setlen(attr, 0);
226 + if (!ap)
227 + return 0;
228 + ap += strlen(name);
229 + if (*ap == '"') {
230 + ap++;
231 + ends = "\"";
232 + }
233 + else
234 + ends = "; \t";
235 + sz = strcspn(ap, ends);
236 + strbuf_add(attr, ap, sz);
237 + return 1;
238 +}
239 +
240 +static int has_attr_value(const char *line, const char *name, const char *value)
241 +{
242 + struct strbuf sb = STRBUF_INIT;
243 + int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
244 + strbuf_release(&sb);
245 + return rc;
246 +}
247 +
248 +static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
249 +{
250 + struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
251 + strbuf_init(boundary, line->len);
252 +
253 + mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
254 + mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
255 +
256 + if (slurp_attr(line->buf, "boundary=", boundary)) {
257 + strbuf_insert(boundary, 0, "--", 2);
258 + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
259 + error("Too many boundaries to handle");
260 + mi->input_error = -1;
261 + mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
262 + return;
263 + }
264 + *(mi->content_top) = boundary;
265 + boundary = NULL;
266 + }
267 + slurp_attr(line->buf, "charset=", &mi->charset);
268 +
269 + if (boundary) {
270 + strbuf_release(boundary);
271 + free(boundary);
272 + }
273 +}
274 +
275 +static void handle_content_transfer_encoding(struct mailinfo *mi,
276 + const struct strbuf *line)
277 +{
278 + if (strcasestr(line->buf, "base64"))
279 + mi->transfer_encoding = TE_BASE64;
280 + else if (strcasestr(line->buf, "quoted-printable"))
281 + mi->transfer_encoding = TE_QP;
282 + else
283 + mi->transfer_encoding = TE_DONTCARE;
284 +}
285 +
286 +static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
287 +{
288 + struct strbuf *content_top = *(mi->content_top);
289 +
290 + return ((content_top->len <= line->len) &&
291 + !memcmp(line->buf, content_top->buf, content_top->len));
292 +}
293 +
294 +static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
295 +{
296 + size_t at = 0;
297 +
298 + while (at < subject->len) {
299 + char *pos;
300 + size_t remove;
301 +
302 + switch (subject->buf[at]) {
303 + case 'r': case 'R':
304 + if (subject->len <= at + 3)
305 + break;
306 + if ((subject->buf[at + 1] == 'e' ||
307 + subject->buf[at + 1] == 'E') &&
308 + subject->buf[at + 2] == ':') {
309 + strbuf_remove(subject, at, 3);
310 + continue;
311 + }
312 + at++;
313 + break;
314 + case ' ': case '\t': case ':':
315 + strbuf_remove(subject, at, 1);
316 + continue;
317 + case '[':
318 + pos = strchr(subject->buf + at, ']');
319 + if (!pos)
320 + break;
321 + remove = pos - subject->buf + at + 1;
322 + if (!mi->keep_non_patch_brackets_in_subject ||
323 + (7 <= remove &&
324 + memmem(subject->buf + at, remove, "PATCH", 5)))
325 + strbuf_remove(subject, at, remove);
326 + else {
327 + at += remove;
328 + /*
329 + * If the input had a space after the ], keep
330 + * it. We don't bother with finding the end of
331 + * the space, since we later normalize it
332 + * anyway.
333 + */
334 + if (isspace(subject->buf[at]))
335 + at += 1;
336 + }
337 + continue;
338 + }
339 + break;
340 + }
341 + strbuf_trim(subject);
342 +}
343 +
344 +#define MAX_HDR_PARSED 10
345 +static const char *header[MAX_HDR_PARSED] = {
346 + "From","Subject","Date",
347 +};
348 +
349 +static inline int cmp_header(const struct strbuf *line, const char *hdr)
350 +{
351 + int len = strlen(hdr);
352 + return !strncasecmp(line->buf, hdr, len) && line->len > len &&
353 + line->buf[len] == ':' && isspace(line->buf[len + 1]);
354 +}
355 +
356 +static int is_format_patch_separator(const char *line, int len)
357 +{
358 + static const char SAMPLE[] =
359 + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
360 + const char *cp;
361 +
362 + if (len != strlen(SAMPLE))
363 + return 0;
364 + if (!skip_prefix(line, "From ", &cp))
365 + return 0;
366 + if (strspn(cp, "0123456789abcdef") != 40)
367 + return 0;
368 + cp += 40;
369 + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
370 +}
371 +
372 +static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
373 +{
374 + const char *in = q_seg->buf;
375 + int c;
376 + struct strbuf *out = xmalloc(sizeof(struct strbuf));
377 + strbuf_init(out, q_seg->len);
378 +
379 + while ((c = *in++) != 0) {
380 + if (c == '=') {
381 + int ch, d = *in;
382 + if (d == '\n' || !d)
383 + break; /* drop trailing newline */
384 + ch = hex2chr(in);
385 + if (ch >= 0) {
386 + strbuf_addch(out, ch);
387 + in += 2;
388 + continue;
389 + }
390 + /* garbage -- fall through */
391 + }
392 + if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
393 + c = 0x20;
394 + strbuf_addch(out, c);
395 + }
396 + return out;
397 +}
398 +
399 +static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
400 +{
401 + /* Decode in..ep, possibly in-place to ot */
402 + int c, pos = 0, acc = 0;
403 + const char *in = b_seg->buf;
404 + struct strbuf *out = xmalloc(sizeof(struct strbuf));
405 + strbuf_init(out, b_seg->len);
406 +
407 + while ((c = *in++) != 0) {
408 + if (c == '+')
409 + c = 62;
410 + else if (c == '/')
411 + c = 63;
412 + else if ('A' <= c && c <= 'Z')
413 + c -= 'A';
414 + else if ('a' <= c && c <= 'z')
415 + c -= 'a' - 26;
416 + else if ('0' <= c && c <= '9')
417 + c -= '0' - 52;
418 + else
419 + continue; /* garbage */
420 + switch (pos++) {
421 + case 0:
422 + acc = (c << 2);
423 + break;
424 + case 1:
425 + strbuf_addch(out, (acc | (c >> 4)));
426 + acc = (c & 15) << 4;
427 + break;
428 + case 2:
429 + strbuf_addch(out, (acc | (c >> 2)));
430 + acc = (c & 3) << 6;
431 + break;
432 + case 3:
433 + strbuf_addch(out, (acc | c));
434 + acc = pos = 0;
435 + break;
436 + }
437 + }
438 + return out;
439 +}
440 +
441 +static int convert_to_utf8(struct mailinfo *mi,
442 + struct strbuf *line, const char *charset)
443 +{
444 + char *out;
445 +
446 + if (!mi->metainfo_charset || !charset || !*charset)
447 + return 0;
448 +
449 + if (same_encoding(mi->metainfo_charset, charset))
450 + return 0;
451 + out = reencode_string(line->buf, mi->metainfo_charset, charset);
452 + if (!out) {
453 + mi->input_error = -1;
454 + return error("cannot convert from %s to %s",
455 + charset, mi->metainfo_charset);
456 + }
457 + strbuf_attach(line, out, strlen(out), strlen(out));
458 + return 0;
459 +}
460 +
461 +static void decode_header(struct mailinfo *mi, struct strbuf *it)
462 +{
463 + char *in, *ep, *cp;
464 + struct strbuf outbuf = STRBUF_INIT, *dec;
465 + struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
466 + int found_error = 1; /* pessimism */
467 +
468 + in = it->buf;
469 + while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
470 + int encoding;
471 + strbuf_reset(&charset_q);
472 + strbuf_reset(&piecebuf);
473 +
474 + if (in != ep) {
475 + /*
476 + * We are about to process an encoded-word
477 + * that begins at ep, but there is something
478 + * before the encoded word.
479 + */
480 + char *scan;
481 + for (scan = in; scan < ep; scan++)
482 + if (!isspace(*scan))
483 + break;
484 +
485 + if (scan != ep || in == it->buf) {
486 + /*
487 + * We should not lose that "something",
488 + * unless we have just processed an
489 + * encoded-word, and there is only LWS
490 + * before the one we are about to process.
491 + */
492 + strbuf_add(&outbuf, in, ep - in);
493 + }
494 + }
495 + /* E.g.
496 + * ep : "=?iso-2022-jp?B?GyR...?= foo"
497 + * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
498 + */
499 + ep += 2;
500 +
501 + if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
502 + goto release_return;
503 +
504 + if (cp + 3 - it->buf > it->len)
505 + goto release_return;
506 + strbuf_add(&charset_q, ep, cp - ep);
507 +
508 + encoding = cp[1];
509 + if (!encoding || cp[2] != '?')
510 + goto release_return;
511 + ep = strstr(cp + 3, "?=");
512 + if (!ep)
513 + goto release_return;
514 + strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
515 + switch (tolower(encoding)) {
516 + default:
517 + goto release_return;
518 + case 'b':
519 + dec = decode_b_segment(&piecebuf);
520 + break;
521 + case 'q':
522 + dec = decode_q_segment(&piecebuf, 1);
523 + break;
524 + }
525 + if (convert_to_utf8(mi, dec, charset_q.buf))
526 + goto release_return;
527 +
528 + strbuf_addbuf(&outbuf, dec);
529 + strbuf_release(dec);
530 + free(dec);
531 + in = ep + 2;
532 + }
533 + strbuf_addstr(&outbuf, in);
534 + strbuf_reset(it);
535 + strbuf_addbuf(it, &outbuf);
536 + found_error = 0;
537 +release_return:
538 + strbuf_release(&outbuf);
539 + strbuf_release(&charset_q);
540 + strbuf_release(&piecebuf);
541 +
542 + if (found_error)
543 + mi->input_error = -1;
544 +}
545 +
546 +static int check_header(struct mailinfo *mi,
547 + const struct strbuf *line,
548 + struct strbuf *hdr_data[], int overwrite)
549 +{
550 + int i, ret = 0, len;
551 + struct strbuf sb = STRBUF_INIT;
552 +
553 + /* search for the interesting parts */
554 + for (i = 0; header[i]; i++) {
555 + int len = strlen(header[i]);
556 + if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
557 + /* Unwrap inline B and Q encoding, and optionally
558 + * normalize the meta information to utf8.
559 + */
560 + strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
561 + decode_header(mi, &sb);
562 + handle_header(&hdr_data[i], &sb);
563 + ret = 1;
564 + goto check_header_out;
565 + }
566 + }
567 +
568 + /* Content stuff */
569 + if (cmp_header(line, "Content-Type")) {
570 + len = strlen("Content-Type: ");
571 + strbuf_add(&sb, line->buf + len, line->len - len);
572 + decode_header(mi, &sb);
573 + strbuf_insert(&sb, 0, "Content-Type: ", len);
574 + handle_content_type(mi, &sb);
575 + ret = 1;
576 + goto check_header_out;
577 + }
578 + if (cmp_header(line, "Content-Transfer-Encoding")) {
579 + len = strlen("Content-Transfer-Encoding: ");
580 + strbuf_add(&sb, line->buf + len, line->len - len);
581 + decode_header(mi, &sb);
582 + handle_content_transfer_encoding(mi, &sb);
583 + ret = 1;
584 + goto check_header_out;
585 + }
586 + if (cmp_header(line, "Message-Id")) {
587 + len = strlen("Message-Id: ");
588 + strbuf_add(&sb, line->buf + len, line->len - len);
589 + decode_header(mi, &sb);
590 + if (mi->add_message_id)
591 + mi->message_id = strbuf_detach(&sb, NULL);
592 + ret = 1;
593 + goto check_header_out;
594 + }
595 +
596 +check_header_out:
597 + strbuf_release(&sb);
598 + return ret;
599 +}
600 +
601 +/*
602 + * Returns 1 if the given line or any line beginning with the given line is an
603 + * in-body header (that is, check_header will succeed when passed
604 + * mi->s_hdr_data).
605 + */
606 +static int is_inbody_header(const struct mailinfo *mi,
607 + const struct strbuf *line)
608 +{
609 + int i;
610 + for (i = 0; header[i]; i++)
611 + if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
612 + return 1;
613 + return 0;
614 +}
615 +
616 +static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
617 +{
618 + struct strbuf *ret;
619 +
620 + switch (mi->transfer_encoding) {
621 + case TE_QP:
622 + ret = decode_q_segment(line, 0);
623 + break;
624 + case TE_BASE64:
625 + ret = decode_b_segment(line);
626 + break;
627 + case TE_DONTCARE:
628 + default:
629 + return;
630 + }
631 + strbuf_reset(line);
632 + strbuf_addbuf(line, ret);
633 + strbuf_release(ret);
634 + free(ret);
635 +}
636 +
637 +static inline int patchbreak(const struct strbuf *line)
638 +{
639 + size_t i;
640 +
641 + /* Beginning of a "diff -" header? */
642 + if (starts_with(line->buf, "diff -"))
643 + return 1;
644 +
645 + /* CVS "Index: " line? */
646 + if (starts_with(line->buf, "Index: "))
647 + return 1;
648 +
649 + /*
650 + * "--- <filename>" starts patches without headers
651 + * "---<sp>*" is a manual separator
652 + */
653 + if (line->len < 4)
654 + return 0;
655 +
656 + if (starts_with(line->buf, "---")) {
657 + /* space followed by a filename? */
658 + if (line->buf[3] == ' ' && !isspace(line->buf[4]))
659 + return 1;
660 + /* Just whitespace? */
661 + for (i = 3; i < line->len; i++) {
662 + unsigned char c = line->buf[i];
663 + if (c == '\n')
664 + return 1;
665 + if (!isspace(c))
666 + break;
667 + }
668 + return 0;
669 + }
670 + return 0;
671 +}
672 +
673 +static int is_scissors_line(const char *line)
674 +{
675 + const char *c;
676 + int scissors = 0, gap = 0;
677 + const char *first_nonblank = NULL, *last_nonblank = NULL;
678 + int visible, perforation = 0, in_perforation = 0;
679 +
680 + for (c = line; *c; c++) {
681 + if (isspace(*c)) {
682 + if (in_perforation) {
683 + perforation++;
684 + gap++;
685 + }
686 + continue;
687 + }
688 + last_nonblank = c;
689 + if (first_nonblank == NULL)
690 + first_nonblank = c;
691 + if (*c == '-') {
692 + in_perforation = 1;
693 + perforation++;
694 + continue;
695 + }
696 + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
697 + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
698 + in_perforation = 1;
699 + perforation += 2;
700 + scissors += 2;
701 + c++;
702 + continue;
703 + }
704 + in_perforation = 0;
705 + }
706 +
707 + /*
708 + * The mark must be at least 8 bytes long (e.g. "-- >8 --").
709 + * Even though there can be arbitrary cruft on the same line
710 + * (e.g. "cut here"), in order to avoid misidentification, the
711 + * perforation must occupy more than a third of the visible
712 + * width of the line, and dashes and scissors must occupy more
713 + * than half of the perforation.
714 + */
715 +
716 + if (first_nonblank && last_nonblank)
717 + visible = last_nonblank - first_nonblank + 1;
718 + else
719 + visible = 0;
720 + return (scissors && 8 <= visible &&
721 + visible < perforation * 3 &&
722 + gap * 2 < perforation);
723 +}
724 +
725 +static void flush_inbody_header_accum(struct mailinfo *mi)
726 +{
727 + if (!mi->inbody_header_accum.len)
728 + return;
729 + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
730 + BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
731 + strbuf_reset(&mi->inbody_header_accum);
732 +}
733 +
734 +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
735 +{
736 + if (mi->inbody_header_accum.len &&
737 + (line->buf[0] == ' ' || line->buf[0] == '\t')) {
738 + if (mi->use_scissors && is_scissors_line(line->buf)) {
739 + /*
740 + * This is a scissors line; do not consider this line
741 + * as a header continuation line.
742 + */
743 + flush_inbody_header_accum(mi);
744 + return 0;
745 + }
746 + strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
747 + strbuf_addbuf(&mi->inbody_header_accum, line);
748 + return 1;
749 + }
750 +
751 + flush_inbody_header_accum(mi);
752 +
753 + if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
754 + return is_format_patch_separator(line->buf + 1, line->len - 1);
755 + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
756 + int i;
757 + for (i = 0; header[i]; i++)
758 + if (!strcmp("Subject", header[i])) {
759 + handle_header(&mi->s_hdr_data[i], line);
760 + return 1;
761 + }
762 + return 0;
763 + }
764 + if (is_inbody_header(mi, line)) {
765 + strbuf_addbuf(&mi->inbody_header_accum, line);
766 + return 1;
767 + }
768 + return 0;
769 +}
770 +
771 +static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
772 +{
773 + assert(!mi->filter_stage);
774 +
775 + if (mi->header_stage) {
776 + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) {
777 + if (mi->inbody_header_accum.len) {
778 + flush_inbody_header_accum(mi);
779 + mi->header_stage = 0;
780 + }
781 + return 0;
782 + }
783 + }
784 +
785 + if (mi->use_inbody_headers && mi->header_stage) {
786 + mi->header_stage = check_inbody_header(mi, line);
787 + if (mi->header_stage)
788 + return 0;
789 + } else
790 + /* Only trim the first (blank) line of the commit message
791 + * when ignoring in-body headers.
792 + */
793 + mi->header_stage = 0;
794 +
795 + /* normalize the log message to UTF-8. */
796 + if (convert_to_utf8(mi, line, mi->charset.buf))
797 + return 0; /* mi->input_error already set */
798 +
799 + if (mi->use_scissors && is_scissors_line(line->buf)) {
800 + int i;
801 +
802 + strbuf_setlen(&mi->log_message, 0);
803 + mi->header_stage = 1;
804 +
805 + /*
806 + * We may have already read "secondary headers"; purge
807 + * them to give ourselves a clean restart.
808 + */
809 + for (i = 0; header[i]; i++) {
810 + if (mi->s_hdr_data[i])
811 + strbuf_release(mi->s_hdr_data[i]);
812 + mi->s_hdr_data[i] = NULL;
813 + }
814 + return 0;
815 + }
816 +
817 + if (patchbreak(line)) {
818 + if (mi->message_id)
819 + strbuf_addf(&mi->log_message,
820 + "Message-Id: %s\n", mi->message_id);
821 + return 1;
822 + }
823 +
824 + strbuf_addbuf(&mi->log_message, line);
825 + return 0;
826 +}
827 +
828 +static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
829 +{
830 + fwrite(line->buf, 1, line->len, mi->patchfile);
831 + mi->patch_lines++;
832 +}
833 +
834 +static void handle_filter(struct mailinfo *mi, struct strbuf *line)
835 +{
836 + switch (mi->filter_stage) {
837 + case 0:
838 + if (!handle_commit_msg(mi, line))
839 + break;
840 + mi->filter_stage++;
841 + /* fallthrough */
842 + case 1:
843 + handle_patch(mi, line);
844 + break;
845 + }
846 +}
847 +
848 +static int is_rfc2822_header(const struct strbuf *line)
849 +{
850 + /*
851 + * The section that defines the loosest possible
852 + * field name is "3.6.8 Optional fields".
853 + *
854 + * optional-field = field-name ":" unstructured CRLF
855 + * field-name = 1*ftext
856 + * ftext = %d33-57 / %59-126
857 + */
858 + int ch;
859 + char *cp = line->buf;
860 +
861 + /* Count mbox From headers as headers */
862 + if (starts_with(cp, "From ") || starts_with(cp, ">From "))
863 + return 1;
864 +
865 + while ((ch = *cp++)) {
866 + if (ch == ':')
867 + return 1;
868 + if ((33 <= ch && ch <= 57) ||
869 + (59 <= ch && ch <= 126))
870 + continue;
871 + break;
872 + }
873 + return 0;
874 +}
875 +
876 +static int read_one_header_line(struct strbuf *line, FILE *in)
877 +{
878 + struct strbuf continuation = STRBUF_INIT;
879 +
880 + /* Get the first part of the line. */
881 + if (strbuf_getline_lf(line, in))
882 + return 0;
883 +
884 + /*
885 + * Is it an empty line or not a valid rfc2822 header?
886 + * If so, stop here, and return false ("not a header")
887 + */
888 + strbuf_rtrim(line);
889 + if (!line->len || !is_rfc2822_header(line)) {
890 + /* Re-add the newline */
891 + strbuf_addch(line, '\n');
892 + return 0;
893 + }
894 +
895 + /*
896 + * Now we need to eat all the continuation lines..
897 + * Yuck, 2822 header "folding"
898 + */
899 + for (;;) {
900 + int peek;
901 +
902 + peek = fgetc(in);
903 + if (peek == EOF)
904 + break;
905 + ungetc(peek, in);
906 + if (peek != ' ' && peek != '\t')
907 + break;
908 + if (strbuf_getline_lf(&continuation, in))
909 + break;
910 + continuation.buf[0] = ' ';
911 + strbuf_rtrim(&continuation);
912 + strbuf_addbuf(line, &continuation);
913 + }
914 + strbuf_release(&continuation);
915 +
916 + return 1;
917 +}
918 +
919 +static int find_boundary(struct mailinfo *mi, struct strbuf *line)
920 +{
921 + while (!strbuf_getline_lf(line, mi->input)) {
922 + if (*(mi->content_top) && is_multipart_boundary(mi, line))
923 + return 1;
924 + }
925 + return 0;
926 +}
927 +
928 +static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
929 +{
930 + struct strbuf newline = STRBUF_INIT;
931 +
932 + strbuf_addch(&newline, '\n');
933 +again:
934 + if (line->len >= (*(mi->content_top))->len + 2 &&
935 + !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
936 + /* we hit an end boundary */
937 + /* pop the current boundary off the stack */
938 + strbuf_release(*(mi->content_top));
939 + FREE_AND_NULL(*(mi->content_top));
940 +
941 + /* technically won't happen as is_multipart_boundary()
942 + will fail first. But just in case..
943 + */
944 + if (--mi->content_top < mi->content) {
945 + error("Detected mismatched boundaries, can't recover");
946 + mi->input_error = -1;
947 + mi->content_top = mi->content;
948 + strbuf_release(&newline);
949 + return 0;
950 + }
951 + handle_filter(mi, &newline);
952 + strbuf_release(&newline);
953 + if (mi->input_error)
954 + return 0;
955 +
956 + /* skip to the next boundary */
957 + if (!find_boundary(mi, line))
958 + return 0;
959 + goto again;
960 + }
961 +
962 + /* set some defaults */
963 + mi->transfer_encoding = TE_DONTCARE;
964 + strbuf_reset(&mi->charset);
965 +
966 + /* slurp in this section's info */
967 + while (read_one_header_line(line, mi->input))
968 + check_header(mi, line, mi->p_hdr_data, 0);
969 +
970 + strbuf_release(&newline);
971 + /* replenish line */
972 + if (strbuf_getline_lf(line, mi->input))
973 + return 0;
974 + strbuf_addch(line, '\n');
975 + return 1;
976 +}
977 +
978 +static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
979 + struct strbuf *prev)
980 +{
981 + size_t len = line->len;
982 + const char *rest;
983 +
984 + if (!mi->format_flowed) {
985 + handle_filter(mi, line);
986 + return;
987 + }
988 +
989 + if (line->buf[len - 1] == '\n') {
990 + len--;
991 + if (len && line->buf[len - 1] == '\r')
992 + len--;
993 + }
994 +
995 + /* Keep signature separator as-is. */
996 + if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
997 + if (prev->len) {
998 + handle_filter(mi, prev);
999 + strbuf_reset(prev);
1000 + }
1001 + handle_filter(mi, line);
1002 + return;
1003 + }
1004 +
1005 + /* Unstuff space-stuffed line. */
1006 + if (len && line->buf[0] == ' ') {
1007 + strbuf_remove(line, 0, 1);
1008 + len--;
1009 + }
1010 +
1011 + /* Save flowed line for later, but without the soft line break. */
1012 + if (len && line->buf[len - 1] == ' ') {
1013 + strbuf_add(prev, line->buf, len - !!mi->delsp);
1014 + return;
1015 + }
1016 +
1017 + /* Prepend any previous partial lines */
1018 + strbuf_insert(line, 0, prev->buf, prev->len);
1019 + strbuf_reset(prev);
1020 +
1021 + handle_filter(mi, line);
1022 +}
1023 +
1024 +static void handle_body(struct mailinfo *mi, struct strbuf *line)
1025 +{
1026 + struct strbuf prev = STRBUF_INIT;
1027 +
1028 + /* Skip up to the first boundary */
1029 + if (*(mi->content_top)) {
1030 + if (!find_boundary(mi, line))
1031 + goto handle_body_out;
1032 + }
1033 +
1034 + do {
1035 + /* process any boundary lines */
1036 + if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
1037 + /* flush any leftover */
1038 + if (prev.len) {
1039 + handle_filter(mi, &prev);
1040 + strbuf_reset(&prev);
1041 + }
1042 + if (!handle_boundary(mi, line))
1043 + goto handle_body_out;
1044 + }
1045 +
1046 + /* Unwrap transfer encoding */
1047 + decode_transfer_encoding(mi, line);
1048 +
1049 + switch (mi->transfer_encoding) {
1050 + case TE_BASE64:
1051 + case TE_QP:
1052 + {
1053 + struct strbuf **lines, **it, *sb;
1054 +
1055 + /* Prepend any previous partial lines */
1056 + strbuf_insert(line, 0, prev.buf, prev.len);
1057 + strbuf_reset(&prev);
1058 +
1059 + /*
1060 + * This is a decoded line that may contain
1061 + * multiple new lines. Pass only one chunk
1062 + * at a time to handle_filter()
1063 + */
1064 + lines = strbuf_split(line, '\n');
1065 + for (it = lines; (sb = *it); it++) {
1066 + if (*(it + 1) == NULL) /* The last line */
1067 + if (sb->buf[sb->len - 1] != '\n') {
1068 + /* Partial line, save it for later. */
1069 + strbuf_addbuf(&prev, sb);
1070 + break;
1071 + }
1072 + handle_filter_flowed(mi, sb, &prev);
1073 + }
1074 + /*
1075 + * The partial chunk is saved in "prev" and will be
1076 + * appended by the next iteration of read_line_with_nul().
1077 + */
1078 + strbuf_list_free(lines);
1079 + break;
1080 + }
1081 + default:
1082 + handle_filter_flowed(mi, line, &prev);
1083 + }
1084 +
1085 + if (mi->input_error)
1086 + break;
1087 + } while (!strbuf_getwholeline(line, mi->input, '\n'));
1088 +
1089 + if (prev.len)
1090 + handle_filter(mi, &prev);
1091 +
1092 + flush_inbody_header_accum(mi);
1093 +
1094 +handle_body_out:
1095 + strbuf_release(&prev);
1096 +}
1097 +
1098 +static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
1099 +{
1100 + const char *sp = data->buf;
1101 + while (1) {
1102 + char *ep = strchr(sp, '\n');
1103 + int len;
1104 + if (!ep)
1105 + len = strlen(sp);
1106 + else
1107 + len = ep - sp;
1108 + fprintf(fout, "%s: %.*s\n", hdr, len, sp);
1109 + if (!ep)
1110 + break;
1111 + sp = ep + 1;
1112 + }
1113 +}
1114 +
1115 +static void handle_info(struct mailinfo *mi)
1116 +{
1117 + struct strbuf *hdr;
1118 + int i;
1119 +
1120 + for (i = 0; header[i]; i++) {
1121 + /* only print inbody headers if we output a patch file */
1122 + if (mi->patch_lines && mi->s_hdr_data[i])
1123 + hdr = mi->s_hdr_data[i];
1124 + else if (mi->p_hdr_data[i])
1125 + hdr = mi->p_hdr_data[i];
1126 + else
1127 + continue;
1128 +
1129 + if (!strcmp(header[i], "Subject")) {
1130 + if (!mi->keep_subject) {
1131 + cleanup_subject(mi, hdr);
1132 + cleanup_space(hdr);
1133 + }
1134 + output_header_lines(mi->output, "Subject", hdr);
1135 + } else if (!strcmp(header[i], "From")) {
1136 + cleanup_space(hdr);
1137 + handle_from(mi, hdr);
1138 + fprintf(mi->output, "Author: %s\n", mi->name.buf);
1139 + fprintf(mi->output, "Email: %s\n", mi->email.buf);
1140 + } else {
1141 + cleanup_space(hdr);
1142 + fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
1143 + }
1144 + }
1145 + fprintf(mi->output, "\n");
1146 +}
1147 +
1148 +int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
1149 +{
1150 + FILE *cmitmsg;
1151 + int peek;
1152 + struct strbuf line = STRBUF_INIT;
1153 +
1154 + cmitmsg = fopen(msg, "w");
1155 + if (!cmitmsg) {
1156 + perror(msg);
1157 + return -1;
1158 + }
1159 + mi->patchfile = fopen(patch, "w");
1160 + if (!mi->patchfile) {
1161 + perror(patch);
1162 + fclose(cmitmsg);
1163 + return -1;
1164 + }
1165 +
1166 + mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
1167 + mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
1168 +
1169 + do {
1170 + peek = fgetc(mi->input);
1171 + if (peek == EOF) {
1172 + fclose(cmitmsg);
1173 + return error("empty patch: '%s'", patch);
1174 + }
1175 + } while (isspace(peek));
1176 + ungetc(peek, mi->input);
1177 +
1178 + /* process the email header */
1179 + while (read_one_header_line(&line, mi->input))
1180 + check_header(mi, &line, mi->p_hdr_data, 1);
1181 +
1182 + handle_body(mi, &line);
1183 + fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
1184 + fclose(cmitmsg);
1185 + fclose(mi->patchfile);
1186 +
1187 + handle_info(mi);
1188 + strbuf_release(&line);
1189 + return mi->input_error;
1190 +}
1191 +
1192 +static int git_mailinfo_config(const char *var, const char *value, void *mi_)
1193 +{
1194 + struct mailinfo *mi = mi_;
1195 +
1196 + if (!starts_with(var, "mailinfo."))
1197 + return git_default_config(var, value, NULL);
1198 + if (!strcmp(var, "mailinfo.scissors")) {
1199 + mi->use_scissors = git_config_bool(var, value);
1200 + return 0;
1201 + }
1202 + /* perhaps others here */
1203 + return 0;
1204 +}
1205 +
1206 +void setup_mailinfo(struct mailinfo *mi)
1207 +{
1208 + memset(mi, 0, sizeof(*mi));
1209 + strbuf_init(&mi->name, 0);
1210 + strbuf_init(&mi->email, 0);
1211 + strbuf_init(&mi->charset, 0);
1212 + strbuf_init(&mi->log_message, 0);
1213 + strbuf_init(&mi->inbody_header_accum, 0);
1214 + mi->header_stage = 1;
1215 + mi->use_inbody_headers = 1;
1216 + mi->content_top = mi->content;
1217 + git_config(git_mailinfo_config, mi);
1218 +}
1219 +
1220 +void clear_mailinfo(struct mailinfo *mi)
1221 +{
1222 + int i;
1223 +
1224 + strbuf_release(&mi->name);
1225 + strbuf_release(&mi->email);
1226 + strbuf_release(&mi->charset);
1227 + strbuf_release(&mi->inbody_header_accum);
1228 + free(mi->message_id);
1229 +
1230 + if (mi->p_hdr_data)
1231 + for (i = 0; mi->p_hdr_data[i]; i++)
1232 + strbuf_release(mi->p_hdr_data[i]);
1233 + free(mi->p_hdr_data);
1234 + if (mi->s_hdr_data)
1235 + for (i = 0; mi->s_hdr_data[i]; i++)
1236 + strbuf_release(mi->s_hdr_data[i]);
1237 + free(mi->s_hdr_data);
1238 +
1239 + while (mi->content < mi->content_top) {
1240 + free(*(mi->content_top));
1241 + mi->content_top--;
1242 + }
1243 +
1244 + strbuf_release(&mi->log_message);
1245 +}
t/t4256/1/mailinfo.c.orig new
+1185
@@ -0,0 +1,1185 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "utf8.h"
4 +#include "strbuf.h"
5 +#include "mailinfo.h"
6 +
7 +static void cleanup_space(struct strbuf *sb)
8 +{
9 + size_t pos, cnt;
10 + for (pos = 0; pos < sb->len; pos++) {
11 + if (isspace(sb->buf[pos])) {
12 + sb->buf[pos] = ' ';
13 + for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
14 + strbuf_remove(sb, pos + 1, cnt);
15 + }
16 + }
17 +}
18 +
19 +static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
20 +{
21 + struct strbuf *src = name;
22 + if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
23 + strchr(name->buf, '<') || strchr(name->buf, '>'))
24 + src = email;
25 + else if (name == out)
26 + return;
27 + strbuf_reset(out);
28 + strbuf_addbuf(out, src);
29 +}
30 +
31 +static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
32 +{
33 + /* John Doe <johndoe> */
34 +
35 + char *bra, *ket;
36 + /* This is fallback, so do not bother if we already have an
37 + * e-mail address.
38 + */
39 + if (mi->email.len)
40 + return;
41 +
42 + bra = strchr(line->buf, '<');
43 + if (!bra)
44 + return;
45 + ket = strchr(bra, '>');
46 + if (!ket)
47 + return;
48 +
49 + strbuf_reset(&mi->email);
50 + strbuf_add(&mi->email, bra + 1, ket - bra - 1);
51 +
52 + strbuf_reset(&mi->name);
53 + strbuf_add(&mi->name, line->buf, bra - line->buf);
54 + strbuf_trim(&mi->name);
55 + get_sane_name(&mi->name, &mi->name, &mi->email);
56 +}
57 +
58 +static const char *unquote_comment(struct strbuf *outbuf, const char *in)
59 +{
60 + int c;
61 + int take_next_literally = 0;
62 +
63 + strbuf_addch(outbuf, '(');
64 +
65 + while ((c = *in++) != 0) {
66 + if (take_next_literally == 1) {
67 + take_next_literally = 0;
68 + } else {
69 + switch (c) {
70 + case '\\':
71 + take_next_literally = 1;
72 + continue;
73 + case '(':
74 + in = unquote_comment(outbuf, in);
75 + continue;
76 + case ')':
77 + strbuf_addch(outbuf, ')');
78 + return in;
79 + }
80 + }
81 +
82 + strbuf_addch(outbuf, c);
83 + }
84 +
85 + return in;
86 +}
87 +
88 +static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
89 +{
90 + int c;
91 + int take_next_literally = 0;
92 +
93 + while ((c = *in++) != 0) {
94 + if (take_next_literally == 1) {
95 + take_next_literally = 0;
96 + } else {
97 + switch (c) {
98 + case '\\':
99 + take_next_literally = 1;
100 + continue;
101 + case '"':
102 + return in;
103 + }
104 + }
105 +
106 + strbuf_addch(outbuf, c);
107 + }
108 +
109 + return in;
110 +}
111 +
112 +static void unquote_quoted_pair(struct strbuf *line)
113 +{
114 + struct strbuf outbuf;
115 + const char *in = line->buf;
116 + int c;
117 +
118 + strbuf_init(&outbuf, line->len);
119 +
120 + while ((c = *in++) != 0) {
121 + switch (c) {
122 + case '"':
123 + in = unquote_quoted_string(&outbuf, in);
124 + continue;
125 + case '(':
126 + in = unquote_comment(&outbuf, in);
127 + continue;
128 + }
129 +
130 + strbuf_addch(&outbuf, c);
131 + }
132 +
133 + strbuf_swap(&outbuf, line);
134 + strbuf_release(&outbuf);
135 +
136 +}
137 +
138 +static void handle_from(struct mailinfo *mi, const struct strbuf *from)
139 +{
140 + char *at;
141 + size_t el;
142 + struct strbuf f;
143 +
144 + strbuf_init(&f, from->len);
145 + strbuf_addbuf(&f, from);
146 +
147 + unquote_quoted_pair(&f);
148 +
149 + at = strchr(f.buf, '@');
150 + if (!at) {
151 + parse_bogus_from(mi, from);
152 + goto out;
153 + }
154 +
155 + /*
156 + * If we already have one email, don't take any confusing lines
157 + */
158 + if (mi->email.len && strchr(at + 1, '@'))
159 + goto out;
160 +
161 + /* Pick up the string around '@', possibly delimited with <>
162 + * pair; that is the email part.
163 + */
164 + while (at > f.buf) {
165 + char c = at[-1];
166 + if (isspace(c))
167 + break;
168 + if (c == '<') {
169 + at[-1] = ' ';
170 + break;
171 + }
172 + at--;
173 + }
174 + el = strcspn(at, " \n\t\r\v\f>");
175 + strbuf_reset(&mi->email);
176 + strbuf_add(&mi->email, at, el);
177 + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
178 +
179 + /* The remainder is name. It could be
180 + *
181 + * - "John Doe <john.doe@xz>" (a), or
182 + * - "john.doe@xz (John Doe)" (b), or
183 + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
184 + *
185 + * but we have removed the email part, so
186 + *
187 + * - remove extra spaces which could stay after email (case 'c'), and
188 + * - trim from both ends, possibly removing the () pair at the end
189 + * (cases 'a' and 'b').
190 + */
191 + cleanup_space(&f);
192 + strbuf_trim(&f);
193 + if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
194 + strbuf_remove(&f, 0, 1);
195 + strbuf_setlen(&f, f.len - 1);
196 + }
197 +
198 + get_sane_name(&mi->name, &f, &mi->email);
199 +out:
200 + strbuf_release(&f);
201 +}
202 +
203 +static void handle_header(struct strbuf **out, const struct strbuf *line)
204 +{
205 + if (!*out) {
206 + *out = xmalloc(sizeof(struct strbuf));
207 + strbuf_init(*out, line->len);
208 + } else
209 + strbuf_reset(*out);
210 +
211 + strbuf_addbuf(*out, line);
212 +}
213 +
214 +/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
215 + * to have enough heuristics to grok MIME encoded patches often found
216 + * on our mailing lists. For example, we do not even treat header lines
217 + * case insensitively.
218 + */
219 +
220 +static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
221 +{
222 + const char *ends, *ap = strcasestr(line, name);
223 + size_t sz;
224 +
225 + strbuf_setlen(attr, 0);
226 + if (!ap)
227 + return 0;
228 + ap += strlen(name);
229 + if (*ap == '"') {
230 + ap++;
231 + ends = "\"";
232 + }
233 + else
234 + ends = "; \t";
235 + sz = strcspn(ap, ends);
236 + strbuf_add(attr, ap, sz);
237 + return 1;
238 +}
239 +
240 +static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
241 +{
242 + struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
243 + strbuf_init(boundary, line->len);
244 +
245 + if (slurp_attr(line->buf, "boundary=", boundary)) {
246 + strbuf_insert(boundary, 0, "--", 2);
247 + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
248 + error("Too many boundaries to handle");
249 + mi->input_error = -1;
250 + mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
251 + return;
252 + }
253 + *(mi->content_top) = boundary;
254 + boundary = NULL;
255 + }
256 + slurp_attr(line->buf, "charset=", &mi->charset);
257 +
258 + if (boundary) {
259 + strbuf_release(boundary);
260 + free(boundary);
261 + }
262 +}
263 +
264 +static void handle_content_transfer_encoding(struct mailinfo *mi,
265 + const struct strbuf *line)
266 +{
267 + if (strcasestr(line->buf, "base64"))
268 + mi->transfer_encoding = TE_BASE64;
269 + else if (strcasestr(line->buf, "quoted-printable"))
270 + mi->transfer_encoding = TE_QP;
271 + else
272 + mi->transfer_encoding = TE_DONTCARE;
273 +}
274 +
275 +static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
276 +{
277 + struct strbuf *content_top = *(mi->content_top);
278 +
279 + return ((content_top->len <= line->len) &&
280 + !memcmp(line->buf, content_top->buf, content_top->len));
281 +}
282 +
283 +static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
284 +{
285 + size_t at = 0;
286 +
287 + while (at < subject->len) {
288 + char *pos;
289 + size_t remove;
290 +
291 + switch (subject->buf[at]) {
292 + case 'r': case 'R':
293 + if (subject->len <= at + 3)
294 + break;
295 + if ((subject->buf[at + 1] == 'e' ||
296 + subject->buf[at + 1] == 'E') &&
297 + subject->buf[at + 2] == ':') {
298 + strbuf_remove(subject, at, 3);
299 + continue;
300 + }
301 + at++;
302 + break;
303 + case ' ': case '\t': case ':':
304 + strbuf_remove(subject, at, 1);
305 + continue;
306 + case '[':
307 + pos = strchr(subject->buf + at, ']');
308 + if (!pos)
309 + break;
310 + remove = pos - subject->buf + at + 1;
311 + if (!mi->keep_non_patch_brackets_in_subject ||
312 + (7 <= remove &&
313 + memmem(subject->buf + at, remove, "PATCH", 5)))
314 + strbuf_remove(subject, at, remove);
315 + else {
316 + at += remove;
317 + /*
318 + * If the input had a space after the ], keep
319 + * it. We don't bother with finding the end of
320 + * the space, since we later normalize it
321 + * anyway.
322 + */
323 + if (isspace(subject->buf[at]))
324 + at += 1;
325 + }
326 + continue;
327 + }
328 + break;
329 + }
330 + strbuf_trim(subject);
331 +}
332 +
333 +#define MAX_HDR_PARSED 10
334 +static const char *header[MAX_HDR_PARSED] = {
335 + "From","Subject","Date",
336 +};
337 +
338 +static inline int cmp_header(const struct strbuf *line, const char *hdr)
339 +{
340 + int len = strlen(hdr);
341 + return !strncasecmp(line->buf, hdr, len) && line->len > len &&
342 + line->buf[len] == ':' && isspace(line->buf[len + 1]);
343 +}
344 +
345 +static int is_format_patch_separator(const char *line, int len)
346 +{
347 + static const char SAMPLE[] =
348 + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
349 + const char *cp;
350 +
351 + if (len != strlen(SAMPLE))
352 + return 0;
353 + if (!skip_prefix(line, "From ", &cp))
354 + return 0;
355 + if (strspn(cp, "0123456789abcdef") != 40)
356 + return 0;
357 + cp += 40;
358 + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
359 +}
360 +
361 +static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
362 +{
363 + const char *in = q_seg->buf;
364 + int c;
365 + struct strbuf *out = xmalloc(sizeof(struct strbuf));
366 + strbuf_init(out, q_seg->len);
367 +
368 + while ((c = *in++) != 0) {
369 + if (c == '=') {
370 + int ch, d = *in;
371 + if (d == '\n' || !d)
372 + break; /* drop trailing newline */
373 + ch = hex2chr(in);
374 + if (ch >= 0) {
375 + strbuf_addch(out, ch);
376 + in += 2;
377 + continue;
378 + }
379 + /* garbage -- fall through */
380 + }
381 + if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
382 + c = 0x20;
383 + strbuf_addch(out, c);
384 + }
385 + return out;
386 +}
387 +
388 +static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
389 +{
390 + /* Decode in..ep, possibly in-place to ot */
391 + int c, pos = 0, acc = 0;
392 + const char *in = b_seg->buf;
393 + struct strbuf *out = xmalloc(sizeof(struct strbuf));
394 + strbuf_init(out, b_seg->len);
395 +
396 + while ((c = *in++) != 0) {
397 + if (c == '+')
398 + c = 62;
399 + else if (c == '/')
400 + c = 63;
401 + else if ('A' <= c && c <= 'Z')
402 + c -= 'A';
403 + else if ('a' <= c && c <= 'z')
404 + c -= 'a' - 26;
405 + else if ('0' <= c && c <= '9')
406 + c -= '0' - 52;
407 + else
408 + continue; /* garbage */
409 + switch (pos++) {
410 + case 0:
411 + acc = (c << 2);
412 + break;
413 + case 1:
414 + strbuf_addch(out, (acc | (c >> 4)));
415 + acc = (c & 15) << 4;
416 + break;
417 + case 2:
418 + strbuf_addch(out, (acc | (c >> 2)));
419 + acc = (c & 3) << 6;
420 + break;
421 + case 3:
422 + strbuf_addch(out, (acc | c));
423 + acc = pos = 0;
424 + break;
425 + }
426 + }
427 + return out;
428 +}
429 +
430 +static int convert_to_utf8(struct mailinfo *mi,
431 + struct strbuf *line, const char *charset)
432 +{
433 + char *out;
434 +
435 + if (!mi->metainfo_charset || !charset || !*charset)
436 + return 0;
437 +
438 + if (same_encoding(mi->metainfo_charset, charset))
439 + return 0;
440 + out = reencode_string(line->buf, mi->metainfo_charset, charset);
441 + if (!out) {
442 + mi->input_error = -1;
443 + return error("cannot convert from %s to %s",
444 + charset, mi->metainfo_charset);
445 + }
446 + strbuf_attach(line, out, strlen(out), strlen(out));
447 + return 0;
448 +}
449 +
450 +static void decode_header(struct mailinfo *mi, struct strbuf *it)
451 +{
452 + char *in, *ep, *cp;
453 + struct strbuf outbuf = STRBUF_INIT, *dec;
454 + struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
455 + int found_error = 1; /* pessimism */
456 +
457 + in = it->buf;
458 + while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
459 + int encoding;
460 + strbuf_reset(&charset_q);
461 + strbuf_reset(&piecebuf);
462 +
463 + if (in != ep) {
464 + /*
465 + * We are about to process an encoded-word
466 + * that begins at ep, but there is something
467 + * before the encoded word.
468 + */
469 + char *scan;
470 + for (scan = in; scan < ep; scan++)
471 + if (!isspace(*scan))
472 + break;
473 +
474 + if (scan != ep || in == it->buf) {
475 + /*
476 + * We should not lose that "something",
477 + * unless we have just processed an
478 + * encoded-word, and there is only LWS
479 + * before the one we are about to process.
480 + */
481 + strbuf_add(&outbuf, in, ep - in);
482 + }
483 + }
484 + /* E.g.
485 + * ep : "=?iso-2022-jp?B?GyR...?= foo"
486 + * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
487 + */
488 + ep += 2;
489 +
490 + if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
491 + goto release_return;
492 +
493 + if (cp + 3 - it->buf > it->len)
494 + goto release_return;
495 + strbuf_add(&charset_q, ep, cp - ep);
496 +
497 + encoding = cp[1];
498 + if (!encoding || cp[2] != '?')
499 + goto release_return;
500 + ep = strstr(cp + 3, "?=");
501 + if (!ep)
502 + goto release_return;
503 + strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
504 + switch (tolower(encoding)) {
505 + default:
506 + goto release_return;
507 + case 'b':
508 + dec = decode_b_segment(&piecebuf);
509 + break;
510 + case 'q':
511 + dec = decode_q_segment(&piecebuf, 1);
512 + break;
513 + }
514 + if (convert_to_utf8(mi, dec, charset_q.buf))
515 + goto release_return;
516 +
517 + strbuf_addbuf(&outbuf, dec);
518 + strbuf_release(dec);
519 + free(dec);
520 + in = ep + 2;
521 + }
522 + strbuf_addstr(&outbuf, in);
523 + strbuf_reset(it);
524 + strbuf_addbuf(it, &outbuf);
525 + found_error = 0;
526 +release_return:
527 + strbuf_release(&outbuf);
528 + strbuf_release(&charset_q);
529 + strbuf_release(&piecebuf);
530 +
531 + if (found_error)
532 + mi->input_error = -1;
533 +}
534 +
535 +static int check_header(struct mailinfo *mi,
536 + const struct strbuf *line,
537 + struct strbuf *hdr_data[], int overwrite)
538 +{
539 + int i, ret = 0, len;
540 + struct strbuf sb = STRBUF_INIT;
541 +
542 + /* search for the interesting parts */
543 + for (i = 0; header[i]; i++) {
544 + int len = strlen(header[i]);
545 + if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
546 + /* Unwrap inline B and Q encoding, and optionally
547 + * normalize the meta information to utf8.
548 + */
549 + strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
550 + decode_header(mi, &sb);
551 + handle_header(&hdr_data[i], &sb);
552 + ret = 1;
553 + goto check_header_out;
554 + }
555 + }
556 +
557 + /* Content stuff */
558 + if (cmp_header(line, "Content-Type")) {
559 + len = strlen("Content-Type: ");
560 + strbuf_add(&sb, line->buf + len, line->len - len);
561 + decode_header(mi, &sb);
562 + strbuf_insert(&sb, 0, "Content-Type: ", len);
563 + handle_content_type(mi, &sb);
564 + ret = 1;
565 + goto check_header_out;
566 + }
567 + if (cmp_header(line, "Content-Transfer-Encoding")) {
568 + len = strlen("Content-Transfer-Encoding: ");
569 + strbuf_add(&sb, line->buf + len, line->len - len);
570 + decode_header(mi, &sb);
571 + handle_content_transfer_encoding(mi, &sb);
572 + ret = 1;
573 + goto check_header_out;
574 + }
575 + if (cmp_header(line, "Message-Id")) {
576 + len = strlen("Message-Id: ");
577 + strbuf_add(&sb, line->buf + len, line->len - len);
578 + decode_header(mi, &sb);
579 + if (mi->add_message_id)
580 + mi->message_id = strbuf_detach(&sb, NULL);
581 + ret = 1;
582 + goto check_header_out;
583 + }
584 +
585 +check_header_out:
586 + strbuf_release(&sb);
587 + return ret;
588 +}
589 +
590 +/*
591 + * Returns 1 if the given line or any line beginning with the given line is an
592 + * in-body header (that is, check_header will succeed when passed
593 + * mi->s_hdr_data).
594 + */
595 +static int is_inbody_header(const struct mailinfo *mi,
596 + const struct strbuf *line)
597 +{
598 + int i;
599 + for (i = 0; header[i]; i++)
600 + if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
601 + return 1;
602 + return 0;
603 +}
604 +
605 +static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
606 +{
607 + struct strbuf *ret;
608 +
609 + switch (mi->transfer_encoding) {
610 + case TE_QP:
611 + ret = decode_q_segment(line, 0);
612 + break;
613 + case TE_BASE64:
614 + ret = decode_b_segment(line);
615 + break;
616 + case TE_DONTCARE:
617 + default:
618 + return;
619 + }
620 + strbuf_reset(line);
621 + strbuf_addbuf(line, ret);
622 + strbuf_release(ret);
623 + free(ret);
624 +}
625 +
626 +static inline int patchbreak(const struct strbuf *line)
627 +{
628 + size_t i;
629 +
630 + /* Beginning of a "diff -" header? */
631 + if (starts_with(line->buf, "diff -"))
632 + return 1;
633 +
634 + /* CVS "Index: " line? */
635 + if (starts_with(line->buf, "Index: "))
636 + return 1;
637 +
638 + /*
639 + * "--- <filename>" starts patches without headers
640 + * "---<sp>*" is a manual separator
641 + */
642 + if (line->len < 4)
643 + return 0;
644 +
645 + if (starts_with(line->buf, "---")) {
646 + /* space followed by a filename? */
647 + if (line->buf[3] == ' ' && !isspace(line->buf[4]))
648 + return 1;
649 + /* Just whitespace? */
650 + for (i = 3; i < line->len; i++) {
651 + unsigned char c = line->buf[i];
652 + if (c == '\n')
653 + return 1;
654 + if (!isspace(c))
655 + break;
656 + }
657 + return 0;
658 + }
659 + return 0;
660 +}
661 +
662 +static int is_scissors_line(const char *line)
663 +{
664 + const char *c;
665 + int scissors = 0, gap = 0;
666 + const char *first_nonblank = NULL, *last_nonblank = NULL;
667 + int visible, perforation = 0, in_perforation = 0;
668 +
669 + for (c = line; *c; c++) {
670 + if (isspace(*c)) {
671 + if (in_perforation) {
672 + perforation++;
673 + gap++;
674 + }
675 + continue;
676 + }
677 + last_nonblank = c;
678 + if (first_nonblank == NULL)
679 + first_nonblank = c;
680 + if (*c == '-') {
681 + in_perforation = 1;
682 + perforation++;
683 + continue;
684 + }
685 + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
686 + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
687 + in_perforation = 1;
688 + perforation += 2;
689 + scissors += 2;
690 + c++;
691 + continue;
692 + }
693 + in_perforation = 0;
694 + }
695 +
696 + /*
697 + * The mark must be at least 8 bytes long (e.g. "-- >8 --").
698 + * Even though there can be arbitrary cruft on the same line
699 + * (e.g. "cut here"), in order to avoid misidentification, the
700 + * perforation must occupy more than a third of the visible
701 + * width of the line, and dashes and scissors must occupy more
702 + * than half of the perforation.
703 + */
704 +
705 + if (first_nonblank && last_nonblank)
706 + visible = last_nonblank - first_nonblank + 1;
707 + else
708 + visible = 0;
709 + return (scissors && 8 <= visible &&
710 + visible < perforation * 3 &&
711 + gap * 2 < perforation);
712 +}
713 +
714 +static void flush_inbody_header_accum(struct mailinfo *mi)
715 +{
716 + if (!mi->inbody_header_accum.len)
717 + return;
718 + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
719 + BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
720 + strbuf_reset(&mi->inbody_header_accum);
721 +}
722 +
723 +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
724 +{
725 + if (mi->inbody_header_accum.len &&
726 + (line->buf[0] == ' ' || line->buf[0] == '\t')) {
727 + if (mi->use_scissors && is_scissors_line(line->buf)) {
728 + /*
729 + * This is a scissors line; do not consider this line
730 + * as a header continuation line.
731 + */
732 + flush_inbody_header_accum(mi);
733 + return 0;
734 + }
735 + strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
736 + strbuf_addbuf(&mi->inbody_header_accum, line);
737 + return 1;
738 + }
739 +
740 + flush_inbody_header_accum(mi);
741 +
742 + if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
743 + return is_format_patch_separator(line->buf + 1, line->len - 1);
744 + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
745 + int i;
746 + for (i = 0; header[i]; i++)
747 + if (!strcmp("Subject", header[i])) {
748 + handle_header(&mi->s_hdr_data[i], line);
749 + return 1;
750 + }
751 + return 0;
752 + }
753 + if (is_inbody_header(mi, line)) {
754 + strbuf_addbuf(&mi->inbody_header_accum, line);
755 + return 1;
756 + }
757 + return 0;
758 +}
759 +
760 +static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
761 +{
762 + assert(!mi->filter_stage);
763 +
764 + if (mi->header_stage) {
765 + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) {
766 + if (mi->inbody_header_accum.len) {
767 + flush_inbody_header_accum(mi);
768 + mi->header_stage = 0;
769 + }
770 + return 0;
771 + }
772 + }
773 +
774 + if (mi->use_inbody_headers && mi->header_stage) {
775 + mi->header_stage = check_inbody_header(mi, line);
776 + if (mi->header_stage)
777 + return 0;
778 + } else
779 + /* Only trim the first (blank) line of the commit message
780 + * when ignoring in-body headers.
781 + */
782 + mi->header_stage = 0;
783 +
784 + /* normalize the log message to UTF-8. */
785 + if (convert_to_utf8(mi, line, mi->charset.buf))
786 + return 0; /* mi->input_error already set */
787 +
788 + if (mi->use_scissors && is_scissors_line(line->buf)) {
789 + int i;
790 +
791 + strbuf_setlen(&mi->log_message, 0);
792 + mi->header_stage = 1;
793 +
794 + /*
795 + * We may have already read "secondary headers"; purge
796 + * them to give ourselves a clean restart.
797 + */
798 + for (i = 0; header[i]; i++) {
799 + if (mi->s_hdr_data[i])
800 + strbuf_release(mi->s_hdr_data[i]);
801 + mi->s_hdr_data[i] = NULL;
802 + }
803 + return 0;
804 + }
805 +
806 + if (patchbreak(line)) {
807 + if (mi->message_id)
808 + strbuf_addf(&mi->log_message,
809 + "Message-Id: %s\n", mi->message_id);
810 + return 1;
811 + }
812 +
813 + strbuf_addbuf(&mi->log_message, line);
814 + return 0;
815 +}
816 +
817 +static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
818 +{
819 + fwrite(line->buf, 1, line->len, mi->patchfile);
820 + mi->patch_lines++;
821 +}
822 +
823 +static void handle_filter(struct mailinfo *mi, struct strbuf *line)
824 +{
825 + switch (mi->filter_stage) {
826 + case 0:
827 + if (!handle_commit_msg(mi, line))
828 + break;
829 + mi->filter_stage++;
830 + /* fallthrough */
831 + case 1:
832 + handle_patch(mi, line);
833 + break;
834 + }
835 +}
836 +
837 +static int is_rfc2822_header(const struct strbuf *line)
838 +{
839 + /*
840 + * The section that defines the loosest possible
841 + * field name is "3.6.8 Optional fields".
842 + *
843 + * optional-field = field-name ":" unstructured CRLF
844 + * field-name = 1*ftext
845 + * ftext = %d33-57 / %59-126
846 + */
847 + int ch;
848 + char *cp = line->buf;
849 +
850 + /* Count mbox From headers as headers */
851 + if (starts_with(cp, "From ") || starts_with(cp, ">From "))
852 + return 1;
853 +
854 + while ((ch = *cp++)) {
855 + if (ch == ':')
856 + return 1;
857 + if ((33 <= ch && ch <= 57) ||
858 + (59 <= ch && ch <= 126))
859 + continue;
860 + break;
861 + }
862 + return 0;
863 +}
864 +
865 +static int read_one_header_line(struct strbuf *line, FILE *in)
866 +{
867 + struct strbuf continuation = STRBUF_INIT;
868 +
869 + /* Get the first part of the line. */
870 + if (strbuf_getline_lf(line, in))
871 + return 0;
872 +
873 + /*
874 + * Is it an empty line or not a valid rfc2822 header?
875 + * If so, stop here, and return false ("not a header")
876 + */
877 + strbuf_rtrim(line);
878 + if (!line->len || !is_rfc2822_header(line)) {
879 + /* Re-add the newline */
880 + strbuf_addch(line, '\n');
881 + return 0;
882 + }
883 +
884 + /*
885 + * Now we need to eat all the continuation lines..
886 + * Yuck, 2822 header "folding"
887 + */
888 + for (;;) {
889 + int peek;
890 +
891 + peek = fgetc(in);
892 + if (peek == EOF)
893 + break;
894 + ungetc(peek, in);
895 + if (peek != ' ' && peek != '\t')
896 + break;
897 + if (strbuf_getline_lf(&continuation, in))
898 + break;
899 + continuation.buf[0] = ' ';
900 + strbuf_rtrim(&continuation);
901 + strbuf_addbuf(line, &continuation);
902 + }
903 + strbuf_release(&continuation);
904 +
905 + return 1;
906 +}
907 +
908 +static int find_boundary(struct mailinfo *mi, struct strbuf *line)
909 +{
910 + while (!strbuf_getline_lf(line, mi->input)) {
911 + if (*(mi->content_top) && is_multipart_boundary(mi, line))
912 + return 1;
913 + }
914 + return 0;
915 +}
916 +
917 +static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
918 +{
919 + struct strbuf newline = STRBUF_INIT;
920 +
921 + strbuf_addch(&newline, '\n');
922 +again:
923 + if (line->len >= (*(mi->content_top))->len + 2 &&
924 + !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
925 + /* we hit an end boundary */
926 + /* pop the current boundary off the stack */
927 + strbuf_release(*(mi->content_top));
928 + FREE_AND_NULL(*(mi->content_top));
929 +
930 + /* technically won't happen as is_multipart_boundary()
931 + will fail first. But just in case..
932 + */
933 + if (--mi->content_top < mi->content) {
934 + error("Detected mismatched boundaries, can't recover");
935 + mi->input_error = -1;
936 + mi->content_top = mi->content;
937 + strbuf_release(&newline);
938 + return 0;
939 + }
940 + handle_filter(mi, &newline);
941 + strbuf_release(&newline);
942 + if (mi->input_error)
943 + return 0;
944 +
945 + /* skip to the next boundary */
946 + if (!find_boundary(mi, line))
947 + return 0;
948 + goto again;
949 + }
950 +
951 + /* set some defaults */
952 + mi->transfer_encoding = TE_DONTCARE;
953 + strbuf_reset(&mi->charset);
954 +
955 + /* slurp in this section's info */
956 + while (read_one_header_line(line, mi->input))
957 + check_header(mi, line, mi->p_hdr_data, 0);
958 +
959 + strbuf_release(&newline);
960 + /* replenish line */
961 + if (strbuf_getline_lf(line, mi->input))
962 + return 0;
963 + strbuf_addch(line, '\n');
964 + return 1;
965 +}
966 +
967 +static void handle_body(struct mailinfo *mi, struct strbuf *line)
968 +{
969 + struct strbuf prev = STRBUF_INIT;
970 +
971 + /* Skip up to the first boundary */
972 + if (*(mi->content_top)) {
973 + if (!find_boundary(mi, line))
974 + goto handle_body_out;
975 + }
976 +
977 + do {
978 + /* process any boundary lines */
979 + if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
980 + /* flush any leftover */
981 + if (prev.len) {
982 + handle_filter(mi, &prev);
983 + strbuf_reset(&prev);
984 + }
985 + if (!handle_boundary(mi, line))
986 + goto handle_body_out;
987 + }
988 +
989 + /* Unwrap transfer encoding */
990 + decode_transfer_encoding(mi, line);
991 +
992 + switch (mi->transfer_encoding) {
993 + case TE_BASE64:
994 + case TE_QP:
995 + {
996 + struct strbuf **lines, **it, *sb;
997 +
998 + /* Prepend any previous partial lines */
999 + strbuf_insert(line, 0, prev.buf, prev.len);
1000 + strbuf_reset(&prev);
1001 +
1002 + /*
1003 + * This is a decoded line that may contain
1004 + * multiple new lines. Pass only one chunk
1005 + * at a time to handle_filter()
1006 + */
1007 + lines = strbuf_split(line, '\n');
1008 + for (it = lines; (sb = *it); it++) {
1009 + if (*(it + 1) == NULL) /* The last line */
1010 + if (sb->buf[sb->len - 1] != '\n') {
1011 + /* Partial line, save it for later. */
1012 + strbuf_addbuf(&prev, sb);
1013 + break;
1014 + }
1015 + handle_filter(mi, sb);
1016 + }
1017 + /*
1018 + * The partial chunk is saved in "prev" and will be
1019 + * appended by the next iteration of read_line_with_nul().
1020 + */
1021 + strbuf_list_free(lines);
1022 + break;
1023 + }
1024 + default:
1025 + handle_filter(mi, line);
1026 + }
1027 +
1028 + if (mi->input_error)
1029 + break;
1030 + } while (!strbuf_getwholeline(line, mi->input, '\n'));
1031 +
1032 + flush_inbody_header_accum(mi);
1033 +
1034 +handle_body_out:
1035 + strbuf_release(&prev);
1036 +}
1037 +
1038 +static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
1039 +{
1040 + const char *sp = data->buf;
1041 + while (1) {
1042 + char *ep = strchr(sp, '\n');
1043 + int len;
1044 + if (!ep)
1045 + len = strlen(sp);
1046 + else
1047 + len = ep - sp;
1048 + fprintf(fout, "%s: %.*s\n", hdr, len, sp);
1049 + if (!ep)
1050 + break;
1051 + sp = ep + 1;
1052 + }
1053 +}
1054 +
1055 +static void handle_info(struct mailinfo *mi)
1056 +{
1057 + struct strbuf *hdr;
1058 + int i;
1059 +
1060 + for (i = 0; header[i]; i++) {
1061 + /* only print inbody headers if we output a patch file */
1062 + if (mi->patch_lines && mi->s_hdr_data[i])
1063 + hdr = mi->s_hdr_data[i];
1064 + else if (mi->p_hdr_data[i])
1065 + hdr = mi->p_hdr_data[i];
1066 + else
1067 + continue;
1068 +
1069 + if (!strcmp(header[i], "Subject")) {
1070 + if (!mi->keep_subject) {
1071 + cleanup_subject(mi, hdr);
1072 + cleanup_space(hdr);
1073 + }
1074 + output_header_lines(mi->output, "Subject", hdr);
1075 + } else if (!strcmp(header[i], "From")) {
1076 + cleanup_space(hdr);
1077 + handle_from(mi, hdr);
1078 + fprintf(mi->output, "Author: %s\n", mi->name.buf);
1079 + fprintf(mi->output, "Email: %s\n", mi->email.buf);
1080 + } else {
1081 + cleanup_space(hdr);
1082 + fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
1083 + }
1084 + }
1085 + fprintf(mi->output, "\n");
1086 +}
1087 +
1088 +int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
1089 +{
1090 + FILE *cmitmsg;
1091 + int peek;
1092 + struct strbuf line = STRBUF_INIT;
1093 +
1094 + cmitmsg = fopen(msg, "w");
1095 + if (!cmitmsg) {
1096 + perror(msg);
1097 + return -1;
1098 + }
1099 + mi->patchfile = fopen(patch, "w");
1100 + if (!mi->patchfile) {
1101 + perror(patch);
1102 + fclose(cmitmsg);
1103 + return -1;
1104 + }
1105 +
1106 + mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
1107 + mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
1108 +
1109 + do {
1110 + peek = fgetc(mi->input);
1111 + if (peek == EOF) {
1112 + fclose(cmitmsg);
1113 + return error("empty patch: '%s'", patch);
1114 + }
1115 + } while (isspace(peek));
1116 + ungetc(peek, mi->input);
1117 +
1118 + /* process the email header */
1119 + while (read_one_header_line(&line, mi->input))
1120 + check_header(mi, &line, mi->p_hdr_data, 1);
1121 +
1122 + handle_body(mi, &line);
1123 + fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
1124 + fclose(cmitmsg);
1125 + fclose(mi->patchfile);
1126 +
1127 + handle_info(mi);
1128 + strbuf_release(&line);
1129 + return mi->input_error;
1130 +}
1131 +
1132 +static int git_mailinfo_config(const char *var, const char *value, void *mi_)
1133 +{
1134 + struct mailinfo *mi = mi_;
1135 +
1136 + if (!starts_with(var, "mailinfo."))
1137 + return git_default_config(var, value, NULL);
1138 + if (!strcmp(var, "mailinfo.scissors")) {
1139 + mi->use_scissors = git_config_bool(var, value);
1140 + return 0;
1141 + }
1142 + /* perhaps others here */
1143 + return 0;
1144 +}
1145 +
1146 +void setup_mailinfo(struct mailinfo *mi)
1147 +{
1148 + memset(mi, 0, sizeof(*mi));
1149 + strbuf_init(&mi->name, 0);
1150 + strbuf_init(&mi->email, 0);
1151 + strbuf_init(&mi->charset, 0);
1152 + strbuf_init(&mi->log_message, 0);
1153 + strbuf_init(&mi->inbody_header_accum, 0);
1154 + mi->header_stage = 1;
1155 + mi->use_inbody_headers = 1;
1156 + mi->content_top = mi->content;
1157 + git_config(git_mailinfo_config, mi);
1158 +}
1159 +
1160 +void clear_mailinfo(struct mailinfo *mi)
1161 +{
1162 + int i;
1163 +
1164 + strbuf_release(&mi->name);
1165 + strbuf_release(&mi->email);
1166 + strbuf_release(&mi->charset);
1167 + strbuf_release(&mi->inbody_header_accum);
1168 + free(mi->message_id);
1169 +
1170 + if (mi->p_hdr_data)
1171 + for (i = 0; mi->p_hdr_data[i]; i++)
1172 + strbuf_release(mi->p_hdr_data[i]);
1173 + free(mi->p_hdr_data);
1174 + if (mi->s_hdr_data)
1175 + for (i = 0; mi->s_hdr_data[i]; i++)
1176 + strbuf_release(mi->s_hdr_data[i]);
1177 + free(mi->s_hdr_data);
1178 +
1179 + while (mi->content < mi->content_top) {
1180 + free(*(mi->content_top));
1181 + mi->content_top--;
1182 + }
1183 +
1184 + strbuf_release(&mi->log_message);
1185 +}
t/t4256/1/patch new
+129
@@ -0,0 +1,129 @@
1 +From: A <author@example.com>
2 +Subject: [PATCH] mailinfo: support format=flowed
3 +Message-ID: <aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@example.com>
4 +Date: Sat, 25 Aug 2018 22:04:50 +0200
5 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101
6 + Thunderbird/60.0
7 +MIME-Version: 1.0
8 +Content-Type: text/plain; charset=utf-8; format=flowed
9 +Content-Language: en-US
10 +Content-Transfer-Encoding: 7bit
11 +
12 +---
13 + mailinfo.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
14 + 1 file changed, 62 insertions(+), 2 deletions(-)
15 +
16 +diff --git a/mailinfo.c b/mailinfo.c
17 +index 3281a37d51..b395adbdf2 100644
18 +--- a/mailinfo.c
19 ++++ b/mailinfo.c
20 +@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char
21 +*name, struct strbuf *attr)
22 + return 1;
23 + }
24 +
25 ++static int has_attr_value(const char *line, const char *name, const
26 +char *value)
27 ++{
28 ++ struct strbuf sb = STRBUF_INIT;
29 ++ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
30 ++ strbuf_release(&sb);
31 ++ return rc;
32 ++}
33 ++
34 + static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
35 + {
36 + struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
37 + strbuf_init(boundary, line->len);
38 +
39 ++ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
40 ++ mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
41 ++
42 + if (slurp_attr(line->buf, "boundary=", boundary)) {
43 + strbuf_insert(boundary, 0, "--", 2);
44 + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
45 +@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi,
46 +struct strbuf *line)
47 + return 1;
48 + }
49 +
50 ++static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
51 ++ struct strbuf *prev)
52 ++{
53 ++ size_t len = line->len;
54 ++ const char *rest;
55 ++
56 ++ if (!mi->format_flowed) {
57 ++ handle_filter(mi, line);
58 ++ return;
59 ++ }
60 ++
61 ++ if (line->buf[len - 1] == '\n') {
62 ++ len--;
63 ++ if (len && line->buf[len - 1] == '\r')
64 ++ len--;
65 ++ }
66 ++
67 ++ /* Keep signature separator as-is. */
68 ++ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
69 ++ if (prev->len) {
70 ++ handle_filter(mi, prev);
71 ++ strbuf_reset(prev);
72 ++ }
73 ++ handle_filter(mi, line);
74 ++ return;
75 ++ }
76 ++
77 ++ /* Unstuff space-stuffed line. */
78 ++ if (len && line->buf[0] == ' ') {
79 ++ strbuf_remove(line, 0, 1);
80 ++ len--;
81 ++ }
82 ++
83 ++ /* Save flowed line for later, but without the soft line break. */
84 ++ if (len && line->buf[len - 1] == ' ') {
85 ++ strbuf_add(prev, line->buf, len - !!mi->delsp);
86 ++ return;
87 ++ }
88 ++
89 ++ /* Prepend any previous partial lines */
90 ++ strbuf_insert(line, 0, prev->buf, prev->len);
91 ++ strbuf_reset(prev);
92 ++
93 ++ handle_filter(mi, line);
94 ++}
95 ++
96 + static void handle_body(struct mailinfo *mi, struct strbuf *line)
97 + {
98 + struct strbuf prev = STRBUF_INIT;
99 +@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi,
100 +struct strbuf *line)
101 + strbuf_addbuf(&prev, sb);
102 + break;
103 + }
104 +- handle_filter(mi, sb);
105 ++ handle_filter_flowed(mi, sb, &prev);
106 + }
107 + /*
108 + * The partial chunk is saved in "prev" and will be
109 +@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi,
110 +struct strbuf *line)
111 + break;
112 + }
113 + default:
114 +- handle_filter(mi, line);
115 ++ handle_filter_flowed(mi, line, &prev);
116 + }
117 +
118 + if (mi->input_error)
119 + break;
120 + } while (!strbuf_getwholeline(line, mi->input, '\n'));
121 +
122 ++ if (prev.len)
123 ++ handle_filter(mi, &prev);
124 ++
125 + flush_inbody_header_accum(mi);
126 +
127 + handle_body_out:
128 +--
129 +2.18.0