{"thread":{"id":"49213","subject":"[PATCH] mailinfo: support format=flowed","startedAt":"2018-08-25T21:51:01Z","lastAt":"2018-09-04T23:25:48Z","messageCount":4,"participants":["René Scharfe","Junio C Hamano","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"356525","messageId":"e2f2ca18-849c-0ef4-98a5-9a1379bfcec5@web.de","threadId":"49213","inReplyTo":null,"subject":"[PATCH] mailinfo: support format=flowed","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2018-08-25T21:50:32Z","receivedAt":"2018-08-25T21:51:01Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Add best-effort support for patches sent using format=flowed (RFC 3676).\nRemove leading spaces (\"unstuff\"), remove soft line breaks (indicated\nby space + newline), but leave the signature separator (dash dash space\nnewline) alone.\n\nWarn in git am when encountering a format=flowed patch, because any\ntrailing spaces would most probably be lost, as the sending MUA is\nencouraged to remove them when preparing the email.\n\nProvide a test patch formatted by Mozilla Thunderbird 60 using its\ndefault configuration.  It reuses the contents of the file mailinfo.c\nbefore and after this patch.\n\nSigned-off-by: Rene Scharfe <l.s.r@web.de>\n---\nA test for DelSp=yes would be nice (in a separate patch; this one is\nquite big already), but I don't know how to create one.\n\n builtin/am.c                |    4 +\n mailinfo.c                  |   64 +-\n mailinfo.h                  |    2 +\n t/t4256-am-format-flowed.sh |   19 +\n t/t4256/1/mailinfo.c        | 1245 +++++++++++++++++++++++++++++++++++\n t/t4256/1/mailinfo.c.orig   | 1185 +++++++++++++++++++++++++++++++++\n t/t4256/1/patch             |  129 ++++\n 7 files changed, 2646 insertions(+), 2 deletions(-)\n create mode 100755 t/t4256-am-format-flowed.sh\n create mode 100644 t/t4256/1/mailinfo.c\n create mode 100644 t/t4256/1/mailinfo.c.orig\n create mode 100644 t/t4256/1/patch\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 9f7ecf6ecb..2a7341cf48 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1244,6 +1244,10 @@ static int parse_mail(struct am_state *state, const char *mail)\n \tfclose(mi.input);\n \tfclose(mi.output);\n \n+\tif (mi.format_flowed)\n+\t\twarning(_(\"Patch sent with format=flowed; \"\n+\t\t\t  \"space at the end of lines might be lost.\"));\n+\n \t/* Extract message and author information */\n \tfp = xfopen(am_path(state, \"info\"), \"r\");\n \twhile (!strbuf_getline_lf(&sb, fp)) {\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 3281a37d51..b395adbdf2 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char *name, struct strbuf *attr)\n \treturn 1;\n }\n \n+static int has_attr_value(const char *line, const char *name, const char *value)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);\n+\tstrbuf_release(&sb);\n+\treturn rc;\n+}\n+\n static void handle_content_type(struct mailinfo *mi, struct strbuf *line)\n {\n \tstruct strbuf *boundary = xmalloc(sizeof(struct strbuf));\n \tstrbuf_init(boundary, line->len);\n \n+\tmi->format_flowed = has_attr_value(line->buf, \"format=\", \"flowed\");\n+\tmi->delsp = has_attr_value(line->buf, \"delsp=\", \"yes\");\n+\n \tif (slurp_attr(line->buf, \"boundary=\", boundary)) {\n \t\tstrbuf_insert(boundary, 0, \"--\", 2);\n \t\tif (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {\n@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi, struct strbuf *line)\n \treturn 1;\n }\n \n+static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n+\t\t\t\t struct strbuf *prev)\n+{\n+\tsize_t len = line->len;\n+\tconst char *rest;\n+\n+\tif (!mi->format_flowed) {\n+\t\thandle_filter(mi, line);\n+\t\treturn;\n+\t}\n+\n+\tif (line->buf[len - 1] == '\\n') {\n+\t\tlen--;\n+\t\tif (len && line->buf[len - 1] == '\\r')\n+\t\t\tlen--;\n+\t}\n+\n+\t/* Keep signature separator as-is. */\n+\tif (skip_prefix(line->buf, \"-- \", &rest) && rest - line->buf == len) {\n+\t\tif (prev->len) {\n+\t\t\thandle_filter(mi, prev);\n+\t\t\tstrbuf_reset(prev);\n+\t\t}\n+\t\thandle_filter(mi, line);\n+\t\treturn;\n+\t}\n+\n+\t/* Unstuff space-stuffed line. */\n+\tif (len && line->buf[0] == ' ') {\n+\t\tstrbuf_remove(line, 0, 1);\n+\t\tlen--;\n+\t}\n+\n+\t/* Save flowed line for later, but without the soft line break. */\n+\tif (len && line->buf[len - 1] == ' ') {\n+\t\tstrbuf_add(prev, line->buf, len - !!mi->delsp);\n+\t\treturn;\n+\t}\n+\n+\t/* Prepend any previous partial lines */\n+\tstrbuf_insert(line, 0, prev->buf, prev->len);\n+\tstrbuf_reset(prev);\n+\n+\thandle_filter(mi, line);\n+}\n+\n static void handle_body(struct mailinfo *mi, struct strbuf *line)\n {\n \tstruct strbuf prev = STRBUF_INIT;\n@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \t\t\t\t\t\tstrbuf_addbuf(&prev, sb);\n \t\t\t\t\t\tbreak;\n \t\t\t\t\t}\n-\t\t\t\thandle_filter(mi, sb);\n+\t\t\t\thandle_filter_flowed(mi, sb, &prev);\n \t\t\t}\n \t\t\t/*\n \t\t\t * The partial chunk is saved in \"prev\" and will be\n@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \t\t\tbreak;\n \t\t}\n \t\tdefault:\n-\t\t\thandle_filter(mi, line);\n+\t\t\thandle_filter_flowed(mi, line, &prev);\n \t\t}\n \n \t\tif (mi->input_error)\n \t\t\tbreak;\n \t} while (!strbuf_getwholeline(line, mi->input, '\\n'));\n \n+\tif (prev.len)\n+\t\thandle_filter(mi, &prev);\n+\n \tflush_inbody_header_accum(mi);\n \n handle_body_out:\ndiff --git a/mailinfo.h b/mailinfo.h\nindex 766c03dd1d..6830e1e625 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -22,6 +22,8 @@ struct mailinfo {\n \tstruct strbuf *content[MAX_BOUNDARIES];\n \tstruct strbuf **content_top;\n \tstruct strbuf charset;\n+\tunsigned int format_flowed:1;\n+\tunsigned int delsp:1;\n \tchar *message_id;\n \tenum  {\n \t\tTE_DONTCARE, TE_QP, TE_BASE64\ndiff --git a/t/t4256-am-format-flowed.sh b/t/t4256-am-format-flowed.sh\nnew file mode 100755\nindex 0000000000..6340310e9a\n--- /dev/null\n+++ b/t/t4256-am-format-flowed.sh\n@@ -0,0 +1,19 @@\n+#!/bin/sh\n+\n+test_description='test format=flowed support of git am'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tcp \"$TEST_DIRECTORY/t4256/1/mailinfo.c.orig\" mailinfo.c &&\n+\tgit add mailinfo.c &&\n+\tgit commit -m initial\n+'\n+\n+test_expect_success 'am with format=flowed' '\n+\tgit am <\"$TEST_DIRECTORY/t4256/1/patch\" >stdout 2>stderr &&\n+\ttest_i18ngrep \"warning: Patch sent with format=flowed\" stderr &&\n+\ttest_cmp \"$TEST_DIRECTORY/t4256/1/mailinfo.c\" mailinfo.c\n+'\n+\n+test_done\ndiff --git a/t/t4256/1/mailinfo.c b/t/t4256/1/mailinfo.c\nnew file mode 100644\nindex 0000000000..b395adbdf2\n--- /dev/null\n+++ b/t/t4256/1/mailinfo.c\n@@ -0,0 +1,1245 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"utf8.h\"\n+#include \"strbuf.h\"\n+#include \"mailinfo.h\"\n+\n+static void cleanup_space(struct strbuf *sb)\n+{\n+\tsize_t pos, cnt;\n+\tfor (pos = 0; pos < sb->len; pos++) {\n+\t\tif (isspace(sb->buf[pos])) {\n+\t\t\tsb->buf[pos] = ' ';\n+\t\t\tfor (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);\n+\t\t\tstrbuf_remove(sb, pos + 1, cnt);\n+\t\t}\n+\t}\n+}\n+\n+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)\n+{\n+\tstruct strbuf *src = name;\n+\tif (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||\n+\t\tstrchr(name->buf, '<') || strchr(name->buf, '>'))\n+\t\tsrc = email;\n+\telse if (name == out)\n+\t\treturn;\n+\tstrbuf_reset(out);\n+\tstrbuf_addbuf(out, src);\n+}\n+\n+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\t/* John Doe <johndoe> */\n+\n+\tchar *bra, *ket;\n+\t/* This is fallback, so do not bother if we already have an\n+\t * e-mail address.\n+\t */\n+\tif (mi->email.len)\n+\t\treturn;\n+\n+\tbra = strchr(line->buf, '<');\n+\tif (!bra)\n+\t\treturn;\n+\tket = strchr(bra, '>');\n+\tif (!ket)\n+\t\treturn;\n+\n+\tstrbuf_reset(&mi->email);\n+\tstrbuf_add(&mi->email, bra + 1, ket - bra - 1);\n+\n+\tstrbuf_reset(&mi->name);\n+\tstrbuf_add(&mi->name, line->buf, bra - line->buf);\n+\tstrbuf_trim(&mi->name);\n+\tget_sane_name(&mi->name, &mi->name, &mi->email);\n+}\n+\n+static const char *unquote_comment(struct strbuf *outbuf, const char *in)\n+{\n+\tint c;\n+\tint take_next_literally = 0;\n+\n+\tstrbuf_addch(outbuf, '(');\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (take_next_literally == 1) {\n+\t\t\ttake_next_literally = 0;\n+\t\t} else {\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\\\':\n+\t\t\t\ttake_next_literally = 1;\n+\t\t\t\tcontinue;\n+\t\t\tcase '(':\n+\t\t\t\tin = unquote_comment(outbuf, in);\n+\t\t\t\tcontinue;\n+\t\t\tcase ')':\n+\t\t\t\tstrbuf_addch(outbuf, ')');\n+\t\t\t\treturn in;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstrbuf_addch(outbuf, c);\n+\t}\n+\n+\treturn in;\n+}\n+\n+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)\n+{\n+\tint c;\n+\tint take_next_literally = 0;\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (take_next_literally == 1) {\n+\t\t\ttake_next_literally = 0;\n+\t\t} else {\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\\\':\n+\t\t\t\ttake_next_literally = 1;\n+\t\t\t\tcontinue;\n+\t\t\tcase '\"':\n+\t\t\t\treturn in;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstrbuf_addch(outbuf, c);\n+\t}\n+\n+\treturn in;\n+}\n+\n+static void unquote_quoted_pair(struct strbuf *line)\n+{\n+\tstruct strbuf outbuf;\n+\tconst char *in = line->buf;\n+\tint c;\n+\n+\tstrbuf_init(&outbuf, line->len);\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tswitch (c) {\n+\t\tcase '\"':\n+\t\t\tin = unquote_quoted_string(&outbuf, in);\n+\t\t\tcontinue;\n+\t\tcase '(':\n+\t\t\tin = unquote_comment(&outbuf, in);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_addch(&outbuf, c);\n+\t}\n+\n+\tstrbuf_swap(&outbuf, line);\n+\tstrbuf_release(&outbuf);\n+\n+}\n+\n+static void handle_from(struct mailinfo *mi, const struct strbuf *from)\n+{\n+\tchar *at;\n+\tsize_t el;\n+\tstruct strbuf f;\n+\n+\tstrbuf_init(&f, from->len);\n+\tstrbuf_addbuf(&f, from);\n+\n+\tunquote_quoted_pair(&f);\n+\n+\tat = strchr(f.buf, '@');\n+\tif (!at) {\n+\t\tparse_bogus_from(mi, from);\n+\t\tgoto out;\n+\t}\n+\n+\t/*\n+\t * If we already have one email, don't take any confusing lines\n+\t */\n+\tif (mi->email.len && strchr(at + 1, '@'))\n+\t\tgoto out;\n+\n+\t/* Pick up the string around '@', possibly delimited with <>\n+\t * pair; that is the email part.\n+\t */\n+\twhile (at > f.buf) {\n+\t\tchar c = at[-1];\n+\t\tif (isspace(c))\n+\t\t\tbreak;\n+\t\tif (c == '<') {\n+\t\t\tat[-1] = ' ';\n+\t\t\tbreak;\n+\t\t}\n+\t\tat--;\n+\t}\n+\tel = strcspn(at, \" \\n\\t\\r\\v\\f>\");\n+\tstrbuf_reset(&mi->email);\n+\tstrbuf_add(&mi->email, at, el);\n+\tstrbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));\n+\n+\t/* The remainder is name.  It could be\n+\t *\n+\t * - \"John Doe <john.doe@xz>\"\t\t\t(a), or\n+\t * - \"john.doe@xz (John Doe)\"\t\t\t(b), or\n+\t * - \"John (zzz) Doe <john.doe@xz> (Comment)\"\t(c)\n+\t *\n+\t * but we have removed the email part, so\n+\t *\n+\t * - remove extra spaces which could stay after email (case 'c'), and\n+\t * - trim from both ends, possibly removing the () pair at the end\n+\t *   (cases 'a' and 'b').\n+\t */\n+\tcleanup_space(&f);\n+\tstrbuf_trim(&f);\n+\tif (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {\n+\t\tstrbuf_remove(&f, 0, 1);\n+\t\tstrbuf_setlen(&f, f.len - 1);\n+\t}\n+\n+\tget_sane_name(&mi->name, &f, &mi->email);\n+out:\n+\tstrbuf_release(&f);\n+}\n+\n+static void handle_header(struct strbuf **out, const struct strbuf *line)\n+{\n+\tif (!*out) {\n+\t\t*out = xmalloc(sizeof(struct strbuf));\n+\t\tstrbuf_init(*out, line->len);\n+\t} else\n+\t\tstrbuf_reset(*out);\n+\n+\tstrbuf_addbuf(*out, line);\n+}\n+\n+/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt\n+ * to have enough heuristics to grok MIME encoded patches often found\n+ * on our mailing lists.  For example, we do not even treat header lines\n+ * case insensitively.\n+ */\n+\n+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)\n+{\n+\tconst char *ends, *ap = strcasestr(line, name);\n+\tsize_t sz;\n+\n+\tstrbuf_setlen(attr, 0);\n+\tif (!ap)\n+\t\treturn 0;\n+\tap += strlen(name);\n+\tif (*ap == '\"') {\n+\t\tap++;\n+\t\tends = \"\\\"\";\n+\t}\n+\telse\n+\t\tends = \"; \\t\";\n+\tsz = strcspn(ap, ends);\n+\tstrbuf_add(attr, ap, sz);\n+\treturn 1;\n+}\n+\n+static int has_attr_value(const char *line, const char *name, const char *value)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);\n+\tstrbuf_release(&sb);\n+\treturn rc;\n+}\n+\n+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf *boundary = xmalloc(sizeof(struct strbuf));\n+\tstrbuf_init(boundary, line->len);\n+\n+\tmi->format_flowed = has_attr_value(line->buf, \"format=\", \"flowed\");\n+\tmi->delsp = has_attr_value(line->buf, \"delsp=\", \"yes\");\n+\n+\tif (slurp_attr(line->buf, \"boundary=\", boundary)) {\n+\t\tstrbuf_insert(boundary, 0, \"--\", 2);\n+\t\tif (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {\n+\t\t\terror(\"Too many boundaries to handle\");\n+\t\t\tmi->input_error = -1;\n+\t\t\tmi->content_top = &mi->content[MAX_BOUNDARIES] - 1;\n+\t\t\treturn;\n+\t\t}\n+\t\t*(mi->content_top) = boundary;\n+\t\tboundary = NULL;\n+\t}\n+\tslurp_attr(line->buf, \"charset=\", &mi->charset);\n+\n+\tif (boundary) {\n+\t\tstrbuf_release(boundary);\n+\t\tfree(boundary);\n+\t}\n+}\n+\n+static void handle_content_transfer_encoding(struct mailinfo *mi,\n+\t\t\t\t\t     const struct strbuf *line)\n+{\n+\tif (strcasestr(line->buf, \"base64\"))\n+\t\tmi->transfer_encoding = TE_BASE64;\n+\telse if (strcasestr(line->buf, \"quoted-printable\"))\n+\t\tmi->transfer_encoding = TE_QP;\n+\telse\n+\t\tmi->transfer_encoding = TE_DONTCARE;\n+}\n+\n+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\tstruct strbuf *content_top = *(mi->content_top);\n+\n+\treturn ((content_top->len <= line->len) &&\n+\t\t!memcmp(line->buf, content_top->buf, content_top->len));\n+}\n+\n+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)\n+{\n+\tsize_t at = 0;\n+\n+\twhile (at < subject->len) {\n+\t\tchar *pos;\n+\t\tsize_t remove;\n+\n+\t\tswitch (subject->buf[at]) {\n+\t\tcase 'r': case 'R':\n+\t\t\tif (subject->len <= at + 3)\n+\t\t\t\tbreak;\n+\t\t\tif ((subject->buf[at + 1] == 'e' ||\n+\t\t\t     subject->buf[at + 1] == 'E') &&\n+\t\t\t    subject->buf[at + 2] == ':') {\n+\t\t\t\tstrbuf_remove(subject, at, 3);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tat++;\n+\t\t\tbreak;\n+\t\tcase ' ': case '\\t': case ':':\n+\t\t\tstrbuf_remove(subject, at, 1);\n+\t\t\tcontinue;\n+\t\tcase '[':\n+\t\t\tpos = strchr(subject->buf + at, ']');\n+\t\t\tif (!pos)\n+\t\t\t\tbreak;\n+\t\t\tremove = pos - subject->buf + at + 1;\n+\t\t\tif (!mi->keep_non_patch_brackets_in_subject ||\n+\t\t\t    (7 <= remove &&\n+\t\t\t     memmem(subject->buf + at, remove, \"PATCH\", 5)))\n+\t\t\t\tstrbuf_remove(subject, at, remove);\n+\t\t\telse {\n+\t\t\t\tat += remove;\n+\t\t\t\t/*\n+\t\t\t\t * If the input had a space after the ], keep\n+\t\t\t\t * it.  We don't bother with finding the end of\n+\t\t\t\t * the space, since we later normalize it\n+\t\t\t\t * anyway.\n+\t\t\t\t */\n+\t\t\t\tif (isspace(subject->buf[at]))\n+\t\t\t\t\tat += 1;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\tstrbuf_trim(subject);\n+}\n+\n+#define MAX_HDR_PARSED 10\n+static const char *header[MAX_HDR_PARSED] = {\n+\t\"From\",\"Subject\",\"Date\",\n+};\n+\n+static inline int cmp_header(const struct strbuf *line, const char *hdr)\n+{\n+\tint len = strlen(hdr);\n+\treturn !strncasecmp(line->buf, hdr, len) && line->len > len &&\n+\t\t\tline->buf[len] == ':' && isspace(line->buf[len + 1]);\n+}\n+\n+static int is_format_patch_separator(const char *line, int len)\n+{\n+\tstatic const char SAMPLE[] =\n+\t\t\"From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\\n\";\n+\tconst char *cp;\n+\n+\tif (len != strlen(SAMPLE))\n+\t\treturn 0;\n+\tif (!skip_prefix(line, \"From \", &cp))\n+\t\treturn 0;\n+\tif (strspn(cp, \"0123456789abcdef\") != 40)\n+\t\treturn 0;\n+\tcp += 40;\n+\treturn !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));\n+}\n+\n+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)\n+{\n+\tconst char *in = q_seg->buf;\n+\tint c;\n+\tstruct strbuf *out = xmalloc(sizeof(struct strbuf));\n+\tstrbuf_init(out, q_seg->len);\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (c == '=') {\n+\t\t\tint ch, d = *in;\n+\t\t\tif (d == '\\n' || !d)\n+\t\t\t\tbreak; /* drop trailing newline */\n+\t\t\tch = hex2chr(in);\n+\t\t\tif (ch >= 0) {\n+\t\t\t\tstrbuf_addch(out, ch);\n+\t\t\t\tin += 2;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* garbage -- fall through */\n+\t\t}\n+\t\tif (rfc2047 && c == '_') /* rfc2047 4.2 (2) */\n+\t\t\tc = 0x20;\n+\t\tstrbuf_addch(out, c);\n+\t}\n+\treturn out;\n+}\n+\n+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)\n+{\n+\t/* Decode in..ep, possibly in-place to ot */\n+\tint c, pos = 0, acc = 0;\n+\tconst char *in = b_seg->buf;\n+\tstruct strbuf *out = xmalloc(sizeof(struct strbuf));\n+\tstrbuf_init(out, b_seg->len);\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (c == '+')\n+\t\t\tc = 62;\n+\t\telse if (c == '/')\n+\t\t\tc = 63;\n+\t\telse if ('A' <= c && c <= 'Z')\n+\t\t\tc -= 'A';\n+\t\telse if ('a' <= c && c <= 'z')\n+\t\t\tc -= 'a' - 26;\n+\t\telse if ('0' <= c && c <= '9')\n+\t\t\tc -= '0' - 52;\n+\t\telse\n+\t\t\tcontinue; /* garbage */\n+\t\tswitch (pos++) {\n+\t\tcase 0:\n+\t\t\tacc = (c << 2);\n+\t\t\tbreak;\n+\t\tcase 1:\n+\t\t\tstrbuf_addch(out, (acc | (c >> 4)));\n+\t\t\tacc = (c & 15) << 4;\n+\t\t\tbreak;\n+\t\tcase 2:\n+\t\t\tstrbuf_addch(out, (acc | (c >> 2)));\n+\t\t\tacc = (c & 3) << 6;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\tstrbuf_addch(out, (acc | c));\n+\t\t\tacc = pos = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn out;\n+}\n+\n+static int convert_to_utf8(struct mailinfo *mi,\n+\t\t\t   struct strbuf *line, const char *charset)\n+{\n+\tchar *out;\n+\n+\tif (!mi->metainfo_charset || !charset || !*charset)\n+\t\treturn 0;\n+\n+\tif (same_encoding(mi->metainfo_charset, charset))\n+\t\treturn 0;\n+\tout = reencode_string(line->buf, mi->metainfo_charset, charset);\n+\tif (!out) {\n+\t\tmi->input_error = -1;\n+\t\treturn error(\"cannot convert from %s to %s\",\n+\t\t\t     charset, mi->metainfo_charset);\n+\t}\n+\tstrbuf_attach(line, out, strlen(out), strlen(out));\n+\treturn 0;\n+}\n+\n+static void decode_header(struct mailinfo *mi, struct strbuf *it)\n+{\n+\tchar *in, *ep, *cp;\n+\tstruct strbuf outbuf = STRBUF_INIT, *dec;\n+\tstruct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;\n+\tint found_error = 1; /* pessimism */\n+\n+\tin = it->buf;\n+\twhile (in - it->buf <= it->len && (ep = strstr(in, \"=?\")) != NULL) {\n+\t\tint encoding;\n+\t\tstrbuf_reset(&charset_q);\n+\t\tstrbuf_reset(&piecebuf);\n+\n+\t\tif (in != ep) {\n+\t\t\t/*\n+\t\t\t * We are about to process an encoded-word\n+\t\t\t * that begins at ep, but there is something\n+\t\t\t * before the encoded word.\n+\t\t\t */\n+\t\t\tchar *scan;\n+\t\t\tfor (scan = in; scan < ep; scan++)\n+\t\t\t\tif (!isspace(*scan))\n+\t\t\t\t\tbreak;\n+\n+\t\t\tif (scan != ep || in == it->buf) {\n+\t\t\t\t/*\n+\t\t\t\t * We should not lose that \"something\",\n+\t\t\t\t * unless we have just processed an\n+\t\t\t\t * encoded-word, and there is only LWS\n+\t\t\t\t * before the one we are about to process.\n+\t\t\t\t */\n+\t\t\t\tstrbuf_add(&outbuf, in, ep - in);\n+\t\t\t}\n+\t\t}\n+\t\t/* E.g.\n+\t\t * ep : \"=?iso-2022-jp?B?GyR...?= foo\"\n+\t\t * ep : \"=?ISO-8859-1?Q?Foo=FCbar?= baz\"\n+\t\t */\n+\t\tep += 2;\n+\n+\t\tif (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))\n+\t\t\tgoto release_return;\n+\n+\t\tif (cp + 3 - it->buf > it->len)\n+\t\t\tgoto release_return;\n+\t\tstrbuf_add(&charset_q, ep, cp - ep);\n+\n+\t\tencoding = cp[1];\n+\t\tif (!encoding || cp[2] != '?')\n+\t\t\tgoto release_return;\n+\t\tep = strstr(cp + 3, \"?=\");\n+\t\tif (!ep)\n+\t\t\tgoto release_return;\n+\t\tstrbuf_add(&piecebuf, cp + 3, ep - cp - 3);\n+\t\tswitch (tolower(encoding)) {\n+\t\tdefault:\n+\t\t\tgoto release_return;\n+\t\tcase 'b':\n+\t\t\tdec = decode_b_segment(&piecebuf);\n+\t\t\tbreak;\n+\t\tcase 'q':\n+\t\t\tdec = decode_q_segment(&piecebuf, 1);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (convert_to_utf8(mi, dec, charset_q.buf))\n+\t\t\tgoto release_return;\n+\n+\t\tstrbuf_addbuf(&outbuf, dec);\n+\t\tstrbuf_release(dec);\n+\t\tfree(dec);\n+\t\tin = ep + 2;\n+\t}\n+\tstrbuf_addstr(&outbuf, in);\n+\tstrbuf_reset(it);\n+\tstrbuf_addbuf(it, &outbuf);\n+\tfound_error = 0;\n+release_return:\n+\tstrbuf_release(&outbuf);\n+\tstrbuf_release(&charset_q);\n+\tstrbuf_release(&piecebuf);\n+\n+\tif (found_error)\n+\t\tmi->input_error = -1;\n+}\n+\n+static int check_header(struct mailinfo *mi,\n+\t\t\tconst struct strbuf *line,\n+\t\t\tstruct strbuf *hdr_data[], int overwrite)\n+{\n+\tint i, ret = 0, len;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t/* search for the interesting parts */\n+\tfor (i = 0; header[i]; i++) {\n+\t\tint len = strlen(header[i]);\n+\t\tif ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {\n+\t\t\t/* Unwrap inline B and Q encoding, and optionally\n+\t\t\t * normalize the meta information to utf8.\n+\t\t\t */\n+\t\t\tstrbuf_add(&sb, line->buf + len + 2, line->len - len - 2);\n+\t\t\tdecode_header(mi, &sb);\n+\t\t\thandle_header(&hdr_data[i], &sb);\n+\t\t\tret = 1;\n+\t\t\tgoto check_header_out;\n+\t\t}\n+\t}\n+\n+\t/* Content stuff */\n+\tif (cmp_header(line, \"Content-Type\")) {\n+\t\tlen = strlen(\"Content-Type: \");\n+\t\tstrbuf_add(&sb, line->buf + len, line->len - len);\n+\t\tdecode_header(mi, &sb);\n+\t\tstrbuf_insert(&sb, 0, \"Content-Type: \", len);\n+\t\thandle_content_type(mi, &sb);\n+\t\tret = 1;\n+\t\tgoto check_header_out;\n+\t}\n+\tif (cmp_header(line, \"Content-Transfer-Encoding\")) {\n+\t\tlen = strlen(\"Content-Transfer-Encoding: \");\n+\t\tstrbuf_add(&sb, line->buf + len, line->len - len);\n+\t\tdecode_header(mi, &sb);\n+\t\thandle_content_transfer_encoding(mi, &sb);\n+\t\tret = 1;\n+\t\tgoto check_header_out;\n+\t}\n+\tif (cmp_header(line, \"Message-Id\")) {\n+\t\tlen = strlen(\"Message-Id: \");\n+\t\tstrbuf_add(&sb, line->buf + len, line->len - len);\n+\t\tdecode_header(mi, &sb);\n+\t\tif (mi->add_message_id)\n+\t\t\tmi->message_id = strbuf_detach(&sb, NULL);\n+\t\tret = 1;\n+\t\tgoto check_header_out;\n+\t}\n+\n+check_header_out:\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n+/*\n+ * Returns 1 if the given line or any line beginning with the given line is an\n+ * in-body header (that is, check_header will succeed when passed\n+ * mi->s_hdr_data).\n+ */\n+static int is_inbody_header(const struct mailinfo *mi,\n+\t\t\t    const struct strbuf *line)\n+{\n+\tint i;\n+\tfor (i = 0; header[i]; i++)\n+\t\tif (!mi->s_hdr_data[i] && cmp_header(line, header[i]))\n+\t\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf *ret;\n+\n+\tswitch (mi->transfer_encoding) {\n+\tcase TE_QP:\n+\t\tret = decode_q_segment(line, 0);\n+\t\tbreak;\n+\tcase TE_BASE64:\n+\t\tret = decode_b_segment(line);\n+\t\tbreak;\n+\tcase TE_DONTCARE:\n+\tdefault:\n+\t\treturn;\n+\t}\n+\tstrbuf_reset(line);\n+\tstrbuf_addbuf(line, ret);\n+\tstrbuf_release(ret);\n+\tfree(ret);\n+}\n+\n+static inline int patchbreak(const struct strbuf *line)\n+{\n+\tsize_t i;\n+\n+\t/* Beginning of a \"diff -\" header? */\n+\tif (starts_with(line->buf, \"diff -\"))\n+\t\treturn 1;\n+\n+\t/* CVS \"Index: \" line? */\n+\tif (starts_with(line->buf, \"Index: \"))\n+\t\treturn 1;\n+\n+\t/*\n+\t * \"--- <filename>\" starts patches without headers\n+\t * \"---<sp>*\" is a manual separator\n+\t */\n+\tif (line->len < 4)\n+\t\treturn 0;\n+\n+\tif (starts_with(line->buf, \"---\")) {\n+\t\t/* space followed by a filename? */\n+\t\tif (line->buf[3] == ' ' && !isspace(line->buf[4]))\n+\t\t\treturn 1;\n+\t\t/* Just whitespace? */\n+\t\tfor (i = 3; i < line->len; i++) {\n+\t\t\tunsigned char c = line->buf[i];\n+\t\t\tif (c == '\\n')\n+\t\t\t\treturn 1;\n+\t\t\tif (!isspace(c))\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n+static int is_scissors_line(const char *line)\n+{\n+\tconst char *c;\n+\tint scissors = 0, gap = 0;\n+\tconst char *first_nonblank = NULL, *last_nonblank = NULL;\n+\tint visible, perforation = 0, in_perforation = 0;\n+\n+\tfor (c = line; *c; c++) {\n+\t\tif (isspace(*c)) {\n+\t\t\tif (in_perforation) {\n+\t\t\t\tperforation++;\n+\t\t\t\tgap++;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\tlast_nonblank = c;\n+\t\tif (first_nonblank == NULL)\n+\t\t\tfirst_nonblank = c;\n+\t\tif (*c == '-') {\n+\t\t\tin_perforation = 1;\n+\t\t\tperforation++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif ((!memcmp(c, \">8\", 2) || !memcmp(c, \"8<\", 2) ||\n+\t\t     !memcmp(c, \">%\", 2) || !memcmp(c, \"%<\", 2))) {\n+\t\t\tin_perforation = 1;\n+\t\t\tperforation += 2;\n+\t\t\tscissors += 2;\n+\t\t\tc++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tin_perforation = 0;\n+\t}\n+\n+\t/*\n+\t * The mark must be at least 8 bytes long (e.g. \"-- >8 --\").\n+\t * Even though there can be arbitrary cruft on the same line\n+\t * (e.g. \"cut here\"), in order to avoid misidentification, the\n+\t * perforation must occupy more than a third of the visible\n+\t * width of the line, and dashes and scissors must occupy more\n+\t * than half of the perforation.\n+\t */\n+\n+\tif (first_nonblank && last_nonblank)\n+\t\tvisible = last_nonblank - first_nonblank + 1;\n+\telse\n+\t\tvisible = 0;\n+\treturn (scissors && 8 <= visible &&\n+\t\tvisible < perforation * 3 &&\n+\t\tgap * 2 < perforation);\n+}\n+\n+static void flush_inbody_header_accum(struct mailinfo *mi)\n+{\n+\tif (!mi->inbody_header_accum.len)\n+\t\treturn;\n+\tif (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))\n+\t\tBUG(\"inbody_header_accum, if not empty, must always contain a valid in-body header\");\n+\tstrbuf_reset(&mi->inbody_header_accum);\n+}\n+\n+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\tif (mi->inbody_header_accum.len &&\n+\t    (line->buf[0] == ' ' || line->buf[0] == '\\t')) {\n+\t\tif (mi->use_scissors && is_scissors_line(line->buf)) {\n+\t\t\t/*\n+\t\t\t * This is a scissors line; do not consider this line\n+\t\t\t * as a header continuation line.\n+\t\t\t */\n+\t\t\tflush_inbody_header_accum(mi);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tstrbuf_strip_suffix(&mi->inbody_header_accum, \"\\n\");\n+\t\tstrbuf_addbuf(&mi->inbody_header_accum, line);\n+\t\treturn 1;\n+\t}\n+\n+\tflush_inbody_header_accum(mi);\n+\n+\tif (starts_with(line->buf, \">From\") && isspace(line->buf[5]))\n+\t\treturn is_format_patch_separator(line->buf + 1, line->len - 1);\n+\tif (starts_with(line->buf, \"[PATCH]\") && isspace(line->buf[7])) {\n+\t\tint i;\n+\t\tfor (i = 0; header[i]; i++)\n+\t\t\tif (!strcmp(\"Subject\", header[i])) {\n+\t\t\t\thandle_header(&mi->s_hdr_data[i], line);\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\treturn 0;\n+\t}\n+\tif (is_inbody_header(mi, line)) {\n+\t\tstrbuf_addbuf(&mi->inbody_header_accum, line);\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tassert(!mi->filter_stage);\n+\n+\tif (mi->header_stage) {\n+\t\tif (!line->len || (line->len == 1 && line->buf[0] == '\\n')) {\n+\t\t\tif (mi->inbody_header_accum.len) {\n+\t\t\t\tflush_inbody_header_accum(mi);\n+\t\t\t\tmi->header_stage = 0;\n+\t\t\t}\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (mi->use_inbody_headers && mi->header_stage) {\n+\t\tmi->header_stage = check_inbody_header(mi, line);\n+\t\tif (mi->header_stage)\n+\t\t\treturn 0;\n+\t} else\n+\t\t/* Only trim the first (blank) line of the commit message\n+\t\t * when ignoring in-body headers.\n+\t\t */\n+\t\tmi->header_stage = 0;\n+\n+\t/* normalize the log message to UTF-8. */\n+\tif (convert_to_utf8(mi, line, mi->charset.buf))\n+\t\treturn 0; /* mi->input_error already set */\n+\n+\tif (mi->use_scissors && is_scissors_line(line->buf)) {\n+\t\tint i;\n+\n+\t\tstrbuf_setlen(&mi->log_message, 0);\n+\t\tmi->header_stage = 1;\n+\n+\t\t/*\n+\t\t * We may have already read \"secondary headers\"; purge\n+\t\t * them to give ourselves a clean restart.\n+\t\t */\n+\t\tfor (i = 0; header[i]; i++) {\n+\t\t\tif (mi->s_hdr_data[i])\n+\t\t\t\tstrbuf_release(mi->s_hdr_data[i]);\n+\t\t\tmi->s_hdr_data[i] = NULL;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n+\tif (patchbreak(line)) {\n+\t\tif (mi->message_id)\n+\t\t\tstrbuf_addf(&mi->log_message,\n+\t\t\t\t    \"Message-Id: %s\\n\", mi->message_id);\n+\t\treturn 1;\n+\t}\n+\n+\tstrbuf_addbuf(&mi->log_message, line);\n+\treturn 0;\n+}\n+\n+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\tfwrite(line->buf, 1, line->len, mi->patchfile);\n+\tmi->patch_lines++;\n+}\n+\n+static void handle_filter(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tswitch (mi->filter_stage) {\n+\tcase 0:\n+\t\tif (!handle_commit_msg(mi, line))\n+\t\t\tbreak;\n+\t\tmi->filter_stage++;\n+\t\t/* fallthrough */\n+\tcase 1:\n+\t\thandle_patch(mi, line);\n+\t\tbreak;\n+\t}\n+}\n+\n+static int is_rfc2822_header(const struct strbuf *line)\n+{\n+\t/*\n+\t * The section that defines the loosest possible\n+\t * field name is \"3.6.8 Optional fields\".\n+\t *\n+\t * optional-field = field-name \":\" unstructured CRLF\n+\t * field-name = 1*ftext\n+\t * ftext = %d33-57 / %59-126\n+\t */\n+\tint ch;\n+\tchar *cp = line->buf;\n+\n+\t/* Count mbox From headers as headers */\n+\tif (starts_with(cp, \"From \") || starts_with(cp, \">From \"))\n+\t\treturn 1;\n+\n+\twhile ((ch = *cp++)) {\n+\t\tif (ch == ':')\n+\t\t\treturn 1;\n+\t\tif ((33 <= ch && ch <= 57) ||\n+\t\t    (59 <= ch && ch <= 126))\n+\t\t\tcontinue;\n+\t\tbreak;\n+\t}\n+\treturn 0;\n+}\n+\n+static int read_one_header_line(struct strbuf *line, FILE *in)\n+{\n+\tstruct strbuf continuation = STRBUF_INIT;\n+\n+\t/* Get the first part of the line. */\n+\tif (strbuf_getline_lf(line, in))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Is it an empty line or not a valid rfc2822 header?\n+\t * If so, stop here, and return false (\"not a header\")\n+\t */\n+\tstrbuf_rtrim(line);\n+\tif (!line->len || !is_rfc2822_header(line)) {\n+\t\t/* Re-add the newline */\n+\t\tstrbuf_addch(line, '\\n');\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * Now we need to eat all the continuation lines..\n+\t * Yuck, 2822 header \"folding\"\n+\t */\n+\tfor (;;) {\n+\t\tint peek;\n+\n+\t\tpeek = fgetc(in);\n+\t\tif (peek == EOF)\n+\t\t\tbreak;\n+\t\tungetc(peek, in);\n+\t\tif (peek != ' ' && peek != '\\t')\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_lf(&continuation, in))\n+\t\t\tbreak;\n+\t\tcontinuation.buf[0] = ' ';\n+\t\tstrbuf_rtrim(&continuation);\n+\t\tstrbuf_addbuf(line, &continuation);\n+\t}\n+\tstrbuf_release(&continuation);\n+\n+\treturn 1;\n+}\n+\n+static int find_boundary(struct mailinfo *mi, struct strbuf *line)\n+{\n+\twhile (!strbuf_getline_lf(line, mi->input)) {\n+\t\tif (*(mi->content_top) && is_multipart_boundary(mi, line))\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf newline = STRBUF_INIT;\n+\n+\tstrbuf_addch(&newline, '\\n');\n+again:\n+\tif (line->len >= (*(mi->content_top))->len + 2 &&\n+\t    !memcmp(line->buf + (*(mi->content_top))->len, \"--\", 2)) {\n+\t\t/* we hit an end boundary */\n+\t\t/* pop the current boundary off the stack */\n+\t\tstrbuf_release(*(mi->content_top));\n+\t\tFREE_AND_NULL(*(mi->content_top));\n+\n+\t\t/* technically won't happen as is_multipart_boundary()\n+\t\t   will fail first.  But just in case..\n+\t\t */\n+\t\tif (--mi->content_top < mi->content) {\n+\t\t\terror(\"Detected mismatched boundaries, can't recover\");\n+\t\t\tmi->input_error = -1;\n+\t\t\tmi->content_top = mi->content;\n+\t\t\tstrbuf_release(&newline);\n+\t\t\treturn 0;\n+\t\t}\n+\t\thandle_filter(mi, &newline);\n+\t\tstrbuf_release(&newline);\n+\t\tif (mi->input_error)\n+\t\t\treturn 0;\n+\n+\t\t/* skip to the next boundary */\n+\t\tif (!find_boundary(mi, line))\n+\t\t\treturn 0;\n+\t\tgoto again;\n+\t}\n+\n+\t/* set some defaults */\n+\tmi->transfer_encoding = TE_DONTCARE;\n+\tstrbuf_reset(&mi->charset);\n+\n+\t/* slurp in this section's info */\n+\twhile (read_one_header_line(line, mi->input))\n+\t\tcheck_header(mi, line, mi->p_hdr_data, 0);\n+\n+\tstrbuf_release(&newline);\n+\t/* replenish line */\n+\tif (strbuf_getline_lf(line, mi->input))\n+\t\treturn 0;\n+\tstrbuf_addch(line, '\\n');\n+\treturn 1;\n+}\n+\n+static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n+\t\t\t\t struct strbuf *prev)\n+{\n+\tsize_t len = line->len;\n+\tconst char *rest;\n+\n+\tif (!mi->format_flowed) {\n+\t\thandle_filter(mi, line);\n+\t\treturn;\n+\t}\n+\n+\tif (line->buf[len - 1] == '\\n') {\n+\t\tlen--;\n+\t\tif (len && line->buf[len - 1] == '\\r')\n+\t\t\tlen--;\n+\t}\n+\n+\t/* Keep signature separator as-is. */\n+\tif (skip_prefix(line->buf, \"-- \", &rest) && rest - line->buf == len) {\n+\t\tif (prev->len) {\n+\t\t\thandle_filter(mi, prev);\n+\t\t\tstrbuf_reset(prev);\n+\t\t}\n+\t\thandle_filter(mi, line);\n+\t\treturn;\n+\t}\n+\n+\t/* Unstuff space-stuffed line. */\n+\tif (len && line->buf[0] == ' ') {\n+\t\tstrbuf_remove(line, 0, 1);\n+\t\tlen--;\n+\t}\n+\n+\t/* Save flowed line for later, but without the soft line break. */\n+\tif (len && line->buf[len - 1] == ' ') {\n+\t\tstrbuf_add(prev, line->buf, len - !!mi->delsp);\n+\t\treturn;\n+\t}\n+\n+\t/* Prepend any previous partial lines */\n+\tstrbuf_insert(line, 0, prev->buf, prev->len);\n+\tstrbuf_reset(prev);\n+\n+\thandle_filter(mi, line);\n+}\n+\n+static void handle_body(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf prev = STRBUF_INIT;\n+\n+\t/* Skip up to the first boundary */\n+\tif (*(mi->content_top)) {\n+\t\tif (!find_boundary(mi, line))\n+\t\t\tgoto handle_body_out;\n+\t}\n+\n+\tdo {\n+\t\t/* process any boundary lines */\n+\t\tif (*(mi->content_top) && is_multipart_boundary(mi, line)) {\n+\t\t\t/* flush any leftover */\n+\t\t\tif (prev.len) {\n+\t\t\t\thandle_filter(mi, &prev);\n+\t\t\t\tstrbuf_reset(&prev);\n+\t\t\t}\n+\t\t\tif (!handle_boundary(mi, line))\n+\t\t\t\tgoto handle_body_out;\n+\t\t}\n+\n+\t\t/* Unwrap transfer encoding */\n+\t\tdecode_transfer_encoding(mi, line);\n+\n+\t\tswitch (mi->transfer_encoding) {\n+\t\tcase TE_BASE64:\n+\t\tcase TE_QP:\n+\t\t{\n+\t\t\tstruct strbuf **lines, **it, *sb;\n+\n+\t\t\t/* Prepend any previous partial lines */\n+\t\t\tstrbuf_insert(line, 0, prev.buf, prev.len);\n+\t\t\tstrbuf_reset(&prev);\n+\n+\t\t\t/*\n+\t\t\t * This is a decoded line that may contain\n+\t\t\t * multiple new lines.  Pass only one chunk\n+\t\t\t * at a time to handle_filter()\n+\t\t\t */\n+\t\t\tlines = strbuf_split(line, '\\n');\n+\t\t\tfor (it = lines; (sb = *it); it++) {\n+\t\t\t\tif (*(it + 1) == NULL) /* The last line */\n+\t\t\t\t\tif (sb->buf[sb->len - 1] != '\\n') {\n+\t\t\t\t\t\t/* Partial line, save it for later. */\n+\t\t\t\t\t\tstrbuf_addbuf(&prev, sb);\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\thandle_filter_flowed(mi, sb, &prev);\n+\t\t\t}\n+\t\t\t/*\n+\t\t\t * The partial chunk is saved in \"prev\" and will be\n+\t\t\t * appended by the next iteration of read_line_with_nul().\n+\t\t\t */\n+\t\t\tstrbuf_list_free(lines);\n+\t\t\tbreak;\n+\t\t}\n+\t\tdefault:\n+\t\t\thandle_filter_flowed(mi, line, &prev);\n+\t\t}\n+\n+\t\tif (mi->input_error)\n+\t\t\tbreak;\n+\t} while (!strbuf_getwholeline(line, mi->input, '\\n'));\n+\n+\tif (prev.len)\n+\t\thandle_filter(mi, &prev);\n+\n+\tflush_inbody_header_accum(mi);\n+\n+handle_body_out:\n+\tstrbuf_release(&prev);\n+}\n+\n+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)\n+{\n+\tconst char *sp = data->buf;\n+\twhile (1) {\n+\t\tchar *ep = strchr(sp, '\\n');\n+\t\tint len;\n+\t\tif (!ep)\n+\t\t\tlen = strlen(sp);\n+\t\telse\n+\t\t\tlen = ep - sp;\n+\t\tfprintf(fout, \"%s: %.*s\\n\", hdr, len, sp);\n+\t\tif (!ep)\n+\t\t\tbreak;\n+\t\tsp = ep + 1;\n+\t}\n+}\n+\n+static void handle_info(struct mailinfo *mi)\n+{\n+\tstruct strbuf *hdr;\n+\tint i;\n+\n+\tfor (i = 0; header[i]; i++) {\n+\t\t/* only print inbody headers if we output a patch file */\n+\t\tif (mi->patch_lines && mi->s_hdr_data[i])\n+\t\t\thdr = mi->s_hdr_data[i];\n+\t\telse if (mi->p_hdr_data[i])\n+\t\t\thdr = mi->p_hdr_data[i];\n+\t\telse\n+\t\t\tcontinue;\n+\n+\t\tif (!strcmp(header[i], \"Subject\")) {\n+\t\t\tif (!mi->keep_subject) {\n+\t\t\t\tcleanup_subject(mi, hdr);\n+\t\t\t\tcleanup_space(hdr);\n+\t\t\t}\n+\t\t\toutput_header_lines(mi->output, \"Subject\", hdr);\n+\t\t} else if (!strcmp(header[i], \"From\")) {\n+\t\t\tcleanup_space(hdr);\n+\t\t\thandle_from(mi, hdr);\n+\t\t\tfprintf(mi->output, \"Author: %s\\n\", mi->name.buf);\n+\t\t\tfprintf(mi->output, \"Email: %s\\n\", mi->email.buf);\n+\t\t} else {\n+\t\t\tcleanup_space(hdr);\n+\t\t\tfprintf(mi->output, \"%s: %s\\n\", header[i], hdr->buf);\n+\t\t}\n+\t}\n+\tfprintf(mi->output, \"\\n\");\n+}\n+\n+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n+{\n+\tFILE *cmitmsg;\n+\tint peek;\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\tcmitmsg = fopen(msg, \"w\");\n+\tif (!cmitmsg) {\n+\t\tperror(msg);\n+\t\treturn -1;\n+\t}\n+\tmi->patchfile = fopen(patch, \"w\");\n+\tif (!mi->patchfile) {\n+\t\tperror(patch);\n+\t\tfclose(cmitmsg);\n+\t\treturn -1;\n+\t}\n+\n+\tmi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));\n+\tmi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));\n+\n+\tdo {\n+\t\tpeek = fgetc(mi->input);\n+\t\tif (peek == EOF) {\n+\t\t\tfclose(cmitmsg);\n+\t\t\treturn error(\"empty patch: '%s'\", patch);\n+\t\t}\n+\t} while (isspace(peek));\n+\tungetc(peek, mi->input);\n+\n+\t/* process the email header */\n+\twhile (read_one_header_line(&line, mi->input))\n+\t\tcheck_header(mi, &line, mi->p_hdr_data, 1);\n+\n+\thandle_body(mi, &line);\n+\tfwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);\n+\tfclose(cmitmsg);\n+\tfclose(mi->patchfile);\n+\n+\thandle_info(mi);\n+\tstrbuf_release(&line);\n+\treturn mi->input_error;\n+}\n+\n+static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+{\n+\tstruct mailinfo *mi = mi_;\n+\n+\tif (!starts_with(var, \"mailinfo.\"))\n+\t\treturn git_default_config(var, value, NULL);\n+\tif (!strcmp(var, \"mailinfo.scissors\")) {\n+\t\tmi->use_scissors = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\t/* perhaps others here */\n+\treturn 0;\n+}\n+\n+void setup_mailinfo(struct mailinfo *mi)\n+{\n+\tmemset(mi, 0, sizeof(*mi));\n+\tstrbuf_init(&mi->name, 0);\n+\tstrbuf_init(&mi->email, 0);\n+\tstrbuf_init(&mi->charset, 0);\n+\tstrbuf_init(&mi->log_message, 0);\n+\tstrbuf_init(&mi->inbody_header_accum, 0);\n+\tmi->header_stage = 1;\n+\tmi->use_inbody_headers = 1;\n+\tmi->content_top = mi->content;\n+\tgit_config(git_mailinfo_config, mi);\n+}\n+\n+void clear_mailinfo(struct mailinfo *mi)\n+{\n+\tint i;\n+\n+\tstrbuf_release(&mi->name);\n+\tstrbuf_release(&mi->email);\n+\tstrbuf_release(&mi->charset);\n+\tstrbuf_release(&mi->inbody_header_accum);\n+\tfree(mi->message_id);\n+\n+\tif (mi->p_hdr_data)\n+\t\tfor (i = 0; mi->p_hdr_data[i]; i++)\n+\t\t\tstrbuf_release(mi->p_hdr_data[i]);\n+\tfree(mi->p_hdr_data);\n+\tif (mi->s_hdr_data)\n+\t\tfor (i = 0; mi->s_hdr_data[i]; i++)\n+\t\t\tstrbuf_release(mi->s_hdr_data[i]);\n+\tfree(mi->s_hdr_data);\n+\n+\twhile (mi->content < mi->content_top) {\n+\t\tfree(*(mi->content_top));\n+\t\tmi->content_top--;\n+\t}\n+\n+\tstrbuf_release(&mi->log_message);\n+}\ndiff --git a/t/t4256/1/mailinfo.c.orig b/t/t4256/1/mailinfo.c.orig\nnew file mode 100644\nindex 0000000000..3281a37d51\n--- /dev/null\n+++ b/t/t4256/1/mailinfo.c.orig\n@@ -0,0 +1,1185 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"utf8.h\"\n+#include \"strbuf.h\"\n+#include \"mailinfo.h\"\n+\n+static void cleanup_space(struct strbuf *sb)\n+{\n+\tsize_t pos, cnt;\n+\tfor (pos = 0; pos < sb->len; pos++) {\n+\t\tif (isspace(sb->buf[pos])) {\n+\t\t\tsb->buf[pos] = ' ';\n+\t\t\tfor (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);\n+\t\t\tstrbuf_remove(sb, pos + 1, cnt);\n+\t\t}\n+\t}\n+}\n+\n+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)\n+{\n+\tstruct strbuf *src = name;\n+\tif (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||\n+\t\tstrchr(name->buf, '<') || strchr(name->buf, '>'))\n+\t\tsrc = email;\n+\telse if (name == out)\n+\t\treturn;\n+\tstrbuf_reset(out);\n+\tstrbuf_addbuf(out, src);\n+}\n+\n+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\t/* John Doe <johndoe> */\n+\n+\tchar *bra, *ket;\n+\t/* This is fallback, so do not bother if we already have an\n+\t * e-mail address.\n+\t */\n+\tif (mi->email.len)\n+\t\treturn;\n+\n+\tbra = strchr(line->buf, '<');\n+\tif (!bra)\n+\t\treturn;\n+\tket = strchr(bra, '>');\n+\tif (!ket)\n+\t\treturn;\n+\n+\tstrbuf_reset(&mi->email);\n+\tstrbuf_add(&mi->email, bra + 1, ket - bra - 1);\n+\n+\tstrbuf_reset(&mi->name);\n+\tstrbuf_add(&mi->name, line->buf, bra - line->buf);\n+\tstrbuf_trim(&mi->name);\n+\tget_sane_name(&mi->name, &mi->name, &mi->email);\n+}\n+\n+static const char *unquote_comment(struct strbuf *outbuf, const char *in)\n+{\n+\tint c;\n+\tint take_next_literally = 0;\n+\n+\tstrbuf_addch(outbuf, '(');\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (take_next_literally == 1) {\n+\t\t\ttake_next_literally = 0;\n+\t\t} else {\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\\\':\n+\t\t\t\ttake_next_literally = 1;\n+\t\t\t\tcontinue;\n+\t\t\tcase '(':\n+\t\t\t\tin = unquote_comment(outbuf, in);\n+\t\t\t\tcontinue;\n+\t\t\tcase ')':\n+\t\t\t\tstrbuf_addch(outbuf, ')');\n+\t\t\t\treturn in;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstrbuf_addch(outbuf, c);\n+\t}\n+\n+\treturn in;\n+}\n+\n+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)\n+{\n+\tint c;\n+\tint take_next_literally = 0;\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (take_next_literally == 1) {\n+\t\t\ttake_next_literally = 0;\n+\t\t} else {\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\\\':\n+\t\t\t\ttake_next_literally = 1;\n+\t\t\t\tcontinue;\n+\t\t\tcase '\"':\n+\t\t\t\treturn in;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstrbuf_addch(outbuf, c);\n+\t}\n+\n+\treturn in;\n+}\n+\n+static void unquote_quoted_pair(struct strbuf *line)\n+{\n+\tstruct strbuf outbuf;\n+\tconst char *in = line->buf;\n+\tint c;\n+\n+\tstrbuf_init(&outbuf, line->len);\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tswitch (c) {\n+\t\tcase '\"':\n+\t\t\tin = unquote_quoted_string(&outbuf, in);\n+\t\t\tcontinue;\n+\t\tcase '(':\n+\t\t\tin = unquote_comment(&outbuf, in);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_addch(&outbuf, c);\n+\t}\n+\n+\tstrbuf_swap(&outbuf, line);\n+\tstrbuf_release(&outbuf);\n+\n+}\n+\n+static void handle_from(struct mailinfo *mi, const struct strbuf *from)\n+{\n+\tchar *at;\n+\tsize_t el;\n+\tstruct strbuf f;\n+\n+\tstrbuf_init(&f, from->len);\n+\tstrbuf_addbuf(&f, from);\n+\n+\tunquote_quoted_pair(&f);\n+\n+\tat = strchr(f.buf, '@');\n+\tif (!at) {\n+\t\tparse_bogus_from(mi, from);\n+\t\tgoto out;\n+\t}\n+\n+\t/*\n+\t * If we already have one email, don't take any confusing lines\n+\t */\n+\tif (mi->email.len && strchr(at + 1, '@'))\n+\t\tgoto out;\n+\n+\t/* Pick up the string around '@', possibly delimited with <>\n+\t * pair; that is the email part.\n+\t */\n+\twhile (at > f.buf) {\n+\t\tchar c = at[-1];\n+\t\tif (isspace(c))\n+\t\t\tbreak;\n+\t\tif (c == '<') {\n+\t\t\tat[-1] = ' ';\n+\t\t\tbreak;\n+\t\t}\n+\t\tat--;\n+\t}\n+\tel = strcspn(at, \" \\n\\t\\r\\v\\f>\");\n+\tstrbuf_reset(&mi->email);\n+\tstrbuf_add(&mi->email, at, el);\n+\tstrbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));\n+\n+\t/* The remainder is name.  It could be\n+\t *\n+\t * - \"John Doe <john.doe@xz>\"\t\t\t(a), or\n+\t * - \"john.doe@xz (John Doe)\"\t\t\t(b), or\n+\t * - \"John (zzz) Doe <john.doe@xz> (Comment)\"\t(c)\n+\t *\n+\t * but we have removed the email part, so\n+\t *\n+\t * - remove extra spaces which could stay after email (case 'c'), and\n+\t * - trim from both ends, possibly removing the () pair at the end\n+\t *   (cases 'a' and 'b').\n+\t */\n+\tcleanup_space(&f);\n+\tstrbuf_trim(&f);\n+\tif (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {\n+\t\tstrbuf_remove(&f, 0, 1);\n+\t\tstrbuf_setlen(&f, f.len - 1);\n+\t}\n+\n+\tget_sane_name(&mi->name, &f, &mi->email);\n+out:\n+\tstrbuf_release(&f);\n+}\n+\n+static void handle_header(struct strbuf **out, const struct strbuf *line)\n+{\n+\tif (!*out) {\n+\t\t*out = xmalloc(sizeof(struct strbuf));\n+\t\tstrbuf_init(*out, line->len);\n+\t} else\n+\t\tstrbuf_reset(*out);\n+\n+\tstrbuf_addbuf(*out, line);\n+}\n+\n+/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt\n+ * to have enough heuristics to grok MIME encoded patches often found\n+ * on our mailing lists.  For example, we do not even treat header lines\n+ * case insensitively.\n+ */\n+\n+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)\n+{\n+\tconst char *ends, *ap = strcasestr(line, name);\n+\tsize_t sz;\n+\n+\tstrbuf_setlen(attr, 0);\n+\tif (!ap)\n+\t\treturn 0;\n+\tap += strlen(name);\n+\tif (*ap == '\"') {\n+\t\tap++;\n+\t\tends = \"\\\"\";\n+\t}\n+\telse\n+\t\tends = \"; \\t\";\n+\tsz = strcspn(ap, ends);\n+\tstrbuf_add(attr, ap, sz);\n+\treturn 1;\n+}\n+\n+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf *boundary = xmalloc(sizeof(struct strbuf));\n+\tstrbuf_init(boundary, line->len);\n+\n+\tif (slurp_attr(line->buf, \"boundary=\", boundary)) {\n+\t\tstrbuf_insert(boundary, 0, \"--\", 2);\n+\t\tif (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {\n+\t\t\terror(\"Too many boundaries to handle\");\n+\t\t\tmi->input_error = -1;\n+\t\t\tmi->content_top = &mi->content[MAX_BOUNDARIES] - 1;\n+\t\t\treturn;\n+\t\t}\n+\t\t*(mi->content_top) = boundary;\n+\t\tboundary = NULL;\n+\t}\n+\tslurp_attr(line->buf, \"charset=\", &mi->charset);\n+\n+\tif (boundary) {\n+\t\tstrbuf_release(boundary);\n+\t\tfree(boundary);\n+\t}\n+}\n+\n+static void handle_content_transfer_encoding(struct mailinfo *mi,\n+\t\t\t\t\t     const struct strbuf *line)\n+{\n+\tif (strcasestr(line->buf, \"base64\"))\n+\t\tmi->transfer_encoding = TE_BASE64;\n+\telse if (strcasestr(line->buf, \"quoted-printable\"))\n+\t\tmi->transfer_encoding = TE_QP;\n+\telse\n+\t\tmi->transfer_encoding = TE_DONTCARE;\n+}\n+\n+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\tstruct strbuf *content_top = *(mi->content_top);\n+\n+\treturn ((content_top->len <= line->len) &&\n+\t\t!memcmp(line->buf, content_top->buf, content_top->len));\n+}\n+\n+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)\n+{\n+\tsize_t at = 0;\n+\n+\twhile (at < subject->len) {\n+\t\tchar *pos;\n+\t\tsize_t remove;\n+\n+\t\tswitch (subject->buf[at]) {\n+\t\tcase 'r': case 'R':\n+\t\t\tif (subject->len <= at + 3)\n+\t\t\t\tbreak;\n+\t\t\tif ((subject->buf[at + 1] == 'e' ||\n+\t\t\t     subject->buf[at + 1] == 'E') &&\n+\t\t\t    subject->buf[at + 2] == ':') {\n+\t\t\t\tstrbuf_remove(subject, at, 3);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tat++;\n+\t\t\tbreak;\n+\t\tcase ' ': case '\\t': case ':':\n+\t\t\tstrbuf_remove(subject, at, 1);\n+\t\t\tcontinue;\n+\t\tcase '[':\n+\t\t\tpos = strchr(subject->buf + at, ']');\n+\t\t\tif (!pos)\n+\t\t\t\tbreak;\n+\t\t\tremove = pos - subject->buf + at + 1;\n+\t\t\tif (!mi->keep_non_patch_brackets_in_subject ||\n+\t\t\t    (7 <= remove &&\n+\t\t\t     memmem(subject->buf + at, remove, \"PATCH\", 5)))\n+\t\t\t\tstrbuf_remove(subject, at, remove);\n+\t\t\telse {\n+\t\t\t\tat += remove;\n+\t\t\t\t/*\n+\t\t\t\t * If the input had a space after the ], keep\n+\t\t\t\t * it.  We don't bother with finding the end of\n+\t\t\t\t * the space, since we later normalize it\n+\t\t\t\t * anyway.\n+\t\t\t\t */\n+\t\t\t\tif (isspace(subject->buf[at]))\n+\t\t\t\t\tat += 1;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\tstrbuf_trim(subject);\n+}\n+\n+#define MAX_HDR_PARSED 10\n+static const char *header[MAX_HDR_PARSED] = {\n+\t\"From\",\"Subject\",\"Date\",\n+};\n+\n+static inline int cmp_header(const struct strbuf *line, const char *hdr)\n+{\n+\tint len = strlen(hdr);\n+\treturn !strncasecmp(line->buf, hdr, len) && line->len > len &&\n+\t\t\tline->buf[len] == ':' && isspace(line->buf[len + 1]);\n+}\n+\n+static int is_format_patch_separator(const char *line, int len)\n+{\n+\tstatic const char SAMPLE[] =\n+\t\t\"From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\\n\";\n+\tconst char *cp;\n+\n+\tif (len != strlen(SAMPLE))\n+\t\treturn 0;\n+\tif (!skip_prefix(line, \"From \", &cp))\n+\t\treturn 0;\n+\tif (strspn(cp, \"0123456789abcdef\") != 40)\n+\t\treturn 0;\n+\tcp += 40;\n+\treturn !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));\n+}\n+\n+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)\n+{\n+\tconst char *in = q_seg->buf;\n+\tint c;\n+\tstruct strbuf *out = xmalloc(sizeof(struct strbuf));\n+\tstrbuf_init(out, q_seg->len);\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (c == '=') {\n+\t\t\tint ch, d = *in;\n+\t\t\tif (d == '\\n' || !d)\n+\t\t\t\tbreak; /* drop trailing newline */\n+\t\t\tch = hex2chr(in);\n+\t\t\tif (ch >= 0) {\n+\t\t\t\tstrbuf_addch(out, ch);\n+\t\t\t\tin += 2;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* garbage -- fall through */\n+\t\t}\n+\t\tif (rfc2047 && c == '_') /* rfc2047 4.2 (2) */\n+\t\t\tc = 0x20;\n+\t\tstrbuf_addch(out, c);\n+\t}\n+\treturn out;\n+}\n+\n+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)\n+{\n+\t/* Decode in..ep, possibly in-place to ot */\n+\tint c, pos = 0, acc = 0;\n+\tconst char *in = b_seg->buf;\n+\tstruct strbuf *out = xmalloc(sizeof(struct strbuf));\n+\tstrbuf_init(out, b_seg->len);\n+\n+\twhile ((c = *in++) != 0) {\n+\t\tif (c == '+')\n+\t\t\tc = 62;\n+\t\telse if (c == '/')\n+\t\t\tc = 63;\n+\t\telse if ('A' <= c && c <= 'Z')\n+\t\t\tc -= 'A';\n+\t\telse if ('a' <= c && c <= 'z')\n+\t\t\tc -= 'a' - 26;\n+\t\telse if ('0' <= c && c <= '9')\n+\t\t\tc -= '0' - 52;\n+\t\telse\n+\t\t\tcontinue; /* garbage */\n+\t\tswitch (pos++) {\n+\t\tcase 0:\n+\t\t\tacc = (c << 2);\n+\t\t\tbreak;\n+\t\tcase 1:\n+\t\t\tstrbuf_addch(out, (acc | (c >> 4)));\n+\t\t\tacc = (c & 15) << 4;\n+\t\t\tbreak;\n+\t\tcase 2:\n+\t\t\tstrbuf_addch(out, (acc | (c >> 2)));\n+\t\t\tacc = (c & 3) << 6;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\tstrbuf_addch(out, (acc | c));\n+\t\t\tacc = pos = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn out;\n+}\n+\n+static int convert_to_utf8(struct mailinfo *mi,\n+\t\t\t   struct strbuf *line, const char *charset)\n+{\n+\tchar *out;\n+\n+\tif (!mi->metainfo_charset || !charset || !*charset)\n+\t\treturn 0;\n+\n+\tif (same_encoding(mi->metainfo_charset, charset))\n+\t\treturn 0;\n+\tout = reencode_string(line->buf, mi->metainfo_charset, charset);\n+\tif (!out) {\n+\t\tmi->input_error = -1;\n+\t\treturn error(\"cannot convert from %s to %s\",\n+\t\t\t     charset, mi->metainfo_charset);\n+\t}\n+\tstrbuf_attach(line, out, strlen(out), strlen(out));\n+\treturn 0;\n+}\n+\n+static void decode_header(struct mailinfo *mi, struct strbuf *it)\n+{\n+\tchar *in, *ep, *cp;\n+\tstruct strbuf outbuf = STRBUF_INIT, *dec;\n+\tstruct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;\n+\tint found_error = 1; /* pessimism */\n+\n+\tin = it->buf;\n+\twhile (in - it->buf <= it->len && (ep = strstr(in, \"=?\")) != NULL) {\n+\t\tint encoding;\n+\t\tstrbuf_reset(&charset_q);\n+\t\tstrbuf_reset(&piecebuf);\n+\n+\t\tif (in != ep) {\n+\t\t\t/*\n+\t\t\t * We are about to process an encoded-word\n+\t\t\t * that begins at ep, but there is something\n+\t\t\t * before the encoded word.\n+\t\t\t */\n+\t\t\tchar *scan;\n+\t\t\tfor (scan = in; scan < ep; scan++)\n+\t\t\t\tif (!isspace(*scan))\n+\t\t\t\t\tbreak;\n+\n+\t\t\tif (scan != ep || in == it->buf) {\n+\t\t\t\t/*\n+\t\t\t\t * We should not lose that \"something\",\n+\t\t\t\t * unless we have just processed an\n+\t\t\t\t * encoded-word, and there is only LWS\n+\t\t\t\t * before the one we are about to process.\n+\t\t\t\t */\n+\t\t\t\tstrbuf_add(&outbuf, in, ep - in);\n+\t\t\t}\n+\t\t}\n+\t\t/* E.g.\n+\t\t * ep : \"=?iso-2022-jp?B?GyR...?= foo\"\n+\t\t * ep : \"=?ISO-8859-1?Q?Foo=FCbar?= baz\"\n+\t\t */\n+\t\tep += 2;\n+\n+\t\tif (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))\n+\t\t\tgoto release_return;\n+\n+\t\tif (cp + 3 - it->buf > it->len)\n+\t\t\tgoto release_return;\n+\t\tstrbuf_add(&charset_q, ep, cp - ep);\n+\n+\t\tencoding = cp[1];\n+\t\tif (!encoding || cp[2] != '?')\n+\t\t\tgoto release_return;\n+\t\tep = strstr(cp + 3, \"?=\");\n+\t\tif (!ep)\n+\t\t\tgoto release_return;\n+\t\tstrbuf_add(&piecebuf, cp + 3, ep - cp - 3);\n+\t\tswitch (tolower(encoding)) {\n+\t\tdefault:\n+\t\t\tgoto release_return;\n+\t\tcase 'b':\n+\t\t\tdec = decode_b_segment(&piecebuf);\n+\t\t\tbreak;\n+\t\tcase 'q':\n+\t\t\tdec = decode_q_segment(&piecebuf, 1);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (convert_to_utf8(mi, dec, charset_q.buf))\n+\t\t\tgoto release_return;\n+\n+\t\tstrbuf_addbuf(&outbuf, dec);\n+\t\tstrbuf_release(dec);\n+\t\tfree(dec);\n+\t\tin = ep + 2;\n+\t}\n+\tstrbuf_addstr(&outbuf, in);\n+\tstrbuf_reset(it);\n+\tstrbuf_addbuf(it, &outbuf);\n+\tfound_error = 0;\n+release_return:\n+\tstrbuf_release(&outbuf);\n+\tstrbuf_release(&charset_q);\n+\tstrbuf_release(&piecebuf);\n+\n+\tif (found_error)\n+\t\tmi->input_error = -1;\n+}\n+\n+static int check_header(struct mailinfo *mi,\n+\t\t\tconst struct strbuf *line,\n+\t\t\tstruct strbuf *hdr_data[], int overwrite)\n+{\n+\tint i, ret = 0, len;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t/* search for the interesting parts */\n+\tfor (i = 0; header[i]; i++) {\n+\t\tint len = strlen(header[i]);\n+\t\tif ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {\n+\t\t\t/* Unwrap inline B and Q encoding, and optionally\n+\t\t\t * normalize the meta information to utf8.\n+\t\t\t */\n+\t\t\tstrbuf_add(&sb, line->buf + len + 2, line->len - len - 2);\n+\t\t\tdecode_header(mi, &sb);\n+\t\t\thandle_header(&hdr_data[i], &sb);\n+\t\t\tret = 1;\n+\t\t\tgoto check_header_out;\n+\t\t}\n+\t}\n+\n+\t/* Content stuff */\n+\tif (cmp_header(line, \"Content-Type\")) {\n+\t\tlen = strlen(\"Content-Type: \");\n+\t\tstrbuf_add(&sb, line->buf + len, line->len - len);\n+\t\tdecode_header(mi, &sb);\n+\t\tstrbuf_insert(&sb, 0, \"Content-Type: \", len);\n+\t\thandle_content_type(mi, &sb);\n+\t\tret = 1;\n+\t\tgoto check_header_out;\n+\t}\n+\tif (cmp_header(line, \"Content-Transfer-Encoding\")) {\n+\t\tlen = strlen(\"Content-Transfer-Encoding: \");\n+\t\tstrbuf_add(&sb, line->buf + len, line->len - len);\n+\t\tdecode_header(mi, &sb);\n+\t\thandle_content_transfer_encoding(mi, &sb);\n+\t\tret = 1;\n+\t\tgoto check_header_out;\n+\t}\n+\tif (cmp_header(line, \"Message-Id\")) {\n+\t\tlen = strlen(\"Message-Id: \");\n+\t\tstrbuf_add(&sb, line->buf + len, line->len - len);\n+\t\tdecode_header(mi, &sb);\n+\t\tif (mi->add_message_id)\n+\t\t\tmi->message_id = strbuf_detach(&sb, NULL);\n+\t\tret = 1;\n+\t\tgoto check_header_out;\n+\t}\n+\n+check_header_out:\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n+/*\n+ * Returns 1 if the given line or any line beginning with the given line is an\n+ * in-body header (that is, check_header will succeed when passed\n+ * mi->s_hdr_data).\n+ */\n+static int is_inbody_header(const struct mailinfo *mi,\n+\t\t\t    const struct strbuf *line)\n+{\n+\tint i;\n+\tfor (i = 0; header[i]; i++)\n+\t\tif (!mi->s_hdr_data[i] && cmp_header(line, header[i]))\n+\t\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf *ret;\n+\n+\tswitch (mi->transfer_encoding) {\n+\tcase TE_QP:\n+\t\tret = decode_q_segment(line, 0);\n+\t\tbreak;\n+\tcase TE_BASE64:\n+\t\tret = decode_b_segment(line);\n+\t\tbreak;\n+\tcase TE_DONTCARE:\n+\tdefault:\n+\t\treturn;\n+\t}\n+\tstrbuf_reset(line);\n+\tstrbuf_addbuf(line, ret);\n+\tstrbuf_release(ret);\n+\tfree(ret);\n+}\n+\n+static inline int patchbreak(const struct strbuf *line)\n+{\n+\tsize_t i;\n+\n+\t/* Beginning of a \"diff -\" header? */\n+\tif (starts_with(line->buf, \"diff -\"))\n+\t\treturn 1;\n+\n+\t/* CVS \"Index: \" line? */\n+\tif (starts_with(line->buf, \"Index: \"))\n+\t\treturn 1;\n+\n+\t/*\n+\t * \"--- <filename>\" starts patches without headers\n+\t * \"---<sp>*\" is a manual separator\n+\t */\n+\tif (line->len < 4)\n+\t\treturn 0;\n+\n+\tif (starts_with(line->buf, \"---\")) {\n+\t\t/* space followed by a filename? */\n+\t\tif (line->buf[3] == ' ' && !isspace(line->buf[4]))\n+\t\t\treturn 1;\n+\t\t/* Just whitespace? */\n+\t\tfor (i = 3; i < line->len; i++) {\n+\t\t\tunsigned char c = line->buf[i];\n+\t\t\tif (c == '\\n')\n+\t\t\t\treturn 1;\n+\t\t\tif (!isspace(c))\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n+static int is_scissors_line(const char *line)\n+{\n+\tconst char *c;\n+\tint scissors = 0, gap = 0;\n+\tconst char *first_nonblank = NULL, *last_nonblank = NULL;\n+\tint visible, perforation = 0, in_perforation = 0;\n+\n+\tfor (c = line; *c; c++) {\n+\t\tif (isspace(*c)) {\n+\t\t\tif (in_perforation) {\n+\t\t\t\tperforation++;\n+\t\t\t\tgap++;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\tlast_nonblank = c;\n+\t\tif (first_nonblank == NULL)\n+\t\t\tfirst_nonblank = c;\n+\t\tif (*c == '-') {\n+\t\t\tin_perforation = 1;\n+\t\t\tperforation++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif ((!memcmp(c, \">8\", 2) || !memcmp(c, \"8<\", 2) ||\n+\t\t     !memcmp(c, \">%\", 2) || !memcmp(c, \"%<\", 2))) {\n+\t\t\tin_perforation = 1;\n+\t\t\tperforation += 2;\n+\t\t\tscissors += 2;\n+\t\t\tc++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tin_perforation = 0;\n+\t}\n+\n+\t/*\n+\t * The mark must be at least 8 bytes long (e.g. \"-- >8 --\").\n+\t * Even though there can be arbitrary cruft on the same line\n+\t * (e.g. \"cut here\"), in order to avoid misidentification, the\n+\t * perforation must occupy more than a third of the visible\n+\t * width of the line, and dashes and scissors must occupy more\n+\t * than half of the perforation.\n+\t */\n+\n+\tif (first_nonblank && last_nonblank)\n+\t\tvisible = last_nonblank - first_nonblank + 1;\n+\telse\n+\t\tvisible = 0;\n+\treturn (scissors && 8 <= visible &&\n+\t\tvisible < perforation * 3 &&\n+\t\tgap * 2 < perforation);\n+}\n+\n+static void flush_inbody_header_accum(struct mailinfo *mi)\n+{\n+\tif (!mi->inbody_header_accum.len)\n+\t\treturn;\n+\tif (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))\n+\t\tBUG(\"inbody_header_accum, if not empty, must always contain a valid in-body header\");\n+\tstrbuf_reset(&mi->inbody_header_accum);\n+}\n+\n+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\tif (mi->inbody_header_accum.len &&\n+\t    (line->buf[0] == ' ' || line->buf[0] == '\\t')) {\n+\t\tif (mi->use_scissors && is_scissors_line(line->buf)) {\n+\t\t\t/*\n+\t\t\t * This is a scissors line; do not consider this line\n+\t\t\t * as a header continuation line.\n+\t\t\t */\n+\t\t\tflush_inbody_header_accum(mi);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tstrbuf_strip_suffix(&mi->inbody_header_accum, \"\\n\");\n+\t\tstrbuf_addbuf(&mi->inbody_header_accum, line);\n+\t\treturn 1;\n+\t}\n+\n+\tflush_inbody_header_accum(mi);\n+\n+\tif (starts_with(line->buf, \">From\") && isspace(line->buf[5]))\n+\t\treturn is_format_patch_separator(line->buf + 1, line->len - 1);\n+\tif (starts_with(line->buf, \"[PATCH]\") && isspace(line->buf[7])) {\n+\t\tint i;\n+\t\tfor (i = 0; header[i]; i++)\n+\t\t\tif (!strcmp(\"Subject\", header[i])) {\n+\t\t\t\thandle_header(&mi->s_hdr_data[i], line);\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\treturn 0;\n+\t}\n+\tif (is_inbody_header(mi, line)) {\n+\t\tstrbuf_addbuf(&mi->inbody_header_accum, line);\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tassert(!mi->filter_stage);\n+\n+\tif (mi->header_stage) {\n+\t\tif (!line->len || (line->len == 1 && line->buf[0] == '\\n')) {\n+\t\t\tif (mi->inbody_header_accum.len) {\n+\t\t\t\tflush_inbody_header_accum(mi);\n+\t\t\t\tmi->header_stage = 0;\n+\t\t\t}\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (mi->use_inbody_headers && mi->header_stage) {\n+\t\tmi->header_stage = check_inbody_header(mi, line);\n+\t\tif (mi->header_stage)\n+\t\t\treturn 0;\n+\t} else\n+\t\t/* Only trim the first (blank) line of the commit message\n+\t\t * when ignoring in-body headers.\n+\t\t */\n+\t\tmi->header_stage = 0;\n+\n+\t/* normalize the log message to UTF-8. */\n+\tif (convert_to_utf8(mi, line, mi->charset.buf))\n+\t\treturn 0; /* mi->input_error already set */\n+\n+\tif (mi->use_scissors && is_scissors_line(line->buf)) {\n+\t\tint i;\n+\n+\t\tstrbuf_setlen(&mi->log_message, 0);\n+\t\tmi->header_stage = 1;\n+\n+\t\t/*\n+\t\t * We may have already read \"secondary headers\"; purge\n+\t\t * them to give ourselves a clean restart.\n+\t\t */\n+\t\tfor (i = 0; header[i]; i++) {\n+\t\t\tif (mi->s_hdr_data[i])\n+\t\t\t\tstrbuf_release(mi->s_hdr_data[i]);\n+\t\t\tmi->s_hdr_data[i] = NULL;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n+\tif (patchbreak(line)) {\n+\t\tif (mi->message_id)\n+\t\t\tstrbuf_addf(&mi->log_message,\n+\t\t\t\t    \"Message-Id: %s\\n\", mi->message_id);\n+\t\treturn 1;\n+\t}\n+\n+\tstrbuf_addbuf(&mi->log_message, line);\n+\treturn 0;\n+}\n+\n+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)\n+{\n+\tfwrite(line->buf, 1, line->len, mi->patchfile);\n+\tmi->patch_lines++;\n+}\n+\n+static void handle_filter(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tswitch (mi->filter_stage) {\n+\tcase 0:\n+\t\tif (!handle_commit_msg(mi, line))\n+\t\t\tbreak;\n+\t\tmi->filter_stage++;\n+\t\t/* fallthrough */\n+\tcase 1:\n+\t\thandle_patch(mi, line);\n+\t\tbreak;\n+\t}\n+}\n+\n+static int is_rfc2822_header(const struct strbuf *line)\n+{\n+\t/*\n+\t * The section that defines the loosest possible\n+\t * field name is \"3.6.8 Optional fields\".\n+\t *\n+\t * optional-field = field-name \":\" unstructured CRLF\n+\t * field-name = 1*ftext\n+\t * ftext = %d33-57 / %59-126\n+\t */\n+\tint ch;\n+\tchar *cp = line->buf;\n+\n+\t/* Count mbox From headers as headers */\n+\tif (starts_with(cp, \"From \") || starts_with(cp, \">From \"))\n+\t\treturn 1;\n+\n+\twhile ((ch = *cp++)) {\n+\t\tif (ch == ':')\n+\t\t\treturn 1;\n+\t\tif ((33 <= ch && ch <= 57) ||\n+\t\t    (59 <= ch && ch <= 126))\n+\t\t\tcontinue;\n+\t\tbreak;\n+\t}\n+\treturn 0;\n+}\n+\n+static int read_one_header_line(struct strbuf *line, FILE *in)\n+{\n+\tstruct strbuf continuation = STRBUF_INIT;\n+\n+\t/* Get the first part of the line. */\n+\tif (strbuf_getline_lf(line, in))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Is it an empty line or not a valid rfc2822 header?\n+\t * If so, stop here, and return false (\"not a header\")\n+\t */\n+\tstrbuf_rtrim(line);\n+\tif (!line->len || !is_rfc2822_header(line)) {\n+\t\t/* Re-add the newline */\n+\t\tstrbuf_addch(line, '\\n');\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * Now we need to eat all the continuation lines..\n+\t * Yuck, 2822 header \"folding\"\n+\t */\n+\tfor (;;) {\n+\t\tint peek;\n+\n+\t\tpeek = fgetc(in);\n+\t\tif (peek == EOF)\n+\t\t\tbreak;\n+\t\tungetc(peek, in);\n+\t\tif (peek != ' ' && peek != '\\t')\n+\t\t\tbreak;\n+\t\tif (strbuf_getline_lf(&continuation, in))\n+\t\t\tbreak;\n+\t\tcontinuation.buf[0] = ' ';\n+\t\tstrbuf_rtrim(&continuation);\n+\t\tstrbuf_addbuf(line, &continuation);\n+\t}\n+\tstrbuf_release(&continuation);\n+\n+\treturn 1;\n+}\n+\n+static int find_boundary(struct mailinfo *mi, struct strbuf *line)\n+{\n+\twhile (!strbuf_getline_lf(line, mi->input)) {\n+\t\tif (*(mi->content_top) && is_multipart_boundary(mi, line))\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf newline = STRBUF_INIT;\n+\n+\tstrbuf_addch(&newline, '\\n');\n+again:\n+\tif (line->len >= (*(mi->content_top))->len + 2 &&\n+\t    !memcmp(line->buf + (*(mi->content_top))->len, \"--\", 2)) {\n+\t\t/* we hit an end boundary */\n+\t\t/* pop the current boundary off the stack */\n+\t\tstrbuf_release(*(mi->content_top));\n+\t\tFREE_AND_NULL(*(mi->content_top));\n+\n+\t\t/* technically won't happen as is_multipart_boundary()\n+\t\t   will fail first.  But just in case..\n+\t\t */\n+\t\tif (--mi->content_top < mi->content) {\n+\t\t\terror(\"Detected mismatched boundaries, can't recover\");\n+\t\t\tmi->input_error = -1;\n+\t\t\tmi->content_top = mi->content;\n+\t\t\tstrbuf_release(&newline);\n+\t\t\treturn 0;\n+\t\t}\n+\t\thandle_filter(mi, &newline);\n+\t\tstrbuf_release(&newline);\n+\t\tif (mi->input_error)\n+\t\t\treturn 0;\n+\n+\t\t/* skip to the next boundary */\n+\t\tif (!find_boundary(mi, line))\n+\t\t\treturn 0;\n+\t\tgoto again;\n+\t}\n+\n+\t/* set some defaults */\n+\tmi->transfer_encoding = TE_DONTCARE;\n+\tstrbuf_reset(&mi->charset);\n+\n+\t/* slurp in this section's info */\n+\twhile (read_one_header_line(line, mi->input))\n+\t\tcheck_header(mi, line, mi->p_hdr_data, 0);\n+\n+\tstrbuf_release(&newline);\n+\t/* replenish line */\n+\tif (strbuf_getline_lf(line, mi->input))\n+\t\treturn 0;\n+\tstrbuf_addch(line, '\\n');\n+\treturn 1;\n+}\n+\n+static void handle_body(struct mailinfo *mi, struct strbuf *line)\n+{\n+\tstruct strbuf prev = STRBUF_INIT;\n+\n+\t/* Skip up to the first boundary */\n+\tif (*(mi->content_top)) {\n+\t\tif (!find_boundary(mi, line))\n+\t\t\tgoto handle_body_out;\n+\t}\n+\n+\tdo {\n+\t\t/* process any boundary lines */\n+\t\tif (*(mi->content_top) && is_multipart_boundary(mi, line)) {\n+\t\t\t/* flush any leftover */\n+\t\t\tif (prev.len) {\n+\t\t\t\thandle_filter(mi, &prev);\n+\t\t\t\tstrbuf_reset(&prev);\n+\t\t\t}\n+\t\t\tif (!handle_boundary(mi, line))\n+\t\t\t\tgoto handle_body_out;\n+\t\t}\n+\n+\t\t/* Unwrap transfer encoding */\n+\t\tdecode_transfer_encoding(mi, line);\n+\n+\t\tswitch (mi->transfer_encoding) {\n+\t\tcase TE_BASE64:\n+\t\tcase TE_QP:\n+\t\t{\n+\t\t\tstruct strbuf **lines, **it, *sb;\n+\n+\t\t\t/* Prepend any previous partial lines */\n+\t\t\tstrbuf_insert(line, 0, prev.buf, prev.len);\n+\t\t\tstrbuf_reset(&prev);\n+\n+\t\t\t/*\n+\t\t\t * This is a decoded line that may contain\n+\t\t\t * multiple new lines.  Pass only one chunk\n+\t\t\t * at a time to handle_filter()\n+\t\t\t */\n+\t\t\tlines = strbuf_split(line, '\\n');\n+\t\t\tfor (it = lines; (sb = *it); it++) {\n+\t\t\t\tif (*(it + 1) == NULL) /* The last line */\n+\t\t\t\t\tif (sb->buf[sb->len - 1] != '\\n') {\n+\t\t\t\t\t\t/* Partial line, save it for later. */\n+\t\t\t\t\t\tstrbuf_addbuf(&prev, sb);\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\thandle_filter(mi, sb);\n+\t\t\t}\n+\t\t\t/*\n+\t\t\t * The partial chunk is saved in \"prev\" and will be\n+\t\t\t * appended by the next iteration of read_line_with_nul().\n+\t\t\t */\n+\t\t\tstrbuf_list_free(lines);\n+\t\t\tbreak;\n+\t\t}\n+\t\tdefault:\n+\t\t\thandle_filter(mi, line);\n+\t\t}\n+\n+\t\tif (mi->input_error)\n+\t\t\tbreak;\n+\t} while (!strbuf_getwholeline(line, mi->input, '\\n'));\n+\n+\tflush_inbody_header_accum(mi);\n+\n+handle_body_out:\n+\tstrbuf_release(&prev);\n+}\n+\n+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)\n+{\n+\tconst char *sp = data->buf;\n+\twhile (1) {\n+\t\tchar *ep = strchr(sp, '\\n');\n+\t\tint len;\n+\t\tif (!ep)\n+\t\t\tlen = strlen(sp);\n+\t\telse\n+\t\t\tlen = ep - sp;\n+\t\tfprintf(fout, \"%s: %.*s\\n\", hdr, len, sp);\n+\t\tif (!ep)\n+\t\t\tbreak;\n+\t\tsp = ep + 1;\n+\t}\n+}\n+\n+static void handle_info(struct mailinfo *mi)\n+{\n+\tstruct strbuf *hdr;\n+\tint i;\n+\n+\tfor (i = 0; header[i]; i++) {\n+\t\t/* only print inbody headers if we output a patch file */\n+\t\tif (mi->patch_lines && mi->s_hdr_data[i])\n+\t\t\thdr = mi->s_hdr_data[i];\n+\t\telse if (mi->p_hdr_data[i])\n+\t\t\thdr = mi->p_hdr_data[i];\n+\t\telse\n+\t\t\tcontinue;\n+\n+\t\tif (!strcmp(header[i], \"Subject\")) {\n+\t\t\tif (!mi->keep_subject) {\n+\t\t\t\tcleanup_subject(mi, hdr);\n+\t\t\t\tcleanup_space(hdr);\n+\t\t\t}\n+\t\t\toutput_header_lines(mi->output, \"Subject\", hdr);\n+\t\t} else if (!strcmp(header[i], \"From\")) {\n+\t\t\tcleanup_space(hdr);\n+\t\t\thandle_from(mi, hdr);\n+\t\t\tfprintf(mi->output, \"Author: %s\\n\", mi->name.buf);\n+\t\t\tfprintf(mi->output, \"Email: %s\\n\", mi->email.buf);\n+\t\t} else {\n+\t\t\tcleanup_space(hdr);\n+\t\t\tfprintf(mi->output, \"%s: %s\\n\", header[i], hdr->buf);\n+\t\t}\n+\t}\n+\tfprintf(mi->output, \"\\n\");\n+}\n+\n+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n+{\n+\tFILE *cmitmsg;\n+\tint peek;\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\tcmitmsg = fopen(msg, \"w\");\n+\tif (!cmitmsg) {\n+\t\tperror(msg);\n+\t\treturn -1;\n+\t}\n+\tmi->patchfile = fopen(patch, \"w\");\n+\tif (!mi->patchfile) {\n+\t\tperror(patch);\n+\t\tfclose(cmitmsg);\n+\t\treturn -1;\n+\t}\n+\n+\tmi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));\n+\tmi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));\n+\n+\tdo {\n+\t\tpeek = fgetc(mi->input);\n+\t\tif (peek == EOF) {\n+\t\t\tfclose(cmitmsg);\n+\t\t\treturn error(\"empty patch: '%s'\", patch);\n+\t\t}\n+\t} while (isspace(peek));\n+\tungetc(peek, mi->input);\n+\n+\t/* process the email header */\n+\twhile (read_one_header_line(&line, mi->input))\n+\t\tcheck_header(mi, &line, mi->p_hdr_data, 1);\n+\n+\thandle_body(mi, &line);\n+\tfwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);\n+\tfclose(cmitmsg);\n+\tfclose(mi->patchfile);\n+\n+\thandle_info(mi);\n+\tstrbuf_release(&line);\n+\treturn mi->input_error;\n+}\n+\n+static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+{\n+\tstruct mailinfo *mi = mi_;\n+\n+\tif (!starts_with(var, \"mailinfo.\"))\n+\t\treturn git_default_config(var, value, NULL);\n+\tif (!strcmp(var, \"mailinfo.scissors\")) {\n+\t\tmi->use_scissors = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\t/* perhaps others here */\n+\treturn 0;\n+}\n+\n+void setup_mailinfo(struct mailinfo *mi)\n+{\n+\tmemset(mi, 0, sizeof(*mi));\n+\tstrbuf_init(&mi->name, 0);\n+\tstrbuf_init(&mi->email, 0);\n+\tstrbuf_init(&mi->charset, 0);\n+\tstrbuf_init(&mi->log_message, 0);\n+\tstrbuf_init(&mi->inbody_header_accum, 0);\n+\tmi->header_stage = 1;\n+\tmi->use_inbody_headers = 1;\n+\tmi->content_top = mi->content;\n+\tgit_config(git_mailinfo_config, mi);\n+}\n+\n+void clear_mailinfo(struct mailinfo *mi)\n+{\n+\tint i;\n+\n+\tstrbuf_release(&mi->name);\n+\tstrbuf_release(&mi->email);\n+\tstrbuf_release(&mi->charset);\n+\tstrbuf_release(&mi->inbody_header_accum);\n+\tfree(mi->message_id);\n+\n+\tif (mi->p_hdr_data)\n+\t\tfor (i = 0; mi->p_hdr_data[i]; i++)\n+\t\t\tstrbuf_release(mi->p_hdr_data[i]);\n+\tfree(mi->p_hdr_data);\n+\tif (mi->s_hdr_data)\n+\t\tfor (i = 0; mi->s_hdr_data[i]; i++)\n+\t\t\tstrbuf_release(mi->s_hdr_data[i]);\n+\tfree(mi->s_hdr_data);\n+\n+\twhile (mi->content < mi->content_top) {\n+\t\tfree(*(mi->content_top));\n+\t\tmi->content_top--;\n+\t}\n+\n+\tstrbuf_release(&mi->log_message);\n+}\ndiff --git a/t/t4256/1/patch b/t/t4256/1/patch\nnew file mode 100644\nindex 0000000000..bd0d8b0251\n--- /dev/null\n+++ b/t/t4256/1/patch\n@@ -0,0 +1,129 @@\n+From: A <author@example.com>\n+Subject: [PATCH] mailinfo: support format=flowed\n+Message-ID: <aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@example.com>\n+Date: Sat, 25 Aug 2018 22:04:50 +0200\n+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101\n+ Thunderbird/60.0\n+MIME-Version: 1.0\n+Content-Type: text/plain; charset=utf-8; format=flowed\n+Content-Language: en-US\n+Content-Transfer-Encoding: 7bit\n+\n+---\n+  mailinfo.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n+  1 file changed, 62 insertions(+), 2 deletions(-)\n+\n+diff --git a/mailinfo.c b/mailinfo.c\n+index 3281a37d51..b395adbdf2 100644\n+--- a/mailinfo.c\n++++ b/mailinfo.c\n+@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char \n+*name, struct strbuf *attr)\n+  \treturn 1;\n+  }\n+\n++static int has_attr_value(const char *line, const char *name, const \n+char *value)\n++{\n++\tstruct strbuf sb = STRBUF_INIT;\n++\tint rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);\n++\tstrbuf_release(&sb);\n++\treturn rc;\n++}\n++\n+  static void handle_content_type(struct mailinfo *mi, struct strbuf *line)\n+  {\n+  \tstruct strbuf *boundary = xmalloc(sizeof(struct strbuf));\n+  \tstrbuf_init(boundary, line->len);\n+\n++\tmi->format_flowed = has_attr_value(line->buf, \"format=\", \"flowed\");\n++\tmi->delsp = has_attr_value(line->buf, \"delsp=\", \"yes\");\n++\n+  \tif (slurp_attr(line->buf, \"boundary=\", boundary)) {\n+  \t\tstrbuf_insert(boundary, 0, \"--\", 2);\n+  \t\tif (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {\n+@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi, \n+struct strbuf *line)\n+  \treturn 1;\n+  }\n+\n++static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n++\t\t\t\t struct strbuf *prev)\n++{\n++\tsize_t len = line->len;\n++\tconst char *rest;\n++\n++\tif (!mi->format_flowed) {\n++\t\thandle_filter(mi, line);\n++\t\treturn;\n++\t}\n++\n++\tif (line->buf[len - 1] == '\\n') {\n++\t\tlen--;\n++\t\tif (len && line->buf[len - 1] == '\\r')\n++\t\t\tlen--;\n++\t}\n++\n++\t/* Keep signature separator as-is. */\n++\tif (skip_prefix(line->buf, \"-- \", &rest) && rest - line->buf == len) {\n++\t\tif (prev->len) {\n++\t\t\thandle_filter(mi, prev);\n++\t\t\tstrbuf_reset(prev);\n++\t\t}\n++\t\thandle_filter(mi, line);\n++\t\treturn;\n++\t}\n++\n++\t/* Unstuff space-stuffed line. */\n++\tif (len && line->buf[0] == ' ') {\n++\t\tstrbuf_remove(line, 0, 1);\n++\t\tlen--;\n++\t}\n++\n++\t/* Save flowed line for later, but without the soft line break. */\n++\tif (len && line->buf[len - 1] == ' ') {\n++\t\tstrbuf_add(prev, line->buf, len - !!mi->delsp);\n++\t\treturn;\n++\t}\n++\n++\t/* Prepend any previous partial lines */\n++\tstrbuf_insert(line, 0, prev->buf, prev->len);\n++\tstrbuf_reset(prev);\n++\n++\thandle_filter(mi, line);\n++}\n++\n+  static void handle_body(struct mailinfo *mi, struct strbuf *line)\n+  {\n+  \tstruct strbuf prev = STRBUF_INIT;\n+@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi, \n+struct strbuf *line)\n+  \t\t\t\t\t\tstrbuf_addbuf(&prev, sb);\n+  \t\t\t\t\t\tbreak;\n+  \t\t\t\t\t}\n+-\t\t\t\thandle_filter(mi, sb);\n++\t\t\t\thandle_filter_flowed(mi, sb, &prev);\n+  \t\t\t}\n+  \t\t\t/*\n+  \t\t\t * The partial chunk is saved in \"prev\" and will be\n+@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi, \n+struct strbuf *line)\n+  \t\t\tbreak;\n+  \t\t}\n+  \t\tdefault:\n+-\t\t\thandle_filter(mi, line);\n++\t\t\thandle_filter_flowed(mi, line, &prev);\n+  \t\t}\n+\n+  \t\tif (mi->input_error)\n+  \t\t\tbreak;\n+  \t} while (!strbuf_getwholeline(line, mi->input, '\\n'));\n+\n++\tif (prev.len)\n++\t\thandle_filter(mi, &prev);\n++\n+  \tflush_inbody_header_accum(mi);\n+\n+  handle_body_out:\n+-- \n+2.18.0\n-- \n2.18.0\n"},{"id":"356907","messageId":"xmqqo9dk94vh.fsf@gitster-ct.c.googlers.com","threadId":"49213","inReplyTo":"e2f2ca18-849c-0ef4-98a5-9a1379bfcec5@web.de","subject":"Re: [PATCH] mailinfo: support format=flowed","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-29T20:19:16Z","receivedAt":"2018-08-29T22:37:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> Add best-effort support for patches sent using format=flowed (RFC 3676).\n> Remove leading spaces (\"unstuff\"), remove soft line breaks (indicated\n> by space + newline), but leave the signature separator (dash dash space\n> newline) alone.\n>\n> Warn in git am when encountering a format=flowed patch, because any\n> trailing spaces would most probably be lost, as the sending MUA is\n> encouraged to remove them when preparing the email.\n\nThe warning is a very good idea, but I wonder if it is loud enough\nwhen mixed with other noise (e.g. \"--whitespace=warn\" for a short\nseries, or patch titles when applying a very long series).\n\nLossage of trailing spaces may even be a feature (just joking), when\nthe project policy makes it OK to use \"am --whitespace=fix\".  I\nusually use \"am --whitespace=fix\" but I used \"am --whitespace=warn\"\nwhile applying this patch to preserve lines that begin with \"SP SP\nHT\" in the sample patch.\n\n"},{"id":"357402","messageId":"20180904231959.GB34268@aiede.svl.corp.google.com","threadId":"49213","inReplyTo":"e2f2ca18-849c-0ef4-98a5-9a1379bfcec5@web.de","subject":"Re: [PATCH] mailinfo: support format=flowed","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-09-04T23:19:59Z","receivedAt":"2018-09-04T23:20:05Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nRené Scharfe wrote:\n\n>  builtin/am.c                |    4 +\n>  mailinfo.c                  |   64 +-\n>  mailinfo.h                  |    2 +\n>  t/t4256-am-format-flowed.sh |   19 +\n>  t/t4256/1/mailinfo.c        | 1245 +++++++++++++++++++++++++++++++++++\n>  t/t4256/1/mailinfo.c.orig   | 1185 +++++++++++++++++++++++++++++++++\n\nThis mailinfo.c.orig file appears to have been lost when applying the\npatch to git.git, resulting in test failures:\n\n $ ./t4256-am-format-flowed.sh -v -i\n Initialized empty Git repository in git/t/trash directory.t4256-am-format-flowed/.git/\n expecting success: \n        cp \"$TEST_DIRECTORY/t4256/1/mailinfo.c.orig\" mailinfo.c &&\n        git add mailinfo.c &&\n        git commit -m initial\n\n cp: cannot stat 'git/t/t4256/1/mailinfo.c.orig': No such file or directory\n not ok 1 - setup\n #\n #               cp \"$TEST_DIRECTORY/t4256/1/mailinfo.c.orig\" mailinfo.c &&\n #               git add mailinfo.c &&\n #               git commit -m initial\n #\n\nKnown issue?\n\nThanks,\nJonathan\n"},{"id":"357403","messageId":"20180904232542.GC34268@aiede.svl.corp.google.com","threadId":"49213","inReplyTo":"20180904231959.GB34268@aiede.svl.corp.google.com","subject":"Re: [PATCH] mailinfo: support format=flowed","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-09-04T23:25:42Z","receivedAt":"2018-09-04T23:25:48Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n> René Scharfe wrote:\n\n>>  builtin/am.c                |    4 +\n>>  mailinfo.c                  |   64 +-\n>>  mailinfo.h                  |    2 +\n>>  t/t4256-am-format-flowed.sh |   19 +\n>>  t/t4256/1/mailinfo.c        | 1245 +++++++++++++++++++++++++++++++++++\n>>  t/t4256/1/mailinfo.c.orig   | 1185 +++++++++++++++++++++++++++++++++\n>\n> This mailinfo.c.orig file appears to have been lost when applying the\n> patch to git.git, resulting in test failures:\n>\n>  $ ./t4256-am-format-flowed.sh -v -i\n>  Initialized empty Git repository in git/t/trash directory.t4256-am-format-flowed/.git/\n>  expecting success: \n>         cp \"$TEST_DIRECTORY/t4256/1/mailinfo.c.orig\" mailinfo.c &&\n>         git add mailinfo.c &&\n>         git commit -m initial\n>\n>  cp: cannot stat 'git/t/t4256/1/mailinfo.c.orig': No such file or directory\n>  not ok 1 - setup\n>  #\n>  #               cp \"$TEST_DIRECTORY/t4256/1/mailinfo.c.orig\" mailinfo.c &&\n>  #               git add mailinfo.c &&\n>  #               git commit -m initial\n>  #\n>\n> Known issue?\n\nMy bad: this was operator error:\n\n $ git status -s\n  D t/t4256/1/mailinfo.c.orig\n\nThe issue was that I ran dh_clean, which removed the .orig file.  I'll\nfix the issue on my side.  Sorry for the false alarm.\n\nSincerely,\nJonathan\n"}]}