mailinfo: libify

Move the bulk of the code from builtin/mailinfo.c to mailinfo.c so that new callers can start calling mailinfo() directly. Note that a few calls to exit() and die() need to be cleaned up for the API to be truly useful, which will come in later steps. Signed-off-by: Junio C Hamano <gitster@pobox.com>

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