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 */