{"thread":{"id":"55533","subject":"[PATCH] mailinfo: strip CR from base64/quoted-printable email","startedAt":"2021-04-21T01:34:19Z","lastAt":"2021-05-09T17:12:37Z","messageCount":35,"participants":["Đoàn Trần Công Danh","Junio C Hamano","brian m. carlson"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"422517","messageId":"20210421013404.17383-1-congdanhqx@gmail.com","threadId":"55533","inReplyTo":null,"subject":"[PATCH] mailinfo: strip CR from base64/quoted-printable email","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-04-21T01:34:04Z","receivedAt":"2021-04-21T01:34:19Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When an SMTP server receives an 8-bit email message, possibly with only\nLF as line ending, some of those servers decide to change said LF to\nCRLF.\n\nSome other SMTP servers, when receives an 8-bit email message, decide to\nencoding such message in base64 and/or quoted-printable instead.\n\nIf an email is transfered through those 2 email servers in order, the\nfinal recipients will receive an email contains a patch mungled with\nCRLF encoded inside another encoding. Thus, such CR couldn't be dropped\nby mailsplit. Such accidents have been observed in the wild [1].\n\nLet's guess if such CR was added automatically and strip them in\nmailinfo.\n\n[1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n\n I'm not sure if guessing the heuristic to strip CR is a good approach.\n I think it's better to pass --keep-cr down from git-am.\n Let's say --keep-cr=<yes|no|auto>\n\n mailinfo.c             | 20 +++++++++++++++++---\n t/t5100-mailinfo.sh    |  5 +++++\n t/t5100/cr-base64.mbox | 22 ++++++++++++++++++++++\n t/t5100/info1000       |  5 +++++\n t/t5100/msg1000        |  2 ++\n t/t5100/patch1000      | 22 ++++++++++++++++++++++\n 6 files changed, 73 insertions(+), 3 deletions(-)\n create mode 100644 t/t5100/cr-base64.mbox\n create mode 100644 t/t5100/info1000\n create mode 100644 t/t5100/msg1000\n create mode 100644 t/t5100/patch1000\n\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 5681d9130d..dbff867f42 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -988,16 +988,27 @@ static int handle_boundary(struct mailinfo *mi, struct strbuf *line)\n }\n \n static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n-\t\t\t\t struct strbuf *prev)\n+\t\t\t\t struct strbuf *prev, int *keep_cr)\n {\n \tsize_t len = line->len;\n \tconst char *rest;\n \n \tif (!mi->format_flowed) {\n+\t\tif (*keep_cr == -1 && len >= 2)\n+\t\t\t*keep_cr = !(line->buf[len - 2] == '\\r' &&\n+\t\t\t\t     line->buf[len - 1] == '\\n');\n+\t\tif (!*keep_cr && len >= 2 &&\n+\t\t    line->buf[len - 2] == '\\r' &&\n+\t\t    line->buf[len - 1] == '\\n') {\n+\t\t\tstrbuf_setlen(line, len - 2);\n+\t\t\tstrbuf_addch(line, '\\n');\n+\t\t\tlen--;\n+\t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n \t}\n \n+\t*keep_cr = 1;\n \tif (line->buf[len - 1] == '\\n') {\n \t\tlen--;\n \t\tif (len && line->buf[len - 1] == '\\r')\n@@ -1036,6 +1047,7 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n static void handle_body(struct mailinfo *mi, struct strbuf *line)\n {\n \tstruct strbuf prev = STRBUF_INIT;\n+\tint keep_cr = -1;\n \n \t/* Skip up to the first boundary */\n \tif (*(mi->content_top)) {\n@@ -1081,7 +1093,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_flowed(mi, sb, &prev);\n+\t\t\t\thandle_filter_flowed(mi, sb, &prev, &keep_cr);\n \t\t\t}\n \t\t\t/*\n \t\t\t * The partial chunk is saved in \"prev\" and will be\n@@ -1091,7 +1103,9 @@ 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_flowed(mi, line, &prev);\n+\t\t\t/* CR in plain message was processed in mailsplit */\n+\t\t\tkeep_cr = 1;\n+\t\t\thandle_filter_flowed(mi, line, &prev, &keep_cr);\n \t\t}\n \n \t\tif (mi->input_error)\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 147e616533..9ccc11d16a 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -228,4 +228,9 @@ test_expect_success 'mailinfo handles unusual header whitespace' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'mailinfo strip CR after decode base64' '\n+\tcp $DATA/cr-base64.mbox 1000 &&\n+\tcheck_mailinfo 1000 \"\"\n+'\n+\n test_done\ndiff --git a/t/t5100/cr-base64.mbox b/t/t5100/cr-base64.mbox\nnew file mode 100644\nindex 0000000000..6ea9806a6b\n--- /dev/null\n+++ b/t/t5100/cr-base64.mbox\n@@ -0,0 +1,22 @@\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+T24gZGlmZmVyZW50IGRpc3RybywgcHl0ZXN0IGlzIHN1ZmZpeGVkIHdpdGggZGlmZmVyZW50IHBh\n+dHRlcm5zLg0KDQotLS0NCiBjb25maWd1cmUgfCAyICstDQogMSBmaWxlIGNoYW5nZWQsIDEgaW5z\n+ZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS9jb25maWd1cmUgYi9jb25m\n+aWd1cmUNCmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUNCi0tLSBhL2NvbmZpZ3VyZQ0K\n+KysrIGIvY29uZmlndXJlDQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAt\n+ZXEgMSBdOyB0aGVuDQogICAgIHByaW50ZiAiQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+\n+PSAzLjApLi4uICINCiAgICAgY29uZj0kKG1rdGVtcCkNCiAgICAgcHJpbnRmICJbcHl0ZXN0XVxu\n+bWludmVyc2lvbj0zLjBcbiIgPiAkY29uZg0KLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZl\n+cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQorICAgIGlmICIkcHl0aG9uIiAtbSBweXRlc3Qg\n+LWMgJGNvbmYgLS12ZXJzaW9uID4vZGV2L251bGwgMj4mMTsgdGhlbg0KICAgICAgICAgcHJpbnRm\n+ICJZZXMuXG4iDQogICAgICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTENCiAgICAgZWxzZQ0KLS0g\n+DQoyLjI4LjANCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\n+CmV4YW1wbGUgbWFpbGluZyBsaXN0IC0tIGxpc3RAZXhhbXBsZS5vcmcKVG8gdW5zdWJzY3JpYmUg\n+c2VuZCBhbiBlbWFpbCB0byBsaXN0LWxlYXZlQGV4YW1wbGUub3JnCg==\ndiff --git a/t/t5100/info1000 b/t/t5100/info1000\nnew file mode 100644\nindex 0000000000..dab2228b70\n--- /dev/null\n+++ b/t/t5100/info1000\n@@ -0,0 +1,5 @@\n+Author: A U Thor\n+Email: mail@example.com\n+Subject: sample\n+Date: Mon, 3 Aug 2020 22:40:55 +0700\n+\ndiff --git a/t/t5100/msg1000 b/t/t5100/msg1000\nnew file mode 100644\nindex 0000000000..5e8e860aae\n--- /dev/null\n+++ b/t/t5100/msg1000\n@@ -0,0 +1,2 @@\n+On different distro, pytest is suffixed with different patterns.\n+\ndiff --git a/t/t5100/patch1000 b/t/t5100/patch1000\nnew file mode 100644\nindex 0000000000..51c4fb4cb5\n--- /dev/null\n+++ b/t/t5100/patch1000\n@@ -0,0 +1,22 @@\n+---\n+ configure | 2 +-\n+ 1 file changed, 1 insertion(+), 1 deletion(-)\n+\n+diff --git a/configure b/configure\n+index db3538b3..f7c1c095 100755\n+--- a/configure\n++++ b/configure\n+@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then\n+     printf \"Checking for python3 pytest (>= 3.0)... \"\n+     conf=$(mktemp)\n+     printf \"[pytest]\\nminversion=3.0\\n\" > $conf\n+-    if pytest-3 -c $conf --version >/dev/null 2>&1; then\n++    if \"$python\" -m pytest -c $conf --version >/dev/null 2>&1; then\n+         printf \"Yes.\\n\"\n+         have_python3_pytest=1\n+     else\n+-- \n+2.28.0\n+_______________________________________________\n+example mailing list -- list@example.org\n+To unsubscribe send an email to list-leave@example.org\n-- \n2.31.1.192.g0881477623\n\n"},{"id":"422518","messageId":"xmqqy2dcz8ve.fsf@gitster.g","threadId":"55533","inReplyTo":"20210421013404.17383-1-congdanhqx@gmail.com","subject":"Re: [PATCH] mailinfo: strip CR from base64/quoted-printable email","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-21T02:09:25Z","receivedAt":"2021-04-21T02:09:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> When an SMTP server receives an 8-bit email message, possibly with only\n> LF as line ending, some of those servers decide to change said LF to\n> CRLF.\n>\n> Some other SMTP servers, when receives an 8-bit email message, decide to\n> encoding such message in base64 and/or quoted-printable instead.\n\nencoding -> encode\n\n>\n> If an email is transfered through those 2 email servers in order, the\n> final recipients will receive an email contains a patch mungled with\n> CRLF encoded inside another encoding. Thus, such CR couldn't be dropped\n> by mailsplit. Such accidents have been observed in the wild [1].\n>\n> Let's guess if such CR was added automatically and strip them in\n> mailinfo.\n>\n> [1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n>\n> Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n> ---\n>\n>  I'm not sure if guessing the heuristic to strip CR is a good approach.\n>  I think it's better to pass --keep-cr down from git-am.\n>  Let's say --keep-cr=<yes|no|auto>\n\nIt matches my instinct to tie this with the existing --keep-cr\noption, even though I admit that I haven't thought things through.\n\n.\n"},{"id":"422521","messageId":"YH+ct4haFn4q5qNB@camp.crustytoothpaste.net","threadId":"55533","inReplyTo":"20210421013404.17383-1-congdanhqx@gmail.com","subject":"Re: [PATCH] mailinfo: strip CR from base64/quoted-printable email","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2021-04-21T03:32:07Z","receivedAt":"2021-04-21T03:32:45Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2021-04-21 at 01:34:04, Đoàn Trần Công Danh wrote:\n> When an SMTP server receives an 8-bit email message, possibly with only\n> LF as line ending, some of those servers decide to change said LF to\n> CRLF.\n> \n> Some other SMTP servers, when receives an 8-bit email message, decide to\n> encoding such message in base64 and/or quoted-printable instead.\n\nThis really isn't an SMTP server.  It's mailing list software, namely\nmailman, and I would argue it's a bug, even though we may want to work\naround it.  For example, re-encoding the message breaks DKIM signatures,\nwhich means that mailman is likely to cause mail to be needlessly\nrejected.\n\n8BITMIME is now so common with SMTP that I'd argue that we should just\nwrite off servers that don't support it (especially in the context of\nSMTPUTF8 existing), but this isn't the case of an SMTP server being\nstuck in the last century.  Can we say more accurately that this is\nmailing list software (or just call it out by name)?\n\n> If an email is transfered through those 2 email servers in order, the\n> final recipients will receive an email contains a patch mungled with\n> CRLF encoded inside another encoding. Thus, such CR couldn't be dropped\n> by mailsplit. Such accidents have been observed in the wild [1].\n> \n> Let's guess if such CR was added automatically and strip them in\n> mailinfo.\n>\n> [1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n> \n> Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n> ---\n> \n>  I'm not sure if guessing the heuristic to strip CR is a good approach.\n>  I think it's better to pass --keep-cr down from git-am.\n>  Let's say --keep-cr=<yes|no|auto>\n\nI think we may want a separate option here.  When I send a 7bit or 8bit\nbody, I expect text canonicalization on the line endings.  However, when\nI send a base64 or quoted-printable body, I don't expect my data to be\nmodified at all, and absent a compelling reason, doing so is incorrect.\nIn most cases, using base64 or quoted-printable is going to mean that\nthe sender knew that the body shouldn't be modified, not that mailman\nmodified it, so we should make line munging in this case opt-in.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"422576","messageId":"YIAVjpdwQhPJbVbl@danh.dev","threadId":"55533","inReplyTo":"YH+ct4haFn4q5qNB@camp.crustytoothpaste.net","subject":"Re: [PATCH] mailinfo: strip CR from base64/quoted-printable email","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-04-21T12:07:42Z","receivedAt":"2021-04-21T12:07:46Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-04-21 03:32:07+0000, \"brian m. carlson\" <sandals@crustytoothpaste.net> wrote:\n> On 2021-04-21 at 01:34:04, Đoàn Trần Công Danh wrote:\n> > When an SMTP server receives an 8-bit email message, possibly with only\n> > LF as line ending, some of those servers decide to change said LF to\n> > CRLF.\n> > \n> > Some other SMTP servers, when receives an 8-bit email message, decide to\n> > encoding such message in base64 and/or quoted-printable instead.\n> \n> This really isn't an SMTP server.  It's mailing list software, namely\n> mailman, and I would argue it's a bug, even though we may want to work\n> around it.  For example, re-encoding the message breaks DKIM signatures,\n> which means that mailman is likely to cause mail to be needlessly\n> rejected.\n> \n> 8BITMIME is now so common with SMTP that I'd argue that we should just\n> write off servers that don't support it (especially in the context of\n> SMTPUTF8 existing), but this isn't the case of an SMTP server being\n> stuck in the last century.  Can we say more accurately that this is\n> mailing list software (or just call it out by name)?\n\nI think replace \"SMTP servers\" with \"mailing list managers\" is\ncorrect. I don't feel comfortable to call it out, since I don't know\nif other managers do it that way or not.\n\n> \n> > If an email is transfered through those 2 email servers in order, the\n> > final recipients will receive an email contains a patch mungled with\n> > CRLF encoded inside another encoding. Thus, such CR couldn't be dropped\n> > by mailsplit. Such accidents have been observed in the wild [1].\n> > \n> > Let's guess if such CR was added automatically and strip them in\n> > mailinfo.\n> >\n> > [1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n> > \n> > Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n> > ---\n> > \n> >  I'm not sure if guessing the heuristic to strip CR is a good approach.\n> >  I think it's better to pass --keep-cr down from git-am.\n> >  Let's say --keep-cr=<yes|no|auto>\n> \n> I think we may want a separate option here.  When I send a 7bit or 8bit\n> body, I expect text canonicalization on the line endings.  However, when\n> I send a base64 or quoted-printable body, I don't expect my data to be\n> modified at all, and absent a compelling reason, doing so is incorrect.\n> In most cases, using base64 or quoted-printable is going to mean that\n> the sender knew that the body shouldn't be modified, not that mailman\n> modified it, so we should make line munging in this case opt-in.\n\nMake sense, this patch was sent mostly for some discussion first.\nWould you mind suggest something for the option.\n\nI'm thinking about --quoted-cr=<nowarn|warn|fix>, mimicking the\n--whitespace option.\n\n-- \nDanh\n"},{"id":"422657","messageId":"YIDNDjJh8vQujkFU@camp.crustytoothpaste.net","threadId":"55533","inReplyTo":"YIAVjpdwQhPJbVbl@danh.dev","subject":"Re: [PATCH] mailinfo: strip CR from base64/quoted-printable email","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2021-04-22T01:10:38Z","receivedAt":"2021-04-22T01:11:16Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2021-04-21 at 12:07:42, Đoàn Trần Công Danh wrote:\n> I think replace \"SMTP servers\" with \"mailing list managers\" is\n> correct. I don't feel comfortable to call it out, since I don't know\n> if other managers do it that way or not.\n\nI think that's fair.  I would hope not for the reasons I mentioned, but\nit also would not be surprising to me if others did nevertheless.\n\n> Make sense, this patch was sent mostly for some discussion first.\n> Would you mind suggest something for the option.\n> \n> I'm thinking about --quoted-cr=<nowarn|warn|fix>, mimicking the\n> --whitespace option.\n\nI think that sounds like a great name.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"423591","messageId":"cover.1620148732.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"20210421013404.17383-1-congdanhqx@gmail.com","subject":"[PATCH v2 0/5] Teach am/mailinfo to process quoted CR","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-04T17:19:57Z","receivedAt":"2021-05-04T17:20:13Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When an SMTP server receives an 8-bit email message, possibly with only\nLF as line ending, some of those servers decide to change said LF to\nCRLF.\n\nSome mailing list software, when receives an 8-bit email message,\ndecide to encoding such message in base64 or quoted-printable.\n\nThis series try to help users of such softwares deal with such patches.\n\nThis series is a complete rewrite of v1.\nHence, no {inter,range}-diff.\n\nSorry for the long delay, life carries me away from Git.\n\n[1]:\nhttps://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n\nĐoàn Trần Công Danh (5):\n  mailinfo: avoid magic number in option parsing\n  mailinfo: warn if CR found in base64/quoted-printable email\n  mailinfo: skip quoted CR on user's wish\n  mailinfo: strip quoted CR on users' wish\n  am: learn to process quoted lines that ends with CRLF\n\n Documentation/git-am.txt       |  4 +++\n Documentation/git-mailinfo.txt | 19 +++++++++++-\n builtin/am.c                   | 56 ++++++++++++++++++++++++++++++++++\n builtin/mailinfo.c             | 13 +++++---\n mailinfo.c                     | 47 ++++++++++++++++++++++++++--\n mailinfo.h                     | 10 ++++++\n t/t4258-am-quoted-cr.sh        | 37 ++++++++++++++++++++++\n t/t4258/mbox                   | 12 ++++++++\n t/t5100-mailinfo.sh            | 21 +++++++++++++\n t/t5100/quoted-cr-info         |  5 +++\n t/t5100/quoted-cr-msg          |  2 ++\n t/t5100/quoted-cr-patch        | 22 +++++++++++++\n t/t5100/quoted-cr.mbox         | 22 +++++++++++++\n 13 files changed, 262 insertions(+), 8 deletions(-)\n create mode 100755 t/t4258-am-quoted-cr.sh\n create mode 100644 t/t4258/mbox\n create mode 100644 t/t5100/quoted-cr-info\n create mode 100644 t/t5100/quoted-cr-msg\n create mode 100644 t/t5100/quoted-cr-patch\n create mode 100644 t/t5100/quoted-cr.mbox\n\n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423592","messageId":"e1e0d8196197d0eb3a39454a48b112a5843d33d7.1620148732.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620148732.git.congdanhqx@gmail.com","subject":"[PATCH v2 1/5] mailinfo: avoid magic number in option parsing","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-04T17:19:58Z","receivedAt":"2021-05-04T17:20:17Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"While current magic number is perfectly correct, in a later change,\nwe would like to parse for another option, namely \"--quoted-cr\".\nLet's refactor a bit to remove that magic number.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n builtin/mailinfo.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex cfb667a594..b309badce5 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -24,6 +24,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tmi.metainfo_charset = def_charset;\n \n \twhile (1 < argc && argv[1][0] == '-') {\n+\t\tconst char *str;\n \t\tif (!strcmp(argv[1], \"-k\"))\n \t\t\tmi.keep_subject = 1;\n \t\telse if (!strcmp(argv[1], \"-b\"))\n@@ -34,8 +35,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \t\t\tmi.metainfo_charset = def_charset;\n \t\telse if (!strcmp(argv[1], \"-n\"))\n \t\t\tmi.metainfo_charset = NULL;\n-\t\telse if (starts_with(argv[1], \"--encoding=\"))\n-\t\t\tmi.metainfo_charset = argv[1] + 11;\n+\t\telse if (skip_prefix(argv[1], \"--encoding=\", &str))\n+\t\t\tmi.metainfo_charset = str;\n \t\telse if (!strcmp(argv[1], \"--scissors\"))\n \t\t\tmi.use_scissors = 1;\n \t\telse if (!strcmp(argv[1], \"--no-scissors\"))\n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423594","messageId":"45d2c4ab58c4b0c6f0c7790890bbf75eb373f999.1620148732.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620148732.git.congdanhqx@gmail.com","subject":"[PATCH v2 2/5] mailinfo: warn if CR found in base64/quoted-printable email","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-04T17:19:59Z","receivedAt":"2021-05-04T17:20:22Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When an SMTP server receives an 8-bit email message, possibly with only\nLF as line ending, some of those servers decide to change said LF to\nCRLF.\n\nSome mailing list softwares, when receives an 8-bit email message,\ndecide to encoding such message in base64 or quoted-printable.\n\nIf an email is transfered through above mail servers, then distributed\nby such mailing list softwares, the recipients will receive an email\ncontains a patch mungled with CRLF encoded inside another encoding.\nThus, such CR couldn't be dropped by mailsplit. Hence, the mailed patch\ncouldn't be applied cleanly. Such accidents have been observed in the wild [1].\n\nLet's give our users some warnings if such CR is found.\n\n[1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n mailinfo.c              | 21 ++++++++++++++++++---\n t/t5100-mailinfo.sh     | 15 +++++++++++++++\n t/t5100/quoted-cr-info  |  5 +++++\n t/t5100/quoted-cr-msg   |  2 ++\n t/t5100/quoted-cr-patch | 22 ++++++++++++++++++++++\n t/t5100/quoted-cr.mbox  | 22 ++++++++++++++++++++++\n 6 files changed, 84 insertions(+), 3 deletions(-)\n create mode 100644 t/t5100/quoted-cr-info\n create mode 100644 t/t5100/quoted-cr-msg\n create mode 100644 t/t5100/quoted-cr-patch\n create mode 100644 t/t5100/quoted-cr.mbox\n\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 5681d9130d..713567f84b 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -988,12 +988,17 @@ static int handle_boundary(struct mailinfo *mi, struct strbuf *line)\n }\n \n static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n-\t\t\t\t struct strbuf *prev)\n+\t\t\t\t struct strbuf *prev, int *have_quoted_cr)\n {\n \tsize_t len = line->len;\n \tconst char *rest;\n \n \tif (!mi->format_flowed) {\n+\t\tif (len >= 2 &&\n+\t\t    line->buf[len - 2] == '\\r' &&\n+\t\t    line->buf[len - 1] == '\\n') {\n+\t\t\t*have_quoted_cr = 1;\n+\t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n \t}\n@@ -1033,9 +1038,16 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \thandle_filter(mi, line);\n }\n \n+static void summarize_quoted_cr(struct mailinfo *mi, int have_quoted_cr)\n+{\n+\tif (have_quoted_cr)\n+\t\twarning(\"quoted CR detected\");\n+}\n+\n static void handle_body(struct mailinfo *mi, struct strbuf *line)\n {\n \tstruct strbuf prev = STRBUF_INIT;\n+\tint have_quoted_cr = 0;\n \n \t/* Skip up to the first boundary */\n \tif (*(mi->content_top)) {\n@@ -1051,6 +1063,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \t\t\t\thandle_filter(mi, &prev);\n \t\t\t\tstrbuf_reset(&prev);\n \t\t\t}\n+\t\t\tsummarize_quoted_cr(mi, have_quoted_cr);\n+\t\t\thave_quoted_cr = 0;\n \t\t\tif (!handle_boundary(mi, line))\n \t\t\t\tgoto handle_body_out;\n \t\t}\n@@ -1081,7 +1095,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_flowed(mi, sb, &prev);\n+\t\t\t\thandle_filter_flowed(mi, sb, &prev, &have_quoted_cr);\n \t\t\t}\n \t\t\t/*\n \t\t\t * The partial chunk is saved in \"prev\" and will be\n@@ -1091,7 +1105,7 @@ 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_flowed(mi, line, &prev);\n+\t\t\thandle_filter_flowed(mi, line, &prev, &have_quoted_cr);\n \t\t}\n \n \t\tif (mi->input_error)\n@@ -1100,6 +1114,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \n \tif (prev.len)\n \t\thandle_filter(mi, &prev);\n+\tsummarize_quoted_cr(mi, have_quoted_cr);\n \n \tflush_inbody_header_accum(mi);\n \ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 147e616533..d8fdda6bea 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -228,4 +228,19 @@ test_expect_success 'mailinfo handles unusual header whitespace' '\n \ttest_cmp expect actual\n '\n \n+check_quoted_cr_mail() {\n+\tgit mailinfo -u \"$@\" quoted-cr-msg quoted-cr-patch \\\n+\t\t<\"$DATA/quoted-cr.mbox\" >quoted-cr-info 2>quoted-cr-err &&\n+\ttest_cmp \"expect-cr-msg\" quoted-cr-msg &&\n+\ttest_cmp \"expect-cr-patch\" quoted-cr-patch &&\n+\ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n+}\n+\n+test_expect_success 'mailinfo warn CR in base64 encoded email' '\n+\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n+\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n+\tcheck_quoted_cr_mail &&\n+\tgrep \"quoted CR detected\" quoted-cr-err\n+'\n+\n test_done\ndiff --git a/t/t5100/quoted-cr-info b/t/t5100/quoted-cr-info\nnew file mode 100644\nindex 0000000000..dab2228b70\n--- /dev/null\n+++ b/t/t5100/quoted-cr-info\n@@ -0,0 +1,5 @@\n+Author: A U Thor\n+Email: mail@example.com\n+Subject: sample\n+Date: Mon, 3 Aug 2020 22:40:55 +0700\n+\ndiff --git a/t/t5100/quoted-cr-msg b/t/t5100/quoted-cr-msg\nnew file mode 100644\nindex 0000000000..a148bc7e26\n--- /dev/null\n+++ b/t/t5100/quoted-cr-msg\n@@ -0,0 +1,2 @@\n+On different distro, pytest is suffixed with different patterns.%%\n+%%\ndiff --git a/t/t5100/quoted-cr-patch b/t/t5100/quoted-cr-patch\nnew file mode 100644\nindex 0000000000..580e2bddb8\n--- /dev/null\n+++ b/t/t5100/quoted-cr-patch\n@@ -0,0 +1,22 @@\n+---%%\n+ configure | 2 +-%%\n+ 1 file changed, 1 insertion(+), 1 deletion(-)%%\n+%%\n+diff --git a/configure b/configure%%\n+index db3538b3..f7c1c095 100755%%\n+--- a/configure%%\n++++ b/configure%%\n+@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%\n+     printf \"Checking for python3 pytest (>= 3.0)... \"%%\n+     conf=$(mktemp)%%\n+     printf \"[pytest]\\nminversion=3.0\\n\" > $conf%%\n+-    if pytest-3 -c $conf --version >/dev/null 2>&1; then%%\n++    if \"$python\" -m pytest -c $conf --version >/dev/null 2>&1; then%%\n+         printf \"Yes.\\n\"%%\n+         have_python3_pytest=1%%\n+     else%%\n+-- %%\n+2.28.0%%\n+_______________________________________________\n+example mailing list -- list@example.org\n+To unsubscribe send an email to list-leave@example.org\ndiff --git a/t/t5100/quoted-cr.mbox b/t/t5100/quoted-cr.mbox\nnew file mode 100644\nindex 0000000000..6ea9806a6b\n--- /dev/null\n+++ b/t/t5100/quoted-cr.mbox\n@@ -0,0 +1,22 @@\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+T24gZGlmZmVyZW50IGRpc3RybywgcHl0ZXN0IGlzIHN1ZmZpeGVkIHdpdGggZGlmZmVyZW50IHBh\n+dHRlcm5zLg0KDQotLS0NCiBjb25maWd1cmUgfCAyICstDQogMSBmaWxlIGNoYW5nZWQsIDEgaW5z\n+ZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS9jb25maWd1cmUgYi9jb25m\n+aWd1cmUNCmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUNCi0tLSBhL2NvbmZpZ3VyZQ0K\n+KysrIGIvY29uZmlndXJlDQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAt\n+ZXEgMSBdOyB0aGVuDQogICAgIHByaW50ZiAiQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+\n+PSAzLjApLi4uICINCiAgICAgY29uZj0kKG1rdGVtcCkNCiAgICAgcHJpbnRmICJbcHl0ZXN0XVxu\n+bWludmVyc2lvbj0zLjBcbiIgPiAkY29uZg0KLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZl\n+cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQorICAgIGlmICIkcHl0aG9uIiAtbSBweXRlc3Qg\n+LWMgJGNvbmYgLS12ZXJzaW9uID4vZGV2L251bGwgMj4mMTsgdGhlbg0KICAgICAgICAgcHJpbnRm\n+ICJZZXMuXG4iDQogICAgICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTENCiAgICAgZWxzZQ0KLS0g\n+DQoyLjI4LjANCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\n+CmV4YW1wbGUgbWFpbGluZyBsaXN0IC0tIGxpc3RAZXhhbXBsZS5vcmcKVG8gdW5zdWJzY3JpYmUg\n+c2VuZCBhbiBlbWFpbCB0byBsaXN0LWxlYXZlQGV4YW1wbGUub3JnCg==\n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423593","messageId":"3215ea95cf869c8495d95cfd774973c330c14d1d.1620148732.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620148732.git.congdanhqx@gmail.com","subject":"[PATCH v2 3/5] mailinfo: skip quoted CR on user's wish","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-04T17:20:00Z","receivedAt":"2021-05-04T17:20:23Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous change, we've turned on warning for quoted CR in base64\nencoded email. Despite those warnings are usually helpful for our users,\nthey may expect quoted CR in their emails.\n\nLet's give them an option to turn off the warning completely.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-mailinfo.txt | 18 +++++++++++++++++-\n builtin/mailinfo.c             |  8 ++++++--\n mailinfo.c                     | 21 ++++++++++++++++++++-\n mailinfo.h                     |  8 ++++++++\n t/t5100-mailinfo.sh            |  6 ++++--\n 5 files changed, 55 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\nindex d343f040f5..c776b27515 100644\n--- a/Documentation/git-mailinfo.txt\n+++ b/Documentation/git-mailinfo.txt\n@@ -9,7 +9,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message\n SYNOPSIS\n --------\n [verse]\n-'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>\n+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] [--quoted-cr=<action>] <msg> <patch>\n \n \n DESCRIPTION\n@@ -89,6 +89,22 @@ This can be enabled by default with the configuration option mailinfo.scissors.\n --no-scissors::\n \tIgnore scissors lines. Useful for overriding mailinfo.scissors settings.\n \n+--quoted-cr=<action>::\n+\tAction when processes email messages sent with base64 or\n+\tquoted-printable encoding, and the decoded lines end with CR-LF\n+\tinstead of a simple LF.\n++\n+The valid actions are:\n++\n+--\n+*\t`nowarn`: Git will do nothing with this action.\n+*\t`warn`: Git will issue a warning for each message if such CR-LF is\n+\tfound.\n+--\n++\n+The default action could be set by configuration option `mailinfo.quotedCR`.\n+If no such configuration option has been set, `warn` will be used.\n+\n <msg>::\n \tThe commit log message extracted from e-mail, usually\n \texcept the title line which comes from e-mail Subject.\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex b309badce5..1d600263cb 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -9,7 +9,7 @@\n #include \"mailinfo.h\"\n \n static const char mailinfo_usage[] =\n-\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n+\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] [--quoted-cr=<action>] <msg> <patch> < mail >info\";\n \n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n@@ -43,7 +43,11 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \t\t\tmi.use_scissors = 0;\n \t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n \t\t\tmi.use_inbody_headers = 0;\n-\t\telse\n+\t\telse if (skip_prefix(argv[1], \"--quoted-cr=\", &str)) {\n+\t\t\tmi.quoted_cr = mailinfo_parse_quoted_cr_action(str);\n+\t\t\tif (mi.quoted_cr == quoted_cr_invalid_action)\n+\t\t\t\tusage(mailinfo_usage);\n+\t\t} else\n \t\t\tusage(mailinfo_usage);\n \t\targc--; argv++;\n \t}\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 713567f84b..fe7ffd01d0 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1040,7 +1040,8 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \n static void summarize_quoted_cr(struct mailinfo *mi, int have_quoted_cr)\n {\n-\tif (have_quoted_cr)\n+\tif (have_quoted_cr\n+\t    && mi->quoted_cr == quoted_cr_warn)\n \t\twarning(\"quoted CR detected\");\n }\n \n@@ -1221,9 +1222,19 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n \treturn mi->input_error;\n }\n \n+enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action)\n+{\n+\tif (!strcmp(action, \"nowarn\"))\n+\t\treturn quoted_cr_nowarn;\n+\telse if (!strcmp(action, \"warn\"))\n+\t\treturn quoted_cr_warn;\n+\treturn quoted_cr_invalid_action;\n+}\n+\n static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n+\tconst char *str;\n \n \tif (!starts_with(var, \"mailinfo.\"))\n \t\treturn git_default_config(var, value, NULL);\n@@ -1231,6 +1242,13 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"mailinfo.quotedcr\")) {\n+\t\tgit_config_string(&str, var, value);\n+\t\tmi->quoted_cr = mailinfo_parse_quoted_cr_action(str);\n+\t\tif (mi->quoted_cr == quoted_cr_invalid_action)\n+\t\t\tdie(_(\"bad action '%s' for '%s'\"), str, var);\n+\t\tfree((void *)str);\n+\t}\n \t/* perhaps others here */\n \treturn 0;\n }\n@@ -1243,6 +1261,7 @@ void setup_mailinfo(struct mailinfo *mi)\n \tstrbuf_init(&mi->charset, 0);\n \tstrbuf_init(&mi->log_message, 0);\n \tstrbuf_init(&mi->inbody_header_accum, 0);\n+\tmi->quoted_cr = quoted_cr_warn;\n \tmi->header_stage = 1;\n \tmi->use_inbody_headers = 1;\n \tmi->content_top = mi->content;\ndiff --git a/mailinfo.h b/mailinfo.h\nindex 79b1d6774e..1bcef5a6f3 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -5,6 +5,12 @@\n \n #define MAX_BOUNDARIES 5\n \n+enum quoted_cr_action {\n+\tquoted_cr_nowarn,\n+\tquoted_cr_warn,\n+\tquoted_cr_invalid_action\n+};\n+\n struct mailinfo {\n \tFILE *input;\n \tFILE *output;\n@@ -14,6 +20,7 @@ struct mailinfo {\n \tstruct strbuf email;\n \tint keep_subject;\n \tint keep_non_patch_brackets_in_subject;\n+\tenum quoted_cr_action quoted_cr;\n \tint add_message_id;\n \tint use_scissors;\n \tint use_inbody_headers;\n@@ -39,6 +46,7 @@ struct mailinfo {\n \tint input_error;\n };\n \n+enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action);\n void setup_mailinfo(struct mailinfo *);\n int mailinfo(struct mailinfo *, const char *msg, const char *patch);\n void clear_mailinfo(struct mailinfo *);\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex d8fdda6bea..57b8fc8104 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -236,11 +236,13 @@ check_quoted_cr_mail() {\n \ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n }\n \n-test_expect_success 'mailinfo warn CR in base64 encoded email' '\n+test_expect_success 'mailinfo handle CR in base64 encoded email' '\n \tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n \tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n \tcheck_quoted_cr_mail &&\n-\tgrep \"quoted CR detected\" quoted-cr-err\n+\tgrep \"quoted CR detected\" quoted-cr-err &&\n+\tcheck_quoted_cr_mail --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr-err\n '\n \n test_done\n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423595","messageId":"01d4a4853b8a0b6911a2d7773f836620566b4293.1620148732.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620148732.git.congdanhqx@gmail.com","subject":"[PATCH v2 4/5] mailinfo: strip quoted CR on users' wish","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-04T17:20:01Z","receivedAt":"2021-05-04T17:20:25Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous changes, we've turned on warning for quoted CR in base64 or\nquoted-printable email messages. Some projects sees those quoted CR a lot\nand they know that it happens most of the time.\n\nThose projects in question usually fall back to use other tools to handle\npatches when receiving such patches.\n\nLet's help those projects handle those patches by stripping those\nexcessive CR-s.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-mailinfo.txt | 1 +\n mailinfo.c                     | 7 +++++++\n mailinfo.h                     | 1 +\n t/t5100-mailinfo.sh            | 4 ++++\n 4 files changed, 13 insertions(+)\n\ndiff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\nindex c776b27515..d700929a46 100644\n--- a/Documentation/git-mailinfo.txt\n+++ b/Documentation/git-mailinfo.txt\n@@ -100,6 +100,7 @@ The valid actions are:\n *\t`nowarn`: Git will do nothing with this action.\n *\t`warn`: Git will issue a warning for each message if such CR-LF is\n \tfound.\n+*\t`strip`: Git will convert those CR-LF to LF.\n --\n +\n The default action could be set by configuration option `mailinfo.quotedCR`.\ndiff --git a/mailinfo.c b/mailinfo.c\nindex fe7ffd01d0..68f4eba72a 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -998,6 +998,11 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \t\t    line->buf[len - 2] == '\\r' &&\n \t\t    line->buf[len - 1] == '\\n') {\n \t\t\t*have_quoted_cr = 1;\n+\t\t\tif (mi->quoted_cr == quoted_cr_strip) {\n+\t\t\t\tstrbuf_setlen(line, len - 2);\n+\t\t\t\tstrbuf_addch(line, '\\n');\n+\t\t\t\tlen--;\n+\t\t\t}\n \t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n@@ -1228,6 +1233,8 @@ enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action)\n \t\treturn quoted_cr_nowarn;\n \telse if (!strcmp(action, \"warn\"))\n \t\treturn quoted_cr_warn;\n+\telse if (!strcmp(action, \"strip\"))\n+\t\treturn quoted_cr_strip;\n \treturn quoted_cr_invalid_action;\n }\n \ndiff --git a/mailinfo.h b/mailinfo.h\nindex 1bcef5a6f3..e0e094c311 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -8,6 +8,7 @@\n enum quoted_cr_action {\n \tquoted_cr_nowarn,\n \tquoted_cr_warn,\n+\tquoted_cr_strip,\n \tquoted_cr_invalid_action\n };\n \ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 57b8fc8104..7559c922c6 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -242,6 +242,10 @@ test_expect_success 'mailinfo handle CR in base64 encoded email' '\n \tcheck_quoted_cr_mail &&\n \tgrep \"quoted CR detected\" quoted-cr-err &&\n \tcheck_quoted_cr_mail --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr-err &&\n+\tsed \"s/%%//\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n+\tsed \"s/%%//\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n+\tcheck_quoted_cr_mail --quoted-cr=strip &&\n \ttest_must_be_empty quoted-cr-err\n '\n \n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423596","messageId":"d2052bcea925ec9e7d780e99e5b5e3bb1b9bd056.1620148732.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620148732.git.congdanhqx@gmail.com","subject":"[PATCH v2 5/5] am: learn to process quoted lines that ends with CRLF","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-04T17:20:02Z","receivedAt":"2021-05-04T17:20:26Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous changes, mailinfo has learnt to process lines that decoded\nfrom base64 or quoted-printable and ends with CRLF.\n\nLet's teach \"am\" that new option, too.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-am.txt |  4 +++\n builtin/am.c             | 56 ++++++++++++++++++++++++++++++++++++++++\n mailinfo.h               |  1 +\n t/t4258-am-quoted-cr.sh  | 37 ++++++++++++++++++++++++++\n t/t4258/mbox             | 12 +++++++++\n 5 files changed, 110 insertions(+)\n create mode 100755 t/t4258-am-quoted-cr.sh\n create mode 100644 t/t4258/mbox\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex decd8ae122..8714dfcb76 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n \t [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n+\t [--quoted-cr=<action>]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -59,6 +60,9 @@ OPTIONS\n --no-scissors::\n \tIgnore scissors lines (see linkgit:git-mailinfo[1]).\n \n+--quoted-cr=<action>::\n+\tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8355e3566f..ff4c8ee68f 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -116,6 +116,7 @@ struct am_state {\n \tint keep; /* enum keep_type */\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n+\tint quoted_cr; /* enum quoted_cr_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -145,6 +146,7 @@ static void am_state_init(struct am_state *state)\n \tgit_config_get_bool(\"am.messageid\", &state->message_id);\n \n \tstate->scissors = SCISSORS_UNSET;\n+\tstate->quoted_cr = quoted_cr_unset;\n \n \tstrvec_init(&state->git_apply_opts);\n \n@@ -165,6 +167,19 @@ static void am_state_release(struct am_state *state)\n \tstrvec_clear(&state->git_apply_opts);\n }\n \n+static int am_option_parse_quoted_cr(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *quoted_cr = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\t*quoted_cr = mailinfo_parse_quoted_cr_action(arg);\n+\tif (*quoted_cr == quoted_cr_invalid_action)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -397,6 +412,14 @@ static void am_load(struct am_state *state)\n \telse\n \t\tstate->scissors = SCISSORS_UNSET;\n \n+\tread_state_file(&sb, state, \"quoted-cr\", 1);\n+\tif (!*sb.buf)\n+\t\tstate->quoted_cr = quoted_cr_unset;\n+\telse\n+\t\tstate->quoted_cr = mailinfo_parse_quoted_cr_action(sb.buf);\n+\tif (state->quoted_cr == quoted_cr_invalid_action)\n+\t\tdie(_(\"could not parse %s\"), am_path(state, \"quoted-cr\"));\n+\n \tread_state_file(&sb, state, \"apply-opt\", 1);\n \tstrvec_clear(&state->git_apply_opts);\n \tif (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)\n@@ -1002,6 +1025,24 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t}\n \twrite_state_text(state, \"scissors\", str);\n \n+\tswitch (state->quoted_cr) {\n+\tcase quoted_cr_unset:\n+\t\tstr = \"\";\n+\t\tbreak;\n+\tcase quoted_cr_nowarn:\n+\t\tstr = \"nowarn\";\n+\t\tbreak;\n+\tcase quoted_cr_warn:\n+\t\tstr = \"warn\";\n+\t\tbreak;\n+\tcase quoted_cr_strip:\n+\t\tstr = \"strip\";\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid value for state->quoted_cr\");\n+\t}\n+\twrite_state_text(state, \"quoted-cr\", str);\n+\n \tsq_quote_argv(&sb, state->git_apply_opts.v);\n \twrite_state_text(state, \"apply-opt\", sb.buf);\n \n@@ -1162,6 +1203,18 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tBUG(\"invalid value for state->scissors\");\n \t}\n \n+\tswitch (state->quoted_cr) {\n+\tcase quoted_cr_unset:\n+\t\tbreak;\n+\tcase quoted_cr_nowarn:\n+\tcase quoted_cr_warn:\n+\tcase quoted_cr_strip:\n+\t\tmi.quoted_cr = state->quoted_cr;\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid value for state->quoted_cr\");\n+\t}\n+\n \tmi.input = xfopen(mail, \"r\");\n \tmi.output = xfopen(am_path(state, \"info\"), \"w\");\n \tif (mailinfo(&mi, am_path(state, \"msg\"), am_path(state, \"patch\")))\n@@ -2242,6 +2295,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\t0, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('c', \"scissors\", &state.scissors,\n \t\t\tN_(\"strip everything before a scissors line\")),\n+\t\tOPT_CALLBACK_F(0, \"quoted-cr\", &state.quoted_cr, N_(\"action\"),\n+\t\t\t       N_(\"pass it through git-mailinfo\"),\n+\t\t\t       PARSE_OPT_NONEG, am_option_parse_quoted_cr),\n \t\tOPT_PASSTHRU_ARGV(0, \"whitespace\", &state.git_apply_opts, N_(\"action\"),\n \t\t\tN_(\"pass it through git-apply\"),\n \t\t\t0),\ndiff --git a/mailinfo.h b/mailinfo.h\nindex e0e094c311..8c78c72bbe 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -6,6 +6,7 @@\n #define MAX_BOUNDARIES 5\n \n enum quoted_cr_action {\n+\tquoted_cr_unset = -1,\n \tquoted_cr_nowarn,\n \tquoted_cr_warn,\n \tquoted_cr_strip,\ndiff --git a/t/t4258-am-quoted-cr.sh b/t/t4258-am-quoted-cr.sh\nnew file mode 100755\nindex 0000000000..2029115ecd\n--- /dev/null\n+++ b/t/t4258-am-quoted-cr.sh\n@@ -0,0 +1,37 @@\n+#!/bin/sh\n+\n+test_description='test am --quoted-cr=<action>'\n+\n+. ./test-lib.sh\n+\n+DATA=\"$TEST_DIRECTORY/t4258\"\n+\n+test_expect_success 'setup' '\n+\ttest_write_lines one two three >text &&\n+\ttest_commit one text &&\n+\ttest_write_lines one owt three >text &&\n+\ttest_commit two text\n+'\n+\n+test_expect_success 'am warn if quoted-cr is found' '\n+\tgit reset --hard one &&\n+\ttest_must_fail git am \"$DATA/mbox\" 2>err &&\n+\tgrep \"quoted CR detected\" err\n+'\n+\n+test_expect_success 'am strip if quoted-cr is found' '\n+\ttest_might_fail git am --abort &&\n+\tgit reset --hard one &&\n+\tgit am --quoted-cr=strip \"$DATA/mbox\" &&\n+\tgit diff --exit-code HEAD two\n+'\n+\n+test_expect_success 'am strip if quoted-cr is found' '\n+\ttest_might_fail git am --abort &&\n+\tgit reset --hard one &&\n+\ttest_config mailinfo.quotedCr strip &&\n+\tgit am \"$DATA/mbox\" &&\n+\tgit diff --exit-code HEAD two\n+'\n+\n+test_done\ndiff --git a/t/t4258/mbox b/t/t4258/mbox\nnew file mode 100644\nindex 0000000000..c62819f3d2\n--- /dev/null\n+++ b/t/t4258/mbox\n@@ -0,0 +1,12 @@\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+VGhpcyBpcyBjb21taXQgbWVzc2FnZS4NCi0tLQ0KIHRleHQgfCAyICstDQogMSBmaWxlIGNoYW5n\n+ZWQsIDEgaW5zZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS90ZXh0IGIv\n+dGV4dA0KaW5kZXggNTYyNmFiZi4uZjcxOWVmZCAxMDA2NDQNCi0tLSBhL3RleHQNCisrKyBiL3Rl\n+eHQNCkBAIC0xICsxIEBADQotb25lDQordHdvDQotLSANCjIuMzEuMQoK\n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423637","messageId":"xmqqr1il4zk0.fsf@gitster.g","threadId":"55533","inReplyTo":"45d2c4ab58c4b0c6f0c7790890bbf75eb373f999.1620148732.git.congdanhqx@gmail.com","subject":"Re: [PATCH v2 2/5] mailinfo: warn if CR found in base64/quoted-printable email","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-05T03:41:51Z","receivedAt":"2021-05-05T03:41:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> When an SMTP server receives an 8-bit email message, possibly with only\n> LF as line ending, some of those servers decide to change said LF to\n> CRLF.\n\ns/an SMTP server receives/SMTP servers receive/\ns/those servers/them/\n\n> Some mailing list softwares, when receives an 8-bit email message,\n> decide to encoding such message in base64 or quoted-printable.\n\ns/encoding/encode/\n\nSo the issue is not about CRLF terminating the lines of base64 or QP\n(we should treat CRLF and LF terminated lines when unwrapping base64\nor QP the same way).  It is about seeing CRLF in the payload after\nunwrapping base64 or QP.  It was unclear which one was at issue from\nthe subject alone.\n\n> If an email is transfered through above mail servers, then distributed\n> by such mailing list softwares, the recipients will receive an email\n> contains a patch mungled with CRLF encoded inside another encoding.\n> Thus, such CR couldn't be dropped by mailsplit. Hence, the mailed patch\n> couldn't be applied cleanly. Such accidents have been observed in the wild [1].\n>\n> Let's give our users some warnings if such CR is found.\n\nHmph.  It is unclear which one of the following we want our endgame\nto be:\n\n (1) strip silently and apply\n (2) warn but strip and apply\n (3) warn but do not strip, letting the application fail\n\nbut let's keep reading.  I suspect (1) and (2) might be error prone,\nas the mailpath that may have caused this kind of breakage may not\nbe under end-user's control.\n\n> +static void summarize_quoted_cr(struct mailinfo *mi, int have_quoted_cr)\n> +{\n> +\tif (have_quoted_cr)\n> +\t\twarning(\"quoted CR detected\");\n> +}\n\nAt this step, it is unclear if it is easier to read to make it the\nresponsibility of the caller to check for have_quoted_cr, but it\nwill become clear as we add more condition for the warning in later\nsteps to let callers unconditionally call this helper and decide\nwhen we want to be silent inside this function.\n\nHave you considered adding a new have_quoted_cr member to \"struct\nmailinfo\"?  After all, the mailinfo struct is not only about end\nuser preference but contains all information we gleaned out of the\nincoming message.\n\n>  static void handle_body(struct mailinfo *mi, struct strbuf *line)\n>  {\n>  \tstruct strbuf prev = STRBUF_INIT;\n> +\tint have_quoted_cr = 0;\n>  \n>  \t/* Skip up to the first boundary */\n>  \tif (*(mi->content_top)) {\n> @@ -1051,6 +1063,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n>  \t\t\t\thandle_filter(mi, &prev);\n>  \t\t\t\tstrbuf_reset(&prev);\n>  \t\t\t}\n> +\t\t\tsummarize_quoted_cr(mi, have_quoted_cr);\n> +\t\t\thave_quoted_cr = 0;\n>  \t\t\tif (!handle_boundary(mi, line))\n>  \t\t\t\tgoto handle_body_out;\n>  \t\t}\n> @@ -1081,7 +1095,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_flowed(mi, sb, &prev);\n> +\t\t\t\thandle_filter_flowed(mi, sb, &prev, &have_quoted_cr);\n>  \t\t\t}\n>  \t\t\t/*\n>  \t\t\t * The partial chunk is saved in \"prev\" and will be\n> @@ -1091,7 +1105,7 @@ 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_flowed(mi, line, &prev);\n> +\t\t\thandle_filter_flowed(mi, line, &prev, &have_quoted_cr);\n>  \t\t}\n>  \n>  \t\tif (mi->input_error)\n> @@ -1100,6 +1114,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n>  \n>  \tif (prev.len)\n>  \t\thandle_filter(mi, &prev);\n> +\tsummarize_quoted_cr(mi, have_quoted_cr);\n>  \n>  \tflush_inbody_header_accum(mi);\n>  \n> diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\n> index 147e616533..d8fdda6bea 100755\n> --- a/t/t5100-mailinfo.sh\n> +++ b/t/t5100-mailinfo.sh\n> @@ -228,4 +228,19 @@ test_expect_success 'mailinfo handles unusual header whitespace' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +check_quoted_cr_mail() {\n\nSP on both sides of (), i.e.\n\n\tcheck_quoted_cr_mail () {\n\n> +\tgit mailinfo -u \"$@\" quoted-cr-msg quoted-cr-patch \\\n> +\t\t<\"$DATA/quoted-cr.mbox\" >quoted-cr-info 2>quoted-cr-err &&\n> +\ttest_cmp \"expect-cr-msg\" quoted-cr-msg &&\n> +\ttest_cmp \"expect-cr-patch\" quoted-cr-patch &&\n> +\ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n> +}\n> +\n> +test_expect_success 'mailinfo warn CR in base64 encoded email' '\n> +\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n> +\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n> +\tcheck_quoted_cr_mail &&\n> +\tgrep \"quoted CR detected\" quoted-cr-err\n> +'\n> +\n>  test_done\n"},{"id":"423643","messageId":"xmqqeeel4y5f.fsf@gitster.g","threadId":"55533","inReplyTo":"3215ea95cf869c8495d95cfd774973c330c14d1d.1620148732.git.congdanhqx@gmail.com","subject":"Re: [PATCH v2 3/5] mailinfo: skip quoted CR on user's wish","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-05T04:12:12Z","receivedAt":"2021-05-05T04:12:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> Subject: Re: [PATCH v2 3/5] mailinfo: skip quoted CR on user's wish\n\nNothing wrong per-se, but \"on user's wish\" feel somewhat bizarre.\nPerhaps\n\n    mailinfo: allow skipping quoted CR\n\nor something along that line?\n\n> In previous change, we've turned on warning for quoted CR in base64\n> encoded email. Despite those warnings are usually helpful for our users,\n> they may expect quoted CR in their emails.\n>\n> Let's give them an option to turn off the warning completely.\n>\n> Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n> ---\n>  Documentation/git-mailinfo.txt | 18 +++++++++++++++++-\n>  builtin/mailinfo.c             |  8 ++++++--\n>  mailinfo.c                     | 21 ++++++++++++++++++++-\n>  mailinfo.h                     |  8 ++++++++\n>  t/t5100-mailinfo.sh            |  6 ++++--\n>  5 files changed, 55 insertions(+), 6 deletions(-)\n>\n> diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\n> index d343f040f5..c776b27515 100644\n> --- a/Documentation/git-mailinfo.txt\n> +++ b/Documentation/git-mailinfo.txt\n> @@ -9,7 +9,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>\n> +'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] [--quoted-cr=<action>] <msg> <patch>\n\nThis line is getting really crowded.  Perhaps it is time to do\n\n\t'git mailinfo' [<options>] <msg> <patch>\n\nlike other Git subcommands with too many options?  Certainly it can\nbe done after the dust settles from this entire series as a follow up\nclean-up patch.\n\n> @@ -89,6 +89,22 @@ This can be enabled by default with the configuration option mailinfo.scissors.\n>  --no-scissors::\n>  \tIgnore scissors lines. Useful for overriding mailinfo.scissors settings.\n>  \n> +--quoted-cr=<action>::\n> +\tAction when processes email messages sent with base64 or\n> +\tquoted-printable encoding, and the decoded lines end with CR-LF\n\ns/with CR-LF/with a CRLF/\n\n> +\tinstead of a simple LF.\n> ++\n> +The valid actions are:\n> ++\n> +--\n> +*\t`nowarn`: Git will do nothing with this action.\n\ns/with this action./when such a CRLF is seen./ perhaps?\n\n> +*\t`warn`: Git will issue a warning for each message if such CR-LF is\n\ns/such CR-LF/such a CRLF/\n\n> diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\n> index b309badce5..1d600263cb 100644\n> --- a/builtin/mailinfo.c\n> +++ b/builtin/mailinfo.c\n> @@ -9,7 +9,7 @@\n>  #include \"mailinfo.h\"\n>  \n>  static const char mailinfo_usage[] =\n> -\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n> +\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] [--quoted-cr=<action>] <msg> <patch> < mail >info\";\n\nIt is surprising that we haven't switched this to parse_options().\nIt of course is outside the scope of this series, but from a cursory\nlook of its option parsing loop, it looks like a trivial improvement\nto make.\n\n> @@ -43,7 +43,11 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n>  \t\t\tmi.use_scissors = 0;\n>  \t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n>  \t\t\tmi.use_inbody_headers = 0;\n> -\t\telse\n> +\t\telse if (skip_prefix(argv[1], \"--quoted-cr=\", &str)) {\n> +\t\t\tmi.quoted_cr = mailinfo_parse_quoted_cr_action(str);\n> +\t\t\tif (mi.quoted_cr == quoted_cr_invalid_action)\n> +\t\t\t\tusage(mailinfo_usage);\n\nThis is not all that helpful, given that mailinfo_usage[] only says\n<action> without saying what the supported values are, and the\nmessage does not make it clear it was issued while looking at the\n--quoted-cr option.\n\nAt least, something like\n\n\t\t\tif (mi.quoted_cr == quoted_cr_invalid_action)\n                        \tdie(\"--quoted-cr=%s: invalid action\", str);\n\nwould be more palatable, but I wonder if mailinfo_parse_quoted_cr_action()\nshould have an option to die with the list of actions it knows about\nin a message.\n\n> diff --git a/mailinfo.c b/mailinfo.c\n> index 713567f84b..fe7ffd01d0 100644\n> --- a/mailinfo.c\n> +++ b/mailinfo.c\n> @@ -1040,7 +1040,8 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n>  \n>  static void summarize_quoted_cr(struct mailinfo *mi, int have_quoted_cr)\n>  {\n> -\tif (have_quoted_cr)\n> +\tif (have_quoted_cr\n> +\t    && mi->quoted_cr == quoted_cr_warn)\n\nExisting code in this file prefers to split a multi-line statement\nafter sequence point like &&, ||, etc., not before.\n\n>  \t\twarning(\"quoted CR detected\");\n>  }\n>  \n> @@ -1221,9 +1222,19 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n>  \treturn mi->input_error;\n>  }\n>  \n> +enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action)\n> +{\n> +\tif (!strcmp(action, \"nowarn\"))\n> +\t\treturn quoted_cr_nowarn;\n> +\telse if (!strcmp(action, \"warn\"))\n> +\t\treturn quoted_cr_warn;\n> +\treturn quoted_cr_invalid_action;\n> +}\n\nOK.\n\n>  static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n>  {\n>  \tstruct mailinfo *mi = mi_;\n> +\tconst char *str;\n>  \n>  \tif (!starts_with(var, \"mailinfo.\"))\n>  \t\treturn git_default_config(var, value, NULL);\n> @@ -1231,6 +1242,13 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n>  \t\tmi->use_scissors = git_config_bool(var, value);\n>  \t\treturn 0;\n>  \t}\n> +\tif (!strcmp(var, \"mailinfo.quotedcr\")) {\n> +\t\tgit_config_string(&str, var, value);\n> +\t\tmi->quoted_cr = mailinfo_parse_quoted_cr_action(str);\n> +\t\tif (mi->quoted_cr == quoted_cr_invalid_action)\n> +\t\t\tdie(_(\"bad action '%s' for '%s'\"), str, var);\n> +\t\tfree((void *)str);\n> +\t}\n\nHere, it is more reasonable.  It still does not say what actions are\naccepted, but at least the user learns where our displeasure comes\nfrom.\n\n>  \t/* perhaps others here */\n>  \treturn 0;\n>  }\n"},{"id":"423644","messageId":"xmqqa6p94xgn.fsf@gitster.g","threadId":"55533","inReplyTo":"01d4a4853b8a0b6911a2d7773f836620566b4293.1620148732.git.congdanhqx@gmail.com","subject":"Re: [PATCH v2 4/5] mailinfo: strip quoted CR on users' wish","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-05T04:27:04Z","receivedAt":"2021-05-05T04:27:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> Subject: Re: [PATCH v2 4/5] mailinfo: strip quoted CR on users' wish\n\nAgain, perhaps\n\n    mailinfo: allow stripping quoted CR without warning\n\nBy the way, the previous one said \"skip\", but I do not think it was\nskipping quoted CR, so its title was misleading.  Perhaps\n\n  [3/5] mailinfo: allow squelching quoted CR warning\n\nor something.\n\n> In previous changes, we've turned on warning for quoted CR in base64 or\n> quoted-printable email messages. Some projects sees those quoted CR a lot\n> and they know that it happens most of the time.\n\n    ... a lot, they know that it happens most of the time, and they\n    know it always is harmless to behave as if these CRs are not\n    there.\n\nThe last sentence is an important precondition for the use of this\nnew option to be safe.\n\n> diff --git a/mailinfo.c b/mailinfo.c\n> index fe7ffd01d0..68f4eba72a 100644\n> --- a/mailinfo.c\n> +++ b/mailinfo.c\n> @@ -998,6 +998,11 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n>  \t\t    line->buf[len - 2] == '\\r' &&\n>  \t\t    line->buf[len - 1] == '\\n') {\n>  \t\t\t*have_quoted_cr = 1;\n> +\t\t\tif (mi->quoted_cr == quoted_cr_strip) {\n> +\t\t\t\tstrbuf_setlen(line, len - 2);\n> +\t\t\t\tstrbuf_addch(line, '\\n');\n> +\t\t\t\tlen--;\n\nThe last one is beating a dead variable immediately before this\nfunction returns, even though it is good for consistency (i.e. there\nis an invaliant throughout the function that len is the number of\nbytes contained in line->buf[]).\n\nI am not sure what to think about this.  I wish there weren't need\nfor a separate len variable, with the need for this extra invariant.\nAfter all, strbuf already has such an invariant that is well\nunderstood by readers of this code (i.e. line->buf[]'s end is at\nline->len).  For now, until we get rid of \"len\" from this function,\nlet's leave the final decrement in to make it absolutely clear that\nwe are aware of this extra invariant.\n\n"},{"id":"423645","messageId":"xmqq5yzx4xa3.fsf@gitster.g","threadId":"55533","inReplyTo":"cover.1620148732.git.congdanhqx@gmail.com","subject":"Re: [PATCH v2 0/5] Teach am/mailinfo to process quoted CR","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-05T04:31:00Z","receivedAt":"2021-05-05T04:31:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> When an SMTP server receives an 8-bit email message, possibly with only\n> LF as line ending, some of those servers decide to change said LF to\n> CRLF.\n>\n> Some mailing list software, when receives an 8-bit email message,\n> decide to encoding such message in base64 or quoted-printable.\n>\n> This series try to help users of such softwares deal with such patches.\n>\n> This series is a complete rewrite of v1.\n> Hence, no {inter,range}-diff.\n\nThanks.\n\nI've left a few comments here and there, but like the overall\napproach.\n"},{"id":"423694","messageId":"YJK/ieA7fzB+h01t@danh.dev","threadId":"55533","inReplyTo":"xmqqeeel4y5f.fsf@gitster.g","subject":"Re: [PATCH v2 3/5] mailinfo: skip quoted CR on user's wish","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-05T15:53:45Z","receivedAt":"2021-05-05T15:53:50Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-05-05 13:12:12+0900, Junio C Hamano <gitster@pobox.com> wrote:\n> > diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\n> > index d343f040f5..c776b27515 100644\n> > --- a/Documentation/git-mailinfo.txt\n> > +++ b/Documentation/git-mailinfo.txt\n> > @@ -9,7 +9,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message\n> >  SYNOPSIS\n> >  --------\n> >  [verse]\n> > -'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>\n> > +'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] [--quoted-cr=<action>] <msg> <patch>\n> \n> This line is getting really crowded.  Perhaps it is time to do\n> \n> \t'git mailinfo' [<options>] <msg> <patch>\n> \n> like other Git subcommands with too many options?  Certainly it can\n> be done after the dust settles from this entire series as a follow up\n> clean-up patch.\n\nYes, I think it's time to do that clean-up.\n\n> >  static const char mailinfo_usage[] =\n> > -\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n> > +\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] [--quoted-cr=<action>] <msg> <patch> < mail >info\";\n> \n> It is surprising that we haven't switched this to parse_options().\n> It of course is outside the scope of this series, but from a cursory\n> look of its option parsing loop, it looks like a trivial improvement\n> to make.\n\nAnd given that we also need 1/5 (otherwise, we need a new declaration\nfor \"const char *str\"), I think it would be better to turn 1/5 to the\nconversion to parse_option.\n\n> > @@ -43,7 +43,11 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n> >  \t\t\tmi.use_scissors = 0;\n> >  \t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n> >  \t\t\tmi.use_inbody_headers = 0;\n> > -\t\telse\n> > +\t\telse if (skip_prefix(argv[1], \"--quoted-cr=\", &str)) {\n> > +\t\t\tmi.quoted_cr = mailinfo_parse_quoted_cr_action(str);\n> > +\t\t\tif (mi.quoted_cr == quoted_cr_invalid_action)\n> > +\t\t\t\tusage(mailinfo_usage);\n> \n> This is not all that helpful, given that mailinfo_usage[] only says\n> <action> without saying what the supported values are, and the\n> message does not make it clear it was issued while looking at the\n> --quoted-cr option.\n> \n> At least, something like\n> \n> \t\t\tif (mi.quoted_cr == quoted_cr_invalid_action)\n>                         \tdie(\"--quoted-cr=%s: invalid action\", str);\n> \n> would be more palatable, but I wonder if mailinfo_parse_quoted_cr_action()\n> should have an option to die with the list of actions it knows about\n> in a message.\n\nI tempted to remove the _invalid_action with the re-roll and always\ndie when it doesn't understand the actions instead.\nLet's see how far I can get with that approach.\n\n-- \nDanh\n"},{"id":"423780","messageId":"cover.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"20210421013404.17383-1-congdanhqx@gmail.com","subject":"[PATCH v3 0/6] Teach am/mailinfo to process quoted CR","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:17Z","receivedAt":"2021-05-06T15:02:37Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When SMTP servers receive 8-bit email messages, possibly with only\nLF as line ending, some of those servers decide to change said LF to\nCRLF.\n\nSome mailing list software, when receives an 8-bit email message,\ndecide to encode such message in base64 or quoted-printable.\n\nThis series try to help users of such softwares deal with such patches.\n\nChange from v2:\n* Change preparatory step (in mailinfo) to use parse_options\n* move \"have_quoted_cr\" to \"struct mailinfo\", thus the patch to warn about\n  quoted CR is less weird.\n* Change \"mailinfo_parse_quoted_cr\" to Git's do-some-work function, return\n  0 on success and negative on failure, eliminate the usage of _invalid_action\n* Better error messages if invalid action was given.\n* completion support for git am --quoted-cr\n* Some style changes\n\n\nĐoàn Trần Công Danh (6):\n  mailinfo: load default metainfo_charset lazily\n  mailinfo: stop parsing options manually\n  mailinfo: warn if CR found in decoded base64/QP email\n  mailinfo: allow squelching quoted CR warning\n  mailinfo: allow stripping quoted CR without warning\n  am: learn to process quoted lines that ends with CRLF\n\n Documentation/git-am.txt               |   4 +\n Documentation/git-mailinfo.txt         |  21 ++++-\n builtin/am.c                           |  51 +++++++++++\n builtin/mailinfo.c                     | 115 ++++++++++++++++++-------\n contrib/completion/git-completion.bash |   5 ++\n mailinfo.c                             |  39 +++++++++\n mailinfo.h                             |  10 +++\n t/t4258-am-quoted-cr.sh                |  37 ++++++++\n t/t4258/mbox                           |  12 +++\n t/t5100-mailinfo.sh                    |  21 +++++\n t/t5100/quoted-cr-info                 |   5 ++\n t/t5100/quoted-cr-msg                  |   2 +\n t/t5100/quoted-cr-patch                |  22 +++++\n t/t5100/quoted-cr.mbox                 |  22 +++++\n 14 files changed, 332 insertions(+), 34 deletions(-)\n create mode 100755 t/t4258-am-quoted-cr.sh\n create mode 100644 t/t4258/mbox\n create mode 100644 t/t5100/quoted-cr-info\n create mode 100644 t/t5100/quoted-cr-msg\n create mode 100644 t/t5100/quoted-cr-patch\n create mode 100644 t/t5100/quoted-cr.mbox\n\nRange-diff against v2:\n1:  5b1cab5f95 < -:  ---------- mailinfo: avoid magic number in option parsing\n-:  ---------- > 1:  fac95392df mailinfo: load default metainfo_charset lazily\n-:  ---------- > 2:  1fb08bb37d mailinfo: stop parsing options manually\n2:  534183c458 ! 3:  5aac2ba38e mailinfo: warn if CR found in base64/quoted-printable email\n    @@ Metadata\n     Author: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## Commit message ##\n    -    mailinfo: warn if CR found in base64/quoted-printable email\n    +    mailinfo: warn if CR found in decoded base64/QP email\n     \n    -    When an SMTP server receives an 8-bit email message, possibly with only\n    -    LF as line ending, some of those servers decide to change said LF to\n    -    CRLF.\n    +    When SMTP servers receive 8-bit email messages, possibly with only\n    +    LF as line ending, some of them decide to change said LF to CRLF.\n     \n    -    Some mailing list softwares, when receives an 8-bit email message,\n    -    decide to encoding such message in base64 or quoted-printable.\n    +    Some mailing list softwares, when receive 8-bit email messages,\n    +    decide to encode those messages in base64 or quoted-printable.\n     \n         If an email is transfered through above mail servers, then distributed\n         by such mailing list softwares, the recipients will receive an email\n         contains a patch mungled with CRLF encoded inside another encoding.\n    -    Thus, such CR couldn't be dropped by mailsplit. Hence, the mailed patch\n    -    couldn't be applied cleanly. Such accidents have been observed in the wild [1].\n     \n    -    Let's give our users some warnings if such CR is found.\n    +    Thus, such CR couldn't be dropped by \"mailsplit\".\n    +    Hence, the mailed patch couldn't be applied cleanly.\n    +    Such accidents have been observed in the wild [1].\n    +\n    +    Instead of silently rejecting those messages, let's give our users\n    +    some warnings if such CR is found.\n     \n         [1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n     \n         Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## mailinfo.c ##\n    -@@ mailinfo.c: static int handle_boundary(struct mailinfo *mi, struct strbuf *line)\n    - }\n    - \n    - static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n    --\t\t\t\t struct strbuf *prev)\n    -+\t\t\t\t struct strbuf *prev, int *have_quoted_cr)\n    - {\n    - \tsize_t len = line->len;\n    +@@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n      \tconst char *rest;\n      \n      \tif (!mi->format_flowed) {\n     +\t\tif (len >= 2 &&\n     +\t\t    line->buf[len - 2] == '\\r' &&\n     +\t\t    line->buf[len - 1] == '\\n') {\n    -+\t\t\t*have_quoted_cr = 1;\n    ++\t\t\tmi->have_quoted_cr = 1;\n     +\t\t}\n      \t\thandle_filter(mi, line);\n      \t\treturn;\n    @@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf\n      \thandle_filter(mi, line);\n      }\n      \n    -+static void summarize_quoted_cr(struct mailinfo *mi, int have_quoted_cr)\n    ++static void summarize_quoted_cr(struct mailinfo *mi)\n     +{\n    -+\tif (have_quoted_cr)\n    ++\tif (mi->have_quoted_cr)\n     +\t\twarning(\"quoted CR detected\");\n     +}\n     +\n      static void handle_body(struct mailinfo *mi, struct strbuf *line)\n      {\n      \tstruct strbuf prev = STRBUF_INIT;\n    -+\tint have_quoted_cr = 0;\n    - \n    - \t/* Skip up to the first boundary */\n    - \tif (*(mi->content_top)) {\n     @@ mailinfo.c: static void handle_body(struct mailinfo *mi, struct strbuf *line)\n      \t\t\t\thandle_filter(mi, &prev);\n      \t\t\t\tstrbuf_reset(&prev);\n      \t\t\t}\n    -+\t\t\tsummarize_quoted_cr(mi, have_quoted_cr);\n    -+\t\t\thave_quoted_cr = 0;\n    ++\t\t\tsummarize_quoted_cr(mi);\n    ++\t\t\tmi->have_quoted_cr = 0;\n      \t\t\tif (!handle_boundary(mi, line))\n      \t\t\t\tgoto handle_body_out;\n      \t\t}\n     @@ mailinfo.c: 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_flowed(mi, sb, &prev);\n    -+\t\t\t\thandle_filter_flowed(mi, sb, &prev, &have_quoted_cr);\n    - \t\t\t}\n    - \t\t\t/*\n    - \t\t\t * The partial chunk is saved in \"prev\" and will be\n    -@@ mailinfo.c: 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_flowed(mi, line, &prev);\n    -+\t\t\thandle_filter_flowed(mi, line, &prev, &have_quoted_cr);\n    - \t\t}\n    - \n    - \t\tif (mi->input_error)\n    -@@ mailinfo.c: static void handle_body(struct mailinfo *mi, struct strbuf *line)\n      \n      \tif (prev.len)\n      \t\thandle_filter(mi, &prev);\n    -+\tsummarize_quoted_cr(mi, have_quoted_cr);\n    ++\tsummarize_quoted_cr(mi);\n      \n      \tflush_inbody_header_accum(mi);\n      \n     \n    + ## mailinfo.h ##\n    +@@ mailinfo.h: struct mailinfo {\n    + \tstruct strbuf charset;\n    + \tunsigned int format_flowed:1;\n    + \tunsigned int delsp:1;\n    ++\tunsigned int have_quoted_cr:1;\n    + \tchar *message_id;\n    + \tenum  {\n    + \t\tTE_DONTCARE, TE_QP, TE_BASE64\n    +\n      ## t/t5100-mailinfo.sh ##\n     @@ t/t5100-mailinfo.sh: test_expect_success 'mailinfo handles unusual header whitespace' '\n      \ttest_cmp expect actual\n      '\n      \n    -+check_quoted_cr_mail() {\n    ++check_quoted_cr_mail () {\n     +\tgit mailinfo -u \"$@\" quoted-cr-msg quoted-cr-patch \\\n     +\t\t<\"$DATA/quoted-cr.mbox\" >quoted-cr-info 2>quoted-cr-err &&\n     +\ttest_cmp \"expect-cr-msg\" quoted-cr-msg &&\n3:  0d115821c3 ! 4:  d5b2da370d mailinfo: skip quoted CR on user's wish\n    @@ Metadata\n     Author: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## Commit message ##\n    -    mailinfo: skip quoted CR on user's wish\n    +    mailinfo: allow squelching quoted CR warning\n     \n    -    In previous change, we've turned on warning for quoted CR in base64\n    -    encoded email. Despite those warnings are usually helpful for our users,\n    -    they may expect quoted CR in their emails.\n    +    In previous change, Git starts to warn for quoted CR in decoded\n    +    base64/QP email. Despite those warnings are usually helpful,\n    +    quoted CR could be part of some users' workflow.\n     \n         Let's give them an option to turn off the warning completely.\n     \n    @@ Documentation/git-mailinfo.txt: git-mailinfo - Extracts patch and authorship fro\n      --------\n      [verse]\n     -'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>\n    -+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] [--quoted-cr=<action>] <msg> <patch>\n    ++'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n]\n    ++\t       [--[no-]scissors] [--quoted-cr=<action>]\n    ++\t       <msg> <patch>\n      \n      \n      DESCRIPTION\n    @@ Documentation/git-mailinfo.txt: This can be enabled by default with the configur\n      \n     +--quoted-cr=<action>::\n     +\tAction when processes email messages sent with base64 or\n    -+\tquoted-printable encoding, and the decoded lines end with CR-LF\n    ++\tquoted-printable encoding, and the decoded lines end with a CRLF\n     +\tinstead of a simple LF.\n     ++\n     +The valid actions are:\n     ++\n     +--\n    -+*\t`nowarn`: Git will do nothing with this action.\n    -+*\t`warn`: Git will issue a warning for each message if such CR-LF is\n    ++*\t`nowarn`: Git will do nothing when such a CRLF is found.\n    ++*\t`warn`: Git will issue a warning for each message if such a CRLF is\n     +\tfound.\n     +--\n     ++\n    @@ Documentation/git-mailinfo.txt: This can be enabled by default with the configur\n      \texcept the title line which comes from e-mail Subject.\n     \n      ## builtin/mailinfo.c ##\n    -@@\n    - #include \"mailinfo.h\"\n    - \n    - static const char mailinfo_usage[] =\n    --\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n    -+\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] [--quoted-cr=<action>] <msg> <patch> < mail >info\";\n    +@@ builtin/mailinfo.c: static int parse_opt_explicit_encoding(const struct option *opt,\n    + \treturn 0;\n    + }\n      \n    ++static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)\n    ++{\n    ++\tBUG_ON_OPT_NEG(unset);\n    ++\n    ++\tif (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)\n    ++\t\treturn error(_(\"bad action '%s' for '%s'\"), arg, \"--quoted-cr\");\n    ++\treturn 0;\n    ++}\n    ++\n      int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n      {\n    + \tstruct metainfo_charset meta_charset;\n     @@ builtin/mailinfo.c: int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n    - \t\t\tmi.use_scissors = 0;\n    - \t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n    - \t\t\tmi.use_inbody_headers = 0;\n    --\t\telse\n    -+\t\telse if (skip_prefix(argv[1], \"--quoted-cr=\", &str)) {\n    -+\t\t\tmi.quoted_cr = mailinfo_parse_quoted_cr_action(str);\n    -+\t\t\tif (mi.quoted_cr == quoted_cr_invalid_action)\n    -+\t\t\t\tusage(mailinfo_usage);\n    -+\t\t} else\n    - \t\t\tusage(mailinfo_usage);\n    - \t\targc--; argv++;\n    - \t}\n    + \t\t\t       N_(\"re-code metadata to this encoding\"),\n    + \t\t\t       PARSE_OPT_NONEG, parse_opt_explicit_encoding),\n    + \t\tOPT_BOOL(0, \"scissors\", &mi.use_scissors, N_(\"use scissors\")),\n    ++\t\tOPT_CALLBACK_F(0, \"quoted-cr\", &mi.quoted_cr, N_(\"<action>\"),\n    ++\t\t\t       N_(\"action when quoted CR is found\"),\n    ++\t\t\t       PARSE_OPT_NONEG, parse_opt_quoted_cr),\n    + \t\tOPT_HIDDEN_BOOL(0, \"inbody-headers\", &mi.use_inbody_headers,\n    + \t\t\t N_(\"use headers in message's body\")),\n    + \t\tOPT_END()\n     \n      ## mailinfo.c ##\n     @@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n      \n    - static void summarize_quoted_cr(struct mailinfo *mi, int have_quoted_cr)\n    + static void summarize_quoted_cr(struct mailinfo *mi)\n      {\n    --\tif (have_quoted_cr)\n    -+\tif (have_quoted_cr\n    -+\t    && mi->quoted_cr == quoted_cr_warn)\n    +-\tif (mi->have_quoted_cr)\n    ++\tif (mi->have_quoted_cr &&\n    ++\t    mi->quoted_cr == quoted_cr_warn)\n      \t\twarning(\"quoted CR detected\");\n      }\n      \n    @@ mailinfo.c: int mailinfo(struct mailinfo *mi, const char *msg, const char *patch\n      \treturn mi->input_error;\n      }\n      \n    -+enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action)\n    ++int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n     +{\n    -+\tif (!strcmp(action, \"nowarn\"))\n    -+\t\treturn quoted_cr_nowarn;\n    -+\telse if (!strcmp(action, \"warn\"))\n    -+\t\treturn quoted_cr_warn;\n    -+\treturn quoted_cr_invalid_action;\n    ++\tif (!strcmp(actionstr, \"nowarn\"))\n    ++\t\t*action = quoted_cr_nowarn;\n    ++\telse if (!strcmp(actionstr, \"warn\"))\n    ++\t\t*action = quoted_cr_warn;\n    ++\telse\n    ++\t\treturn -1;\n    ++\treturn 0;\n     +}\n     +\n      static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n      {\n      \tstruct mailinfo *mi = mi_;\n    -+\tconst char *str;\n    - \n    - \tif (!starts_with(var, \"mailinfo.\"))\n    - \t\treturn git_default_config(var, value, NULL);\n     @@ mailinfo.c: static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n      \t\tmi->use_scissors = git_config_bool(var, value);\n      \t\treturn 0;\n      \t}\n     +\tif (!strcmp(var, \"mailinfo.quotedcr\")) {\n    -+\t\tgit_config_string(&str, var, value);\n    -+\t\tmi->quoted_cr = mailinfo_parse_quoted_cr_action(str);\n    -+\t\tif (mi->quoted_cr == quoted_cr_invalid_action)\n    -+\t\t\tdie(_(\"bad action '%s' for '%s'\"), str, var);\n    -+\t\tfree((void *)str);\n    ++\t\tif (mailinfo_parse_quoted_cr_action(value, &mi->quoted_cr) != 0)\n    ++\t\t\treturn error(_(\"bad action '%s' for '%s'\"), value, var);\n    ++\t\treturn 0;\n     +\t}\n      \t/* perhaps others here */\n      \treturn 0;\n    @@ mailinfo.h\n     +enum quoted_cr_action {\n     +\tquoted_cr_nowarn,\n     +\tquoted_cr_warn,\n    -+\tquoted_cr_invalid_action\n     +};\n     +\n      struct mailinfo {\n    @@ mailinfo.h: struct mailinfo {\n      \tstruct strbuf email;\n      \tint keep_subject;\n      \tint keep_non_patch_brackets_in_subject;\n    -+\tenum quoted_cr_action quoted_cr;\n    ++\tint quoted_cr; /* enum quoted_cr_action */\n      \tint add_message_id;\n      \tint use_scissors;\n      \tint use_inbody_headers;\n    @@ mailinfo.h: struct mailinfo {\n      \tint input_error;\n      };\n      \n    -+enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action);\n    ++int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action);\n      void setup_mailinfo(struct mailinfo *);\n      int mailinfo(struct mailinfo *, const char *msg, const char *patch);\n      void clear_mailinfo(struct mailinfo *);\n     \n      ## t/t5100-mailinfo.sh ##\n    -@@ t/t5100-mailinfo.sh: check_quoted_cr_mail() {\n    +@@ t/t5100-mailinfo.sh: check_quoted_cr_mail () {\n      \ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n      }\n      \n4:  d48733805f ! 5:  9e96d4bf5e mailinfo: strip quoted CR on users' wish\n    @@ Metadata\n     Author: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## Commit message ##\n    -    mailinfo: strip quoted CR on users' wish\n    +    mailinfo: allow stripping quoted CR without warning\n     \n         In previous changes, we've turned on warning for quoted CR in base64 or\n    -    quoted-printable email messages. Some projects sees those quoted CR a lot\n    -    and they know that it happens most of the time.\n    +    quoted-printable email messages. Some projects see those quoted CR a lot,\n    +    they know that it happens most of the time, and they find it's desirable\n    +    to always strip those CR.\n     \n         Those projects in question usually fall back to use other tools to handle\n    -    patches when receiving such patches.\n    +    patches when receive such patches.\n     \n         Let's help those projects handle those patches by stripping those\n    -    excessive CR-s.\n    +    excessive CR.\n     \n         Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## Documentation/git-mailinfo.txt ##\n     @@ Documentation/git-mailinfo.txt: The valid actions are:\n    - *\t`nowarn`: Git will do nothing with this action.\n    - *\t`warn`: Git will issue a warning for each message if such CR-LF is\n    + *\t`nowarn`: Git will do nothing when such a CRLF is found.\n    + *\t`warn`: Git will issue a warning for each message if such a CRLF is\n      \tfound.\n    -+*\t`strip`: Git will convert those CR-LF to LF.\n    ++*\t`strip`: Git will convert those CRLF to LF.\n      --\n      +\n      The default action could be set by configuration option `mailinfo.quotedCR`.\n    @@ mailinfo.c\n     @@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n      \t\t    line->buf[len - 2] == '\\r' &&\n      \t\t    line->buf[len - 1] == '\\n') {\n    - \t\t\t*have_quoted_cr = 1;\n    + \t\t\tmi->have_quoted_cr = 1;\n     +\t\t\tif (mi->quoted_cr == quoted_cr_strip) {\n     +\t\t\t\tstrbuf_setlen(line, len - 2);\n     +\t\t\t\tstrbuf_addch(line, '\\n');\n    @@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf\n      \t\t}\n      \t\thandle_filter(mi, line);\n      \t\treturn;\n    -@@ mailinfo.c: enum quoted_cr_action mailinfo_parse_quoted_cr_action(const char *action)\n    - \t\treturn quoted_cr_nowarn;\n    - \telse if (!strcmp(action, \"warn\"))\n    - \t\treturn quoted_cr_warn;\n    -+\telse if (!strcmp(action, \"strip\"))\n    -+\t\treturn quoted_cr_strip;\n    - \treturn quoted_cr_invalid_action;\n    - }\n    - \n    +@@ mailinfo.c: int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n    + \t\t*action = quoted_cr_nowarn;\n    + \telse if (!strcmp(actionstr, \"warn\"))\n    + \t\t*action = quoted_cr_warn;\n    ++\telse if (!strcmp(actionstr, \"strip\"))\n    ++\t\t*action = quoted_cr_strip;\n    + \telse\n    + \t\treturn -1;\n    + \treturn 0;\n     \n      ## mailinfo.h ##\n     @@\n    @@ mailinfo.h\n      \tquoted_cr_nowarn,\n      \tquoted_cr_warn,\n     +\tquoted_cr_strip,\n    - \tquoted_cr_invalid_action\n      };\n      \n    + struct mailinfo {\n     \n      ## t/t5100-mailinfo.sh ##\n     @@ t/t5100-mailinfo.sh: test_expect_success 'mailinfo handle CR in base64 encoded email' '\n5:  95e309cc8b ! 6:  d6aa12acc0 am: learn to process quoted lines that ends with CRLF\n    @@ Commit message\n         am: learn to process quoted lines that ends with CRLF\n     \n         In previous changes, mailinfo has learnt to process lines that decoded\n    -    from base64 or quoted-printable and ends with CRLF.\n    +    from base64 or quoted-printable, and ends with CRLF.\n     \n    -    Let's teach \"am\" that new option, too.\n    +    Let's teach \"am\" that new trick, too.\n     \n         Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n    @@ builtin/am.c: static void am_state_release(struct am_state *state)\n     +static int am_option_parse_quoted_cr(const struct option *opt,\n     +\t\t\t\t     const char *arg, int unset)\n     +{\n    -+\tint *quoted_cr = opt->value;\n    -+\n     +\tBUG_ON_OPT_NEG(unset);\n     +\n    -+\t*quoted_cr = mailinfo_parse_quoted_cr_action(arg);\n    -+\tif (*quoted_cr == quoted_cr_invalid_action)\n    -+\t\treturn -1;\n    ++\tif (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)\n    ++\t\treturn error(_(\"bad action '%s' for '%s'\"), arg, \"--quoted-cr\");\n     +\treturn 0;\n     +}\n     +\n    @@ builtin/am.c: static void am_load(struct am_state *state)\n     +\tread_state_file(&sb, state, \"quoted-cr\", 1);\n     +\tif (!*sb.buf)\n     +\t\tstate->quoted_cr = quoted_cr_unset;\n    -+\telse\n    -+\t\tstate->quoted_cr = mailinfo_parse_quoted_cr_action(sb.buf);\n    -+\tif (state->quoted_cr == quoted_cr_invalid_action)\n    ++\telse if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)\n     +\t\tdie(_(\"could not parse %s\"), am_path(state, \"quoted-cr\"));\n     +\n      \tread_state_file(&sb, state, \"apply-opt\", 1);\n    @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n      \t\t\tN_(\"pass it through git-apply\"),\n      \t\t\t0),\n     \n    + ## contrib/completion/git-completion.bash ##\n    +@@ contrib/completion/git-completion.bash: __git_whitespacelist=\"nowarn warn error error-all fix\"\n    + __git_patchformat=\"mbox stgit stgit-series hg mboxrd\"\n    + __git_showcurrentpatch=\"diff raw\"\n    + __git_am_inprogress_options=\"--skip --continue --resolved --abort --quit --show-current-patch\"\n    ++__git_quoted_cr=\"nowarn warn strip\"\n    + \n    + _git_am ()\n    + {\n    +@@ contrib/completion/git-completion.bash: _git_am ()\n    + \t\t__gitcomp \"$__git_showcurrentpatch\" \"\" \"${cur##--show-current-patch=}\"\n    + \t\treturn\n    + \t\t;;\n    ++\t--quoted-cr=*)\n    ++\t\t__gitcomp \"$__git_quoted_cr\" \"\" \"${cur##--quoted-cr=}\"\n    ++\t\treturn\n    ++\t\t;;\n    + \t--*)\n    + \t\t__gitcomp_builtin am \"\" \\\n    + \t\t\t\"$__git_am_inprogress_options\"\n    +\n      ## mailinfo.h ##\n     @@\n      #define MAX_BOUNDARIES 5\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423779","messageId":"fac95392df0b1428c32874a1faff0c86d58af0bb.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"[PATCH v3 1/6] mailinfo: load default metainfo_charset lazily","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:18Z","receivedAt":"2021-05-06T15:02:38Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In a later change, we will use parse_option to parse mailinfo's options.\nIn mailinfo, both \"-u\", \"-n\", and \"--encoding\" try to set the same\nfield, with \"-u\" reset that field to some default value from\nconfiguration variable \"i18n.commitEncoding\".\n\nLet's delay the setting of that field until we finish processing all\noptions. By doing that, \"i18n.commitEncoding\" can be parsed on demand.\nMore importantly, it cleans the way for using parse_option.\n\nThis change introduces some inconsistent brackets \"{}\" in \"if/else if\"\nconstruct, however, we will rewrite them in the next few changes.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n builtin/mailinfo.c | 40 +++++++++++++++++++++++++++++++---------\n 1 file changed, 31 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex cfb667a594..77f96177cc 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -11,17 +11,25 @@\n static const char mailinfo_usage[] =\n \t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n \n+struct metainfo_charset\n+{\n+\tenum {\n+\t\tCHARSET_DEFAULT,\n+\t\tCHARSET_NO_REENCODE,\n+\t\tCHARSET_EXPLICIT,\n+\t} policy;\n+\tconst char *charset;\n+};\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *def_charset;\n+\tstruct metainfo_charset meta_charset;\n \tstruct mailinfo mi;\n \tint status;\n \tchar *msgfile, *patchfile;\n \n \tsetup_mailinfo(&mi);\n-\n-\tdef_charset = get_commit_output_encoding();\n-\tmi.metainfo_charset = def_charset;\n+\tmeta_charset.policy = CHARSET_DEFAULT;\n \n \twhile (1 < argc && argv[1][0] == '-') {\n \t\tif (!strcmp(argv[1], \"-k\"))\n@@ -31,12 +39,13 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \t\telse if (!strcmp(argv[1], \"-m\") || !strcmp(argv[1], \"--message-id\"))\n \t\t\tmi.add_message_id = 1;\n \t\telse if (!strcmp(argv[1], \"-u\"))\n-\t\t\tmi.metainfo_charset = def_charset;\n+\t\t\tmeta_charset.policy = CHARSET_DEFAULT;\n \t\telse if (!strcmp(argv[1], \"-n\"))\n-\t\t\tmi.metainfo_charset = NULL;\n-\t\telse if (starts_with(argv[1], \"--encoding=\"))\n-\t\t\tmi.metainfo_charset = argv[1] + 11;\n-\t\telse if (!strcmp(argv[1], \"--scissors\"))\n+\t\t\tmeta_charset.policy = CHARSET_NO_REENCODE;\n+\t\telse if (starts_with(argv[1], \"--encoding=\")) {\n+\t\t\tmeta_charset.policy = CHARSET_EXPLICIT;\n+\t\t\tmeta_charset.charset = argv[1] + 11;\n+\t\t} else if (!strcmp(argv[1], \"--scissors\"))\n \t\t\tmi.use_scissors = 1;\n \t\telse if (!strcmp(argv[1], \"--no-scissors\"))\n \t\t\tmi.use_scissors = 0;\n@@ -50,6 +59,19 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tif (argc != 3)\n \t\tusage(mailinfo_usage);\n \n+\tswitch (meta_charset.policy) {\n+\tcase CHARSET_DEFAULT:\n+\t\tmi.metainfo_charset = get_commit_output_encoding();\n+\t\tbreak;\n+\tcase CHARSET_NO_REENCODE:\n+\t\tmi.metainfo_charset = NULL;\n+\t\tbreak;\n+\tcase CHARSET_EXPLICIT:\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid meta_charset.policy\");\n+\t}\n+\n \tmi.input = stdin;\n \tmi.output = stdout;\n \n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423781","messageId":"ba8ac434189ce91314562d93004b2d2b37fe6868.1620309355.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620309355.git.congdanhqx@gmail.com","subject":"[PATCH v3 2/6] mailinfo: stop parse options manually","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:19Z","receivedAt":"2021-05-06T15:02:47Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In a later change, mailinfo will learn more options, let's switch to our\nrobust parse_options framework before that step.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n builtin/mailinfo.c | 87 +++++++++++++++++++++++++++-------------------\n 1 file changed, 51 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex 71e74bcdcb..a14232a437 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -7,20 +7,37 @@\n #include \"utf8.h\"\n #include \"strbuf.h\"\n #include \"mailinfo.h\"\n+#include \"parse-options.h\"\n \n-static const char mailinfo_usage[] =\n-\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n+static const char * const mailinfo_usage[] = {\n+\t/* TRANSLATORS: keep <> in \"<\" mail \">\" info. */\n+\tN_(\"git mailinfo [<options>] <msg> <patch> < mail >info\"),\n+\tNULL,\n+};\n \n struct metainfo_charset\n {\n \tenum {\n \t\tCHARSET_DEFAULT,\n-\t\tCHARSET_NONE,\n+\t\tCHARSET_NO_REENCODE,\n \t\tCHARSET_EXPLICIT,\n-\t} from;\n+\t} origin;\n \tconst char *charset;\n };\n \n+static int parse_opt_explicit_encoding(const struct option *opt,\n+\t\t\t\t       const char *arg, int unset)\n+{\n+\tstruct metainfo_charset *meta_charset = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tmeta_charset->origin = CHARSET_EXPLICIT;\n+\tmeta_charset->charset = arg;\n+\n+\treturn 0;\n+}\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n \tstruct metainfo_charset meta_charset;\n@@ -28,42 +45,40 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tint status;\n \tchar *msgfile, *patchfile;\n \n+\tstruct option options[] = {\n+\t\tOPT_BOOL('k', NULL, &mi.keep_subject, N_(\"keep subject\")),\n+\t\tOPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,\n+\t\t\t N_(\"keep non patch brackets in subject\")),\n+\t\tOPT_BOOL('m', \"message-id\", &mi.add_message_id,\n+\t\t\t N_(\"copy Message-ID to the end of commit message\")),\n+\t\tOPT_SET_INT_F('u', NULL, &meta_charset.origin,\n+\t\t\t      N_(\"re-code metadata to i18n.commitEncoding\"),\n+\t\t\t      CHARSET_DEFAULT, PARSE_OPT_NONEG),\n+\t\tOPT_SET_INT_F('n', NULL, &meta_charset.origin,\n+\t\t\t      N_(\"disable charset re-coding of metadata\"),\n+\t\t\t      CHARSET_NO_REENCODE, PARSE_OPT_NONEG),\n+\t\tOPT_CALLBACK_F(0, \"encoding\", &meta_charset, N_(\"encoding\"),\n+\t\t\t       N_(\"re-code metadata to this encoding\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_explicit_encoding),\n+\t\tOPT_BOOL(0, \"scissors\", &mi.use_scissors, N_(\"use scissors\")),\n+\t\tOPT_HIDDEN_BOOL(0, \"inbody-headers\", &mi.use_inbody_headers,\n+\t\t\t N_(\"use headers in message's body\")),\n+\t\tOPT_END()\n+\t};\n+\n \tsetup_mailinfo(&mi);\n-\tmeta_charset.from = CHARSET_DEFAULT;\n-\n-\twhile (1 < argc && argv[1][0] == '-') {\n-\t\tif (!strcmp(argv[1], \"-k\"))\n-\t\t\tmi.keep_subject = 1;\n-\t\telse if (!strcmp(argv[1], \"-b\"))\n-\t\t\tmi.keep_non_patch_brackets_in_subject = 1;\n-\t\telse if (!strcmp(argv[1], \"-m\") || !strcmp(argv[1], \"--message-id\"))\n-\t\t\tmi.add_message_id = 1;\n-\t\telse if (!strcmp(argv[1], \"-u\"))\n-\t\t\tmeta_charset.from = CHARSET_DEFAULT;\n-\t\telse if (!strcmp(argv[1], \"-n\"))\n-\t\t\tmeta_charset.from = CHARSET_NONE;\n-\t\telse if (starts_with(argv[1], \"--encoding=\")) {\n-\t\t\tmeta_charset.from = CHARSET_EXPLICIT;\n-\t\t\tmeta_charset.charset = argv[1] + 11;\n-\t\t} else if (!strcmp(argv[1], \"--scissors\"))\n-\t\t\tmi.use_scissors = 1;\n-\t\telse if (!strcmp(argv[1], \"--no-scissors\"))\n-\t\t\tmi.use_scissors = 0;\n-\t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n-\t\t\tmi.use_inbody_headers = 0;\n-\t\telse\n-\t\t\tusage(mailinfo_usage);\n-\t\targc--; argv++;\n-\t}\n+\tmeta_charset.origin = CHARSET_DEFAULT;\n+\n+\targc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);\n \n-\tif (argc != 3)\n-\t\tusage(mailinfo_usage);\n+\tif (argc != 2)\n+\t\tusage_with_options(mailinfo_usage, options);\n \n-\tswitch (meta_charset.from) {\n+\tswitch (meta_charset.origin) {\n \tcase CHARSET_DEFAULT:\n \t\tmi.metainfo_charset = get_commit_output_encoding();\n \t\tbreak;\n-\tcase CHARSET_NONE:\n+\tcase CHARSET_NO_REENCODE:\n \t\tmi.metainfo_charset = NULL;\n \t\tbreak;\n \tcase CHARSET_EXPLICIT:\n@@ -75,8 +90,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tmi.input = stdin;\n \tmi.output = stdout;\n \n-\tmsgfile = prefix_filename(prefix, argv[1]);\n-\tpatchfile = prefix_filename(prefix, argv[2]);\n+\tmsgfile = prefix_filename(prefix, argv[0]);\n+\tpatchfile = prefix_filename(prefix, argv[1]);\n \n \tstatus = !!mailinfo(&mi, msgfile, patchfile);\n \tclear_mailinfo(&mi);\n-- \n2.31.1.500.gbc6bbdd36b\n\n"},{"id":"423782","messageId":"1fb08bb37dc534296b3bf89efc9bfaad67a9b068.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"[PATCH v3 2/6] mailinfo: stop parsing options manually","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:20Z","receivedAt":"2021-05-06T15:02:49Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In a later change, mailinfo will learn more options, let's switch to our\nrobust parse_options framework before that step.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n builtin/mailinfo.c | 75 +++++++++++++++++++++++++++-------------------\n 1 file changed, 45 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex 77f96177cc..f55549a097 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -7,9 +7,13 @@\n #include \"utf8.h\"\n #include \"strbuf.h\"\n #include \"mailinfo.h\"\n+#include \"parse-options.h\"\n \n-static const char mailinfo_usage[] =\n-\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n+static const char * const mailinfo_usage[] = {\n+\t/* TRANSLATORS: keep <> in \"<\" mail \">\" info. */\n+\tN_(\"git mailinfo [<options>] <msg> <patch> < mail >info\"),\n+\tNULL,\n+};\n \n struct metainfo_charset\n {\n@@ -21,6 +25,19 @@ struct metainfo_charset\n \tconst char *charset;\n };\n \n+static int parse_opt_explicit_encoding(const struct option *opt,\n+\t\t\t\t       const char *arg, int unset)\n+{\n+\tstruct metainfo_charset *meta_charset = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tmeta_charset->policy = CHARSET_EXPLICIT;\n+\tmeta_charset->charset = arg;\n+\n+\treturn 0;\n+}\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n \tstruct metainfo_charset meta_charset;\n@@ -28,36 +45,34 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tint status;\n \tchar *msgfile, *patchfile;\n \n+\tstruct option options[] = {\n+\t\tOPT_BOOL('k', NULL, &mi.keep_subject, N_(\"keep subject\")),\n+\t\tOPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,\n+\t\t\t N_(\"keep non patch brackets in subject\")),\n+\t\tOPT_BOOL('m', \"message-id\", &mi.add_message_id,\n+\t\t\t N_(\"copy Message-ID to the end of commit message\")),\n+\t\tOPT_SET_INT_F('u', NULL, &meta_charset.policy,\n+\t\t\t      N_(\"re-code metadata to i18n.commitEncoding\"),\n+\t\t\t      CHARSET_DEFAULT, PARSE_OPT_NONEG),\n+\t\tOPT_SET_INT_F('n', NULL, &meta_charset.policy,\n+\t\t\t      N_(\"disable charset re-coding of metadata\"),\n+\t\t\t      CHARSET_NO_REENCODE, PARSE_OPT_NONEG),\n+\t\tOPT_CALLBACK_F(0, \"encoding\", &meta_charset, N_(\"encoding\"),\n+\t\t\t       N_(\"re-code metadata to this encoding\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_explicit_encoding),\n+\t\tOPT_BOOL(0, \"scissors\", &mi.use_scissors, N_(\"use scissors\")),\n+\t\tOPT_HIDDEN_BOOL(0, \"inbody-headers\", &mi.use_inbody_headers,\n+\t\t\t N_(\"use headers in message's body\")),\n+\t\tOPT_END()\n+\t};\n+\n \tsetup_mailinfo(&mi);\n \tmeta_charset.policy = CHARSET_DEFAULT;\n \n-\twhile (1 < argc && argv[1][0] == '-') {\n-\t\tif (!strcmp(argv[1], \"-k\"))\n-\t\t\tmi.keep_subject = 1;\n-\t\telse if (!strcmp(argv[1], \"-b\"))\n-\t\t\tmi.keep_non_patch_brackets_in_subject = 1;\n-\t\telse if (!strcmp(argv[1], \"-m\") || !strcmp(argv[1], \"--message-id\"))\n-\t\t\tmi.add_message_id = 1;\n-\t\telse if (!strcmp(argv[1], \"-u\"))\n-\t\t\tmeta_charset.policy = CHARSET_DEFAULT;\n-\t\telse if (!strcmp(argv[1], \"-n\"))\n-\t\t\tmeta_charset.policy = CHARSET_NO_REENCODE;\n-\t\telse if (starts_with(argv[1], \"--encoding=\")) {\n-\t\t\tmeta_charset.policy = CHARSET_EXPLICIT;\n-\t\t\tmeta_charset.charset = argv[1] + 11;\n-\t\t} else if (!strcmp(argv[1], \"--scissors\"))\n-\t\t\tmi.use_scissors = 1;\n-\t\telse if (!strcmp(argv[1], \"--no-scissors\"))\n-\t\t\tmi.use_scissors = 0;\n-\t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n-\t\t\tmi.use_inbody_headers = 0;\n-\t\telse\n-\t\t\tusage(mailinfo_usage);\n-\t\targc--; argv++;\n-\t}\n+\targc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);\n \n-\tif (argc != 3)\n-\t\tusage(mailinfo_usage);\n+\tif (argc != 2)\n+\t\tusage_with_options(mailinfo_usage, options);\n \n \tswitch (meta_charset.policy) {\n \tcase CHARSET_DEFAULT:\n@@ -75,8 +90,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tmi.input = stdin;\n \tmi.output = stdout;\n \n-\tmsgfile = prefix_filename(prefix, argv[1]);\n-\tpatchfile = prefix_filename(prefix, argv[2]);\n+\tmsgfile = prefix_filename(prefix, argv[0]);\n+\tpatchfile = prefix_filename(prefix, argv[1]);\n \n \tstatus = !!mailinfo(&mi, msgfile, patchfile);\n \tclear_mailinfo(&mi);\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423783","messageId":"5aac2ba38e5442019d2816c63d7b65bf2bcf062a.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"[PATCH v3 3/6] mailinfo: warn if CR found in decoded base64/QP email","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:21Z","receivedAt":"2021-05-06T15:02:51Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When SMTP servers receive 8-bit email messages, possibly with only\nLF as line ending, some of them decide to change said LF to CRLF.\n\nSome mailing list softwares, when receive 8-bit email messages,\ndecide to encode those messages in base64 or quoted-printable.\n\nIf an email is transfered through above mail servers, then distributed\nby such mailing list softwares, the recipients will receive an email\ncontains a patch mungled with CRLF encoded inside another encoding.\n\nThus, such CR couldn't be dropped by \"mailsplit\".\nHence, the mailed patch couldn't be applied cleanly.\nSuch accidents have been observed in the wild [1].\n\nInstead of silently rejecting those messages, let's give our users\nsome warnings if such CR is found.\n\n[1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n mailinfo.c              | 14 ++++++++++++++\n mailinfo.h              |  1 +\n t/t5100-mailinfo.sh     | 15 +++++++++++++++\n t/t5100/quoted-cr-info  |  5 +++++\n t/t5100/quoted-cr-msg   |  2 ++\n t/t5100/quoted-cr-patch | 22 ++++++++++++++++++++++\n t/t5100/quoted-cr.mbox  | 22 ++++++++++++++++++++++\n 7 files changed, 81 insertions(+)\n create mode 100644 t/t5100/quoted-cr-info\n create mode 100644 t/t5100/quoted-cr-msg\n create mode 100644 t/t5100/quoted-cr-patch\n create mode 100644 t/t5100/quoted-cr.mbox\n\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 5681d9130d..dcf579700d 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -994,6 +994,11 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \tconst char *rest;\n \n \tif (!mi->format_flowed) {\n+\t\tif (len >= 2 &&\n+\t\t    line->buf[len - 2] == '\\r' &&\n+\t\t    line->buf[len - 1] == '\\n') {\n+\t\t\tmi->have_quoted_cr = 1;\n+\t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n \t}\n@@ -1033,6 +1038,12 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \thandle_filter(mi, line);\n }\n \n+static void summarize_quoted_cr(struct mailinfo *mi)\n+{\n+\tif (mi->have_quoted_cr)\n+\t\twarning(\"quoted CR detected\");\n+}\n+\n static void handle_body(struct mailinfo *mi, struct strbuf *line)\n {\n \tstruct strbuf prev = STRBUF_INIT;\n@@ -1051,6 +1062,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \t\t\t\thandle_filter(mi, &prev);\n \t\t\t\tstrbuf_reset(&prev);\n \t\t\t}\n+\t\t\tsummarize_quoted_cr(mi);\n+\t\t\tmi->have_quoted_cr = 0;\n \t\t\tif (!handle_boundary(mi, line))\n \t\t\t\tgoto handle_body_out;\n \t\t}\n@@ -1100,6 +1113,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \n \tif (prev.len)\n \t\thandle_filter(mi, &prev);\n+\tsummarize_quoted_cr(mi);\n \n \tflush_inbody_header_accum(mi);\n \ndiff --git a/mailinfo.h b/mailinfo.h\nindex 79b1d6774e..b394ef9bce 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -24,6 +24,7 @@ struct mailinfo {\n \tstruct strbuf charset;\n \tunsigned int format_flowed:1;\n \tunsigned int delsp:1;\n+\tunsigned int have_quoted_cr:1;\n \tchar *message_id;\n \tenum  {\n \t\tTE_DONTCARE, TE_QP, TE_BASE64\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 147e616533..c7ea1b30df 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -228,4 +228,19 @@ test_expect_success 'mailinfo handles unusual header whitespace' '\n \ttest_cmp expect actual\n '\n \n+check_quoted_cr_mail () {\n+\tgit mailinfo -u \"$@\" quoted-cr-msg quoted-cr-patch \\\n+\t\t<\"$DATA/quoted-cr.mbox\" >quoted-cr-info 2>quoted-cr-err &&\n+\ttest_cmp \"expect-cr-msg\" quoted-cr-msg &&\n+\ttest_cmp \"expect-cr-patch\" quoted-cr-patch &&\n+\ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n+}\n+\n+test_expect_success 'mailinfo warn CR in base64 encoded email' '\n+\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n+\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n+\tcheck_quoted_cr_mail &&\n+\tgrep \"quoted CR detected\" quoted-cr-err\n+'\n+\n test_done\ndiff --git a/t/t5100/quoted-cr-info b/t/t5100/quoted-cr-info\nnew file mode 100644\nindex 0000000000..dab2228b70\n--- /dev/null\n+++ b/t/t5100/quoted-cr-info\n@@ -0,0 +1,5 @@\n+Author: A U Thor\n+Email: mail@example.com\n+Subject: sample\n+Date: Mon, 3 Aug 2020 22:40:55 +0700\n+\ndiff --git a/t/t5100/quoted-cr-msg b/t/t5100/quoted-cr-msg\nnew file mode 100644\nindex 0000000000..a148bc7e26\n--- /dev/null\n+++ b/t/t5100/quoted-cr-msg\n@@ -0,0 +1,2 @@\n+On different distro, pytest is suffixed with different patterns.%%\n+%%\ndiff --git a/t/t5100/quoted-cr-patch b/t/t5100/quoted-cr-patch\nnew file mode 100644\nindex 0000000000..580e2bddb8\n--- /dev/null\n+++ b/t/t5100/quoted-cr-patch\n@@ -0,0 +1,22 @@\n+---%%\n+ configure | 2 +-%%\n+ 1 file changed, 1 insertion(+), 1 deletion(-)%%\n+%%\n+diff --git a/configure b/configure%%\n+index db3538b3..f7c1c095 100755%%\n+--- a/configure%%\n++++ b/configure%%\n+@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%\n+     printf \"Checking for python3 pytest (>= 3.0)... \"%%\n+     conf=$(mktemp)%%\n+     printf \"[pytest]\\nminversion=3.0\\n\" > $conf%%\n+-    if pytest-3 -c $conf --version >/dev/null 2>&1; then%%\n++    if \"$python\" -m pytest -c $conf --version >/dev/null 2>&1; then%%\n+         printf \"Yes.\\n\"%%\n+         have_python3_pytest=1%%\n+     else%%\n+-- %%\n+2.28.0%%\n+_______________________________________________\n+example mailing list -- list@example.org\n+To unsubscribe send an email to list-leave@example.org\ndiff --git a/t/t5100/quoted-cr.mbox b/t/t5100/quoted-cr.mbox\nnew file mode 100644\nindex 0000000000..6ea9806a6b\n--- /dev/null\n+++ b/t/t5100/quoted-cr.mbox\n@@ -0,0 +1,22 @@\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+T24gZGlmZmVyZW50IGRpc3RybywgcHl0ZXN0IGlzIHN1ZmZpeGVkIHdpdGggZGlmZmVyZW50IHBh\n+dHRlcm5zLg0KDQotLS0NCiBjb25maWd1cmUgfCAyICstDQogMSBmaWxlIGNoYW5nZWQsIDEgaW5z\n+ZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS9jb25maWd1cmUgYi9jb25m\n+aWd1cmUNCmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUNCi0tLSBhL2NvbmZpZ3VyZQ0K\n+KysrIGIvY29uZmlndXJlDQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAt\n+ZXEgMSBdOyB0aGVuDQogICAgIHByaW50ZiAiQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+\n+PSAzLjApLi4uICINCiAgICAgY29uZj0kKG1rdGVtcCkNCiAgICAgcHJpbnRmICJbcHl0ZXN0XVxu\n+bWludmVyc2lvbj0zLjBcbiIgPiAkY29uZg0KLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZl\n+cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQorICAgIGlmICIkcHl0aG9uIiAtbSBweXRlc3Qg\n+LWMgJGNvbmYgLS12ZXJzaW9uID4vZGV2L251bGwgMj4mMTsgdGhlbg0KICAgICAgICAgcHJpbnRm\n+ICJZZXMuXG4iDQogICAgICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTENCiAgICAgZWxzZQ0KLS0g\n+DQoyLjI4LjANCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\n+CmV4YW1wbGUgbWFpbGluZyBsaXN0IC0tIGxpc3RAZXhhbXBsZS5vcmcKVG8gdW5zdWJzY3JpYmUg\n+c2VuZCBhbiBlbWFpbCB0byBsaXN0LWxlYXZlQGV4YW1wbGUub3JnCg==\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423784","messageId":"d5b2da370d072489b76d6883f01f309ebb1f3266.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"[PATCH v3 4/6] mailinfo: allow squelching quoted CR warning","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:22Z","receivedAt":"2021-05-06T15:02:52Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous change, Git starts to warn for quoted CR in decoded\nbase64/QP email. Despite those warnings are usually helpful,\nquoted CR could be part of some users' workflow.\n\nLet's give them an option to turn off the warning completely.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-mailinfo.txt | 20 +++++++++++++++++++-\n builtin/mailinfo.c             | 12 ++++++++++++\n mailinfo.c                     | 20 +++++++++++++++++++-\n mailinfo.h                     |  7 +++++++\n t/t5100-mailinfo.sh            |  6 ++++--\n 5 files changed, 61 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\nindex d343f040f5..824947a070 100644\n--- a/Documentation/git-mailinfo.txt\n+++ b/Documentation/git-mailinfo.txt\n@@ -9,7 +9,9 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message\n SYNOPSIS\n --------\n [verse]\n-'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>\n+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n]\n+\t       [--[no-]scissors] [--quoted-cr=<action>]\n+\t       <msg> <patch>\n \n \n DESCRIPTION\n@@ -89,6 +91,22 @@ This can be enabled by default with the configuration option mailinfo.scissors.\n --no-scissors::\n \tIgnore scissors lines. Useful for overriding mailinfo.scissors settings.\n \n+--quoted-cr=<action>::\n+\tAction when processes email messages sent with base64 or\n+\tquoted-printable encoding, and the decoded lines end with a CRLF\n+\tinstead of a simple LF.\n++\n+The valid actions are:\n++\n+--\n+*\t`nowarn`: Git will do nothing when such a CRLF is found.\n+*\t`warn`: Git will issue a warning for each message if such a CRLF is\n+\tfound.\n+--\n++\n+The default action could be set by configuration option `mailinfo.quotedCR`.\n+If no such configuration option has been set, `warn` will be used.\n+\n <msg>::\n \tThe commit log message extracted from e-mail, usually\n \texcept the title line which comes from e-mail Subject.\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex f55549a097..01d16ef9e5 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -38,6 +38,15 @@ static int parse_opt_explicit_encoding(const struct option *opt,\n \treturn 0;\n }\n \n+static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)\n+{\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)\n+\t\treturn error(_(\"bad action '%s' for '%s'\"), arg, \"--quoted-cr\");\n+\treturn 0;\n+}\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n \tstruct metainfo_charset meta_charset;\n@@ -61,6 +70,9 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \t\t\t       N_(\"re-code metadata to this encoding\"),\n \t\t\t       PARSE_OPT_NONEG, parse_opt_explicit_encoding),\n \t\tOPT_BOOL(0, \"scissors\", &mi.use_scissors, N_(\"use scissors\")),\n+\t\tOPT_CALLBACK_F(0, \"quoted-cr\", &mi.quoted_cr, N_(\"<action>\"),\n+\t\t\t       N_(\"action when quoted CR is found\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_quoted_cr),\n \t\tOPT_HIDDEN_BOOL(0, \"inbody-headers\", &mi.use_inbody_headers,\n \t\t\t N_(\"use headers in message's body\")),\n \t\tOPT_END()\ndiff --git a/mailinfo.c b/mailinfo.c\nindex dcf579700d..c57eed25a5 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1040,7 +1040,8 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \n static void summarize_quoted_cr(struct mailinfo *mi)\n {\n-\tif (mi->have_quoted_cr)\n+\tif (mi->have_quoted_cr &&\n+\t    mi->quoted_cr == quoted_cr_warn)\n \t\twarning(\"quoted CR detected\");\n }\n \n@@ -1220,6 +1221,17 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n \treturn mi->input_error;\n }\n \n+int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n+{\n+\tif (!strcmp(actionstr, \"nowarn\"))\n+\t\t*action = quoted_cr_nowarn;\n+\telse if (!strcmp(actionstr, \"warn\"))\n+\t\t*action = quoted_cr_warn;\n+\telse\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n@@ -1230,6 +1242,11 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"mailinfo.quotedcr\")) {\n+\t\tif (mailinfo_parse_quoted_cr_action(value, &mi->quoted_cr) != 0)\n+\t\t\treturn error(_(\"bad action '%s' for '%s'\"), value, var);\n+\t\treturn 0;\n+\t}\n \t/* perhaps others here */\n \treturn 0;\n }\n@@ -1242,6 +1259,7 @@ void setup_mailinfo(struct mailinfo *mi)\n \tstrbuf_init(&mi->charset, 0);\n \tstrbuf_init(&mi->log_message, 0);\n \tstrbuf_init(&mi->inbody_header_accum, 0);\n+\tmi->quoted_cr = quoted_cr_warn;\n \tmi->header_stage = 1;\n \tmi->use_inbody_headers = 1;\n \tmi->content_top = mi->content;\ndiff --git a/mailinfo.h b/mailinfo.h\nindex b394ef9bce..768d06ac2a 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -5,6 +5,11 @@\n \n #define MAX_BOUNDARIES 5\n \n+enum quoted_cr_action {\n+\tquoted_cr_nowarn,\n+\tquoted_cr_warn,\n+};\n+\n struct mailinfo {\n \tFILE *input;\n \tFILE *output;\n@@ -14,6 +19,7 @@ struct mailinfo {\n \tstruct strbuf email;\n \tint keep_subject;\n \tint keep_non_patch_brackets_in_subject;\n+\tint quoted_cr; /* enum quoted_cr_action */\n \tint add_message_id;\n \tint use_scissors;\n \tint use_inbody_headers;\n@@ -40,6 +46,7 @@ struct mailinfo {\n \tint input_error;\n };\n \n+int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action);\n void setup_mailinfo(struct mailinfo *);\n int mailinfo(struct mailinfo *, const char *msg, const char *patch);\n void clear_mailinfo(struct mailinfo *);\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex c7ea1b30df..51e8690fbd 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -236,11 +236,13 @@ check_quoted_cr_mail () {\n \ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n }\n \n-test_expect_success 'mailinfo warn CR in base64 encoded email' '\n+test_expect_success 'mailinfo handle CR in base64 encoded email' '\n \tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n \tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n \tcheck_quoted_cr_mail &&\n-\tgrep \"quoted CR detected\" quoted-cr-err\n+\tgrep \"quoted CR detected\" quoted-cr-err &&\n+\tcheck_quoted_cr_mail --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr-err\n '\n \n test_done\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423785","messageId":"9e96d4bf5e06df57b13af1dec354ca5cf2ea58a1.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"[PATCH v3 5/6] mailinfo: allow stripping quoted CR without warning","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:23Z","receivedAt":"2021-05-06T15:02:54Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous changes, we've turned on warning for quoted CR in base64 or\nquoted-printable email messages. Some projects see those quoted CR a lot,\nthey know that it happens most of the time, and they find it's desirable\nto always strip those CR.\n\nThose projects in question usually fall back to use other tools to handle\npatches when receive such patches.\n\nLet's help those projects handle those patches by stripping those\nexcessive CR.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-mailinfo.txt | 1 +\n mailinfo.c                     | 7 +++++++\n mailinfo.h                     | 1 +\n t/t5100-mailinfo.sh            | 4 ++++\n 4 files changed, 13 insertions(+)\n\ndiff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\nindex 824947a070..3fcfd965fd 100644\n--- a/Documentation/git-mailinfo.txt\n+++ b/Documentation/git-mailinfo.txt\n@@ -102,6 +102,7 @@ The valid actions are:\n *\t`nowarn`: Git will do nothing when such a CRLF is found.\n *\t`warn`: Git will issue a warning for each message if such a CRLF is\n \tfound.\n+*\t`strip`: Git will convert those CRLF to LF.\n --\n +\n The default action could be set by configuration option `mailinfo.quotedCR`.\ndiff --git a/mailinfo.c b/mailinfo.c\nindex c57eed25a5..5cddcdc8ae 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -998,6 +998,11 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \t\t    line->buf[len - 2] == '\\r' &&\n \t\t    line->buf[len - 1] == '\\n') {\n \t\t\tmi->have_quoted_cr = 1;\n+\t\t\tif (mi->quoted_cr == quoted_cr_strip) {\n+\t\t\t\tstrbuf_setlen(line, len - 2);\n+\t\t\t\tstrbuf_addch(line, '\\n');\n+\t\t\t\tlen--;\n+\t\t\t}\n \t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n@@ -1227,6 +1232,8 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \t\t*action = quoted_cr_nowarn;\n \telse if (!strcmp(actionstr, \"warn\"))\n \t\t*action = quoted_cr_warn;\n+\telse if (!strcmp(actionstr, \"strip\"))\n+\t\t*action = quoted_cr_strip;\n \telse\n \t\treturn -1;\n \treturn 0;\ndiff --git a/mailinfo.h b/mailinfo.h\nindex 768d06ac2a..2ddf8be90f 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -8,6 +8,7 @@\n enum quoted_cr_action {\n \tquoted_cr_nowarn,\n \tquoted_cr_warn,\n+\tquoted_cr_strip,\n };\n \n struct mailinfo {\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 51e8690fbd..06807f3852 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -242,6 +242,10 @@ test_expect_success 'mailinfo handle CR in base64 encoded email' '\n \tcheck_quoted_cr_mail &&\n \tgrep \"quoted CR detected\" quoted-cr-err &&\n \tcheck_quoted_cr_mail --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr-err &&\n+\tsed \"s/%%//\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n+\tsed \"s/%%//\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n+\tcheck_quoted_cr_mail --quoted-cr=strip &&\n \ttest_must_be_empty quoted-cr-err\n '\n \n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423786","messageId":"d6aa12acc0c0e11e2268ff344fa63cf5edf51290.1620311544.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"[PATCH v3 6/6] am: learn to process quoted lines that ends with CRLF","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:02:24Z","receivedAt":"2021-05-06T15:02:58Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous changes, mailinfo has learnt to process lines that decoded\nfrom base64 or quoted-printable, and ends with CRLF.\n\nLet's teach \"am\" that new trick, too.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-am.txt               |  4 ++\n builtin/am.c                           | 51 ++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 +++\n mailinfo.h                             |  1 +\n t/t4258-am-quoted-cr.sh                | 37 +++++++++++++++++++\n t/t4258/mbox                           | 12 ++++++\n 6 files changed, 110 insertions(+)\n create mode 100755 t/t4258-am-quoted-cr.sh\n create mode 100644 t/t4258/mbox\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex decd8ae122..8714dfcb76 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n \t [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n+\t [--quoted-cr=<action>]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -59,6 +60,9 @@ OPTIONS\n --no-scissors::\n \tIgnore scissors lines (see linkgit:git-mailinfo[1]).\n \n+--quoted-cr=<action>::\n+\tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8355e3566f..0b2d886c81 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -116,6 +116,7 @@ struct am_state {\n \tint keep; /* enum keep_type */\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n+\tint quoted_cr; /* enum quoted_cr_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -145,6 +146,7 @@ static void am_state_init(struct am_state *state)\n \tgit_config_get_bool(\"am.messageid\", &state->message_id);\n \n \tstate->scissors = SCISSORS_UNSET;\n+\tstate->quoted_cr = quoted_cr_unset;\n \n \tstrvec_init(&state->git_apply_opts);\n \n@@ -165,6 +167,16 @@ static void am_state_release(struct am_state *state)\n \tstrvec_clear(&state->git_apply_opts);\n }\n \n+static int am_option_parse_quoted_cr(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)\n+\t\treturn error(_(\"bad action '%s' for '%s'\"), arg, \"--quoted-cr\");\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -397,6 +409,12 @@ static void am_load(struct am_state *state)\n \telse\n \t\tstate->scissors = SCISSORS_UNSET;\n \n+\tread_state_file(&sb, state, \"quoted-cr\", 1);\n+\tif (!*sb.buf)\n+\t\tstate->quoted_cr = quoted_cr_unset;\n+\telse if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)\n+\t\tdie(_(\"could not parse %s\"), am_path(state, \"quoted-cr\"));\n+\n \tread_state_file(&sb, state, \"apply-opt\", 1);\n \tstrvec_clear(&state->git_apply_opts);\n \tif (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)\n@@ -1002,6 +1020,24 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t}\n \twrite_state_text(state, \"scissors\", str);\n \n+\tswitch (state->quoted_cr) {\n+\tcase quoted_cr_unset:\n+\t\tstr = \"\";\n+\t\tbreak;\n+\tcase quoted_cr_nowarn:\n+\t\tstr = \"nowarn\";\n+\t\tbreak;\n+\tcase quoted_cr_warn:\n+\t\tstr = \"warn\";\n+\t\tbreak;\n+\tcase quoted_cr_strip:\n+\t\tstr = \"strip\";\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid value for state->quoted_cr\");\n+\t}\n+\twrite_state_text(state, \"quoted-cr\", str);\n+\n \tsq_quote_argv(&sb, state->git_apply_opts.v);\n \twrite_state_text(state, \"apply-opt\", sb.buf);\n \n@@ -1162,6 +1198,18 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tBUG(\"invalid value for state->scissors\");\n \t}\n \n+\tswitch (state->quoted_cr) {\n+\tcase quoted_cr_unset:\n+\t\tbreak;\n+\tcase quoted_cr_nowarn:\n+\tcase quoted_cr_warn:\n+\tcase quoted_cr_strip:\n+\t\tmi.quoted_cr = state->quoted_cr;\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid value for state->quoted_cr\");\n+\t}\n+\n \tmi.input = xfopen(mail, \"r\");\n \tmi.output = xfopen(am_path(state, \"info\"), \"w\");\n \tif (mailinfo(&mi, am_path(state, \"msg\"), am_path(state, \"patch\")))\n@@ -2242,6 +2290,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\t0, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('c', \"scissors\", &state.scissors,\n \t\t\tN_(\"strip everything before a scissors line\")),\n+\t\tOPT_CALLBACK_F(0, \"quoted-cr\", &state.quoted_cr, N_(\"action\"),\n+\t\t\t       N_(\"pass it through git-mailinfo\"),\n+\t\t\t       PARSE_OPT_NONEG, am_option_parse_quoted_cr),\n \t\tOPT_PASSTHRU_ARGV(0, \"whitespace\", &state.git_apply_opts, N_(\"action\"),\n \t\t\tN_(\"pass it through git-apply\"),\n \t\t\t0),\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 49e76e9d08..edf635095e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1333,6 +1333,7 @@ __git_whitespacelist=\"nowarn warn error error-all fix\"\n __git_patchformat=\"mbox stgit stgit-series hg mboxrd\"\n __git_showcurrentpatch=\"diff raw\"\n __git_am_inprogress_options=\"--skip --continue --resolved --abort --quit --show-current-patch\"\n+__git_quoted_cr=\"nowarn warn strip\"\n \n _git_am ()\n {\n@@ -1354,6 +1355,10 @@ _git_am ()\n \t\t__gitcomp \"$__git_showcurrentpatch\" \"\" \"${cur##--show-current-patch=}\"\n \t\treturn\n \t\t;;\n+\t--quoted-cr=*)\n+\t\t__gitcomp \"$__git_quoted_cr\" \"\" \"${cur##--quoted-cr=}\"\n+\t\treturn\n+\t\t;;\n \t--*)\n \t\t__gitcomp_builtin am \"\" \\\n \t\t\t\"$__git_am_inprogress_options\"\ndiff --git a/mailinfo.h b/mailinfo.h\nindex 2ddf8be90f..f2ffd0349e 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -6,6 +6,7 @@\n #define MAX_BOUNDARIES 5\n \n enum quoted_cr_action {\n+\tquoted_cr_unset = -1,\n \tquoted_cr_nowarn,\n \tquoted_cr_warn,\n \tquoted_cr_strip,\ndiff --git a/t/t4258-am-quoted-cr.sh b/t/t4258-am-quoted-cr.sh\nnew file mode 100755\nindex 0000000000..2029115ecd\n--- /dev/null\n+++ b/t/t4258-am-quoted-cr.sh\n@@ -0,0 +1,37 @@\n+#!/bin/sh\n+\n+test_description='test am --quoted-cr=<action>'\n+\n+. ./test-lib.sh\n+\n+DATA=\"$TEST_DIRECTORY/t4258\"\n+\n+test_expect_success 'setup' '\n+\ttest_write_lines one two three >text &&\n+\ttest_commit one text &&\n+\ttest_write_lines one owt three >text &&\n+\ttest_commit two text\n+'\n+\n+test_expect_success 'am warn if quoted-cr is found' '\n+\tgit reset --hard one &&\n+\ttest_must_fail git am \"$DATA/mbox\" 2>err &&\n+\tgrep \"quoted CR detected\" err\n+'\n+\n+test_expect_success 'am strip if quoted-cr is found' '\n+\ttest_might_fail git am --abort &&\n+\tgit reset --hard one &&\n+\tgit am --quoted-cr=strip \"$DATA/mbox\" &&\n+\tgit diff --exit-code HEAD two\n+'\n+\n+test_expect_success 'am strip if quoted-cr is found' '\n+\ttest_might_fail git am --abort &&\n+\tgit reset --hard one &&\n+\ttest_config mailinfo.quotedCr strip &&\n+\tgit am \"$DATA/mbox\" &&\n+\tgit diff --exit-code HEAD two\n+'\n+\n+test_done\ndiff --git a/t/t4258/mbox b/t/t4258/mbox\nnew file mode 100644\nindex 0000000000..c62819f3d2\n--- /dev/null\n+++ b/t/t4258/mbox\n@@ -0,0 +1,12 @@\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+VGhpcyBpcyBjb21taXQgbWVzc2FnZS4NCi0tLQ0KIHRleHQgfCAyICstDQogMSBmaWxlIGNoYW5n\n+ZWQsIDEgaW5zZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS90ZXh0IGIv\n+dGV4dA0KaW5kZXggNTYyNmFiZi4uZjcxOWVmZCAxMDA2NDQNCi0tLSBhL3RleHQNCisrKyBiL3Rl\n+eHQNCkBAIC0xICsxIEBADQotb25lDQordHdvDQotLSANCjIuMzEuMQoK\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423788","messageId":"YJQJHK04CSv17Jtw@danh.dev","threadId":"55533","inReplyTo":"ba8ac434189ce91314562d93004b2d2b37fe6868.1620309355.git.congdanhqx@gmail.com","subject":"Re: [PATCH v3 2/6] mailinfo: stop parse options manually","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-06T15:19:56Z","receivedAt":"2021-05-06T15:20:05Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-05-06 22:02:19+0700, Đoàn Trần Công Danh <congdanhqx@gmail.com> wrote:\n> In a later change, mailinfo will learn more options, let's switch to our\n> robust parse_options framework before that step.\n\nPlease ignore this email. The correct patch is down-thread with\n subject:\n\n* [PATCH v3 2/6] mailinfo: stop parsing options manually\n\n-- \nDanh\n"},{"id":"423926","messageId":"xmqq4kfdv718.fsf@gitster.g","threadId":"55533","inReplyTo":"1fb08bb37dc534296b3bf89efc9bfaad67a9b068.1620311544.git.congdanhqx@gmail.com","subject":"Re: [PATCH v3 2/6] mailinfo: stop parsing options manually","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-08T10:44:51Z","receivedAt":"2021-05-08T10:44:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> In a later change, mailinfo will learn more options, let's switch to our\n> robust parse_options framework before that step.\n>\n> Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n> ---\n>  builtin/mailinfo.c | 75 +++++++++++++++++++++++++++-------------------\n>  1 file changed, 45 insertions(+), 30 deletions(-)\n\nLooks quite straight-forward, thanks to the previous step.\n\nNicely done.\n"},{"id":"423927","messageId":"xmqqzgx5ts3z.fsf@gitster.g","threadId":"55533","inReplyTo":"5aac2ba38e5442019d2816c63d7b65bf2bcf062a.1620311544.git.congdanhqx@gmail.com","subject":"Re: [PATCH v3 3/6] mailinfo: warn if CR found in decoded base64/QP email","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-08T10:52:32Z","receivedAt":"2021-05-08T10:52:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> +test_expect_success 'mailinfo warn CR in base64 encoded email' '\n> +\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n> +\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n\nAs we are not interested in a lone CR in the middle of the line, I\nwonder if we want to anchor the double per-cent to the end of the\nline, i.e. \"s/%%$/$(printf \\\\015)/\".\n\nOn the other hand, we may want to make sure that the new option does\nnot disturb CR in the middle of the line, so we may be better off\nleaving double per-cent unanchored, but allow replacing more than\nonce on a line, i.e. \"s/%%/$(printf \\\\015)/g\".\n\nIf we were to go to the latter route, we'd want to try a file with a\nCR in the middle of the line (without a CR at the end of any line)\nand make sure we won't warn or strip.\n"},{"id":"423928","messageId":"xmqqv97ttrw0.fsf@gitster.g","threadId":"55533","inReplyTo":"cover.1620311544.git.congdanhqx@gmail.com","subject":"Re: [PATCH v3 0/6] Teach am/mailinfo to process quoted CR","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-08T10:57:19Z","receivedAt":"2021-05-08T10:57:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Đoàn Trần Công Danh  <congdanhqx@gmail.com> writes:\n\n> When SMTP servers receive 8-bit email messages, possibly with only\n> LF as line ending, some of those servers decide to change said LF to\n> CRLF.\n>\n> Some mailing list software, when receives an 8-bit email message,\n> decide to encode such message in base64 or quoted-printable.\n>\n> This series try to help users of such softwares deal with such patches.\n\nThanks.  Use of parse_options() did make the overall series longer,\nbut the result looks a lot nicer.\n\nI've left a few comments, but overall the series looks almost\nperfect ;-)\n"},{"id":"423967","messageId":"cover.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"20210421013404.17383-1-congdanhqx@gmail.com","subject":"[PATCH v4 0/6] Teach am/mailinfo to process quoted CR","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:07Z","receivedAt":"2021-05-09T17:12:28Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When SMTP servers receive 8-bit email messages, possibly with only\nLF as line ending, some of those servers decide to change said LF to\nCRLF.\n\nSome mailing list software, when receives an 8-bit email message,\ndecide to encode such message in base64 or quoted-printable.\n\nThis series try to help users of such softwares deal with such patches.\n\nChange in v4:\n* Mark warning message for l10n\n* Change all CR references to CRLF\n* Add test case for not warn/strip if CR is found in the middle of the line.\n\nChange in v3:\n* Change preparatory step (in mailinfo) to use parse_options\n* move \"have_quoted_cr\" to \"struct mailinfo\", thus the patch to warn about\n  quoted CR is less weird.\n* Change \"mailinfo_parse_quoted_cr\" to Git's do-some-work function, return\n  0 on success and negative on failure, eliminate the usage of _invalid_action\n* Better error messages if invalid action was given.\n* completion support for git am --quoted-cr\n* Some style changes\n\nĐoàn Trần Công Danh (6):\n  mailinfo: load default metainfo_charset lazily\n  mailinfo: stop parsing options manually\n  mailinfo: warn if CRLF found in decoded base64/QP email\n  mailinfo: allow squelching quoted CRLF warning\n  mailinfo: allow stripping quoted CR without warning\n  am: learn to process quoted lines that ends with CRLF\n\n Documentation/git-am.txt               |   4 +\n Documentation/git-mailinfo.txt         |  21 ++++-\n builtin/am.c                           |  51 +++++++++++\n builtin/mailinfo.c                     | 115 ++++++++++++++++++-------\n contrib/completion/git-completion.bash |   5 ++\n mailinfo.c                             |  39 +++++++++\n mailinfo.h                             |  10 +++\n t/t4258-am-quoted-cr.sh                |  37 ++++++++\n t/t4258/mbox                           |  12 +++\n t/t5100-mailinfo.sh                    |  40 +++++++++\n t/t5100/quoted-cr-info                 |   5 ++\n t/t5100/quoted-cr-msg                  |   2 +\n t/t5100/quoted-cr-patch                |  22 +++++\n t/t5100/quoted-cr.mbox                 |  47 ++++++++++\n 14 files changed, 376 insertions(+), 34 deletions(-)\n create mode 100755 t/t4258-am-quoted-cr.sh\n create mode 100644 t/t4258/mbox\n create mode 100644 t/t5100/quoted-cr-info\n create mode 100644 t/t5100/quoted-cr-msg\n create mode 100644 t/t5100/quoted-cr-patch\n create mode 100644 t/t5100/quoted-cr.mbox\n\nRange-diff against v3:\n1:  fac95392df = 1:  fac95392df mailinfo: load default metainfo_charset lazily\n2:  1fb08bb37d = 2:  1fb08bb37d mailinfo: stop parsing options manually\n3:  5aac2ba38e ! 3:  50404ffe74 mailinfo: warn if CR found in decoded base64/QP email\n    @@ Metadata\n     Author: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## Commit message ##\n    -    mailinfo: warn if CR found in decoded base64/QP email\n    +    mailinfo: warn if CRLF found in decoded base64/QP email\n     \n         When SMTP servers receive 8-bit email messages, possibly with only\n         LF as line ending, some of them decide to change said LF to CRLF.\n    @@ Commit message\n         by such mailing list softwares, the recipients will receive an email\n         contains a patch mungled with CRLF encoded inside another encoding.\n     \n    -    Thus, such CR couldn't be dropped by \"mailsplit\".\n    +    Thus, such CR (in CRLF) couldn't be dropped by \"mailsplit\".\n         Hence, the mailed patch couldn't be applied cleanly.\n         Such accidents have been observed in the wild [1].\n     \n         Instead of silently rejecting those messages, let's give our users\n    -    some warnings if such CR is found.\n    +    some warnings if such CR (as part of CRLF) is found.\n     \n         [1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n     \n    @@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf\n     +static void summarize_quoted_cr(struct mailinfo *mi)\n     +{\n     +\tif (mi->have_quoted_cr)\n    -+\t\twarning(\"quoted CR detected\");\n    ++\t\twarning(_(\"quoted CRLF detected\"));\n     +}\n     +\n      static void handle_body(struct mailinfo *mi, struct strbuf *line)\n    @@ t/t5100-mailinfo.sh: test_expect_success 'mailinfo handles unusual header whites\n      '\n      \n     +check_quoted_cr_mail () {\n    -+\tgit mailinfo -u \"$@\" quoted-cr-msg quoted-cr-patch \\\n    -+\t\t<\"$DATA/quoted-cr.mbox\" >quoted-cr-info 2>quoted-cr-err &&\n    -+\ttest_cmp \"expect-cr-msg\" quoted-cr-msg &&\n    -+\ttest_cmp \"expect-cr-patch\" quoted-cr-patch &&\n    -+\ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n    ++\tmail=\"$1\" && shift &&\n    ++\tgit mailinfo -u \"$@\" \"$mail.msg\" \"$mail.patch\" \\\n    ++\t\t<\"$mail\" >\"$mail.info\" 2>\"$mail.err\" &&\n    ++\ttest_cmp \"$mail-expected.msg\" \"$mail.msg\" &&\n    ++\ttest_cmp \"$mail-expected.patch\" \"$mail.patch\" &&\n    ++\ttest_cmp \"$DATA/quoted-cr-info\" \"$mail.info\"\n     +}\n     +\n    ++test_expect_success 'split base64 email with quoted-cr' '\n    ++\tmkdir quoted-cr &&\n    ++\tgit mailsplit -oquoted-cr \"$DATA/quoted-cr.mbox\" >quoted-cr/last &&\n    ++\ttest $(cat quoted-cr/last) = 2\n    ++'\n    ++\n     +test_expect_success 'mailinfo warn CR in base64 encoded email' '\n    -+\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n    -+\tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n    -+\tcheck_quoted_cr_mail &&\n    -+\tgrep \"quoted CR detected\" quoted-cr-err\n    ++\tsed -e \"s/%%$//\" -e \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-msg\" \\\n    ++\t\t>quoted-cr/0001-expected.msg &&\n    ++\tsed \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-msg\" \\\n    ++\t\t>quoted-cr/0002-expected.msg &&\n    ++\tsed -e \"s/%%$//\" -e \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-patch\" \\\n    ++\t\t>quoted-cr/0001-expected.patch &&\n    ++\tsed \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-patch\" \\\n    ++\t\t>quoted-cr/0002-expected.patch &&\n    ++\tcheck_quoted_cr_mail quoted-cr/0001 &&\n    ++\ttest_must_be_empty quoted-cr/0001.err &&\n    ++\tcheck_quoted_cr_mail quoted-cr/0002 &&\n    ++\tgrep \"quoted CRLF detected\" quoted-cr/0002.err\n     +'\n     +\n      test_done\n    @@ t/t5100/quoted-cr-info (new)\n     \n      ## t/t5100/quoted-cr-msg (new) ##\n     @@\n    -+On different distro, pytest is suffixed with different patterns.%%\n    ++On different distro, %%pytest is suffixed with different patterns.%%\n     +%%\n     \n      ## t/t5100/quoted-cr-patch (new) ##\n    @@ t/t5100/quoted-cr-patch (new)\n     +--- a/configure%%\n     ++++ b/configure%%\n     +@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%\n    -+     printf \"Checking for python3 pytest (>= 3.0)... \"%%\n    ++     printf \"%%Checking for python3 pytest (>= 3.0)... \"%%\n     +     conf=$(mktemp)%%\n     +     printf \"[pytest]\\nminversion=3.0\\n\" > $conf%%\n     +-    if pytest-3 -c $conf --version >/dev/null 2>&1; then%%\n    @@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%\n     \n      ## t/t5100/quoted-cr.mbox (new) ##\n     @@\n    ++From nobody Mon Sep 17 00:00:00 2001\n     +From: A U Thor <mail@example.com>\n     +To: list@example.org\n     +Subject: [PATCH v2] sample\n    @@ t/t5100/quoted-cr.mbox (new)\n     +Content-Type: text/plain; charset=\"utf-8\"\n     +Content-Transfer-Encoding: base64\n     +\n    -+T24gZGlmZmVyZW50IGRpc3RybywgcHl0ZXN0IGlzIHN1ZmZpeGVkIHdpdGggZGlmZmVyZW50IHBh\n    -+dHRlcm5zLg0KDQotLS0NCiBjb25maWd1cmUgfCAyICstDQogMSBmaWxlIGNoYW5nZWQsIDEgaW5z\n    -+ZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS9jb25maWd1cmUgYi9jb25m\n    -+aWd1cmUNCmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUNCi0tLSBhL2NvbmZpZ3VyZQ0K\n    -+KysrIGIvY29uZmlndXJlDQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAt\n    -+ZXEgMSBdOyB0aGVuDQogICAgIHByaW50ZiAiQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+\n    -+PSAzLjApLi4uICINCiAgICAgY29uZj0kKG1rdGVtcCkNCiAgICAgcHJpbnRmICJbcHl0ZXN0XVxu\n    -+bWludmVyc2lvbj0zLjBcbiIgPiAkY29uZg0KLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZl\n    -+cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQorICAgIGlmICIkcHl0aG9uIiAtbSBweXRlc3Qg\n    -+LWMgJGNvbmYgLS12ZXJzaW9uID4vZGV2L251bGwgMj4mMTsgdGhlbg0KICAgICAgICAgcHJpbnRm\n    -+ICJZZXMuXG4iDQogICAgICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTENCiAgICAgZWxzZQ0KLS0g\n    -+DQoyLjI4LjANCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\n    -+CmV4YW1wbGUgbWFpbGluZyBsaXN0IC0tIGxpc3RAZXhhbXBsZS5vcmcKVG8gdW5zdWJzY3JpYmUg\n    -+c2VuZCBhbiBlbWFpbCB0byBsaXN0LWxlYXZlQGV4YW1wbGUub3JnCg==\n    ++T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw\n    ++YXR0ZXJucy4KCi0tLQogY29uZmlndXJlIHwgMiArLQogMSBmaWxlIGNoYW5nZWQsIDEgaW5zZXJ0\n    ++aW9uKCspLCAxIGRlbGV0aW9uKC0pCgpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29uZmlndXJl\n    ++CmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUKLS0tIGEvY29uZmlndXJlCisrKyBiL2Nv\n    ++bmZpZ3VyZQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAtZXEgMSBdOyB0\n    ++aGVuCiAgICAgcHJpbnRmICINQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+PSAzLjApLi4u\n    ++ICIKICAgICBjb25mPSQobWt0ZW1wKQogICAgIHByaW50ZiAiW3B5dGVzdF1cbm1pbnZlcnNpb249\n    ++My4wXG4iID4gJGNvbmYKLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYv\n    ++bnVsbCAyPiYxOyB0aGVuCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVzdCAtYyAkY29uZiAtLXZl\n    ++cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAgICAgIHByaW50ZiAiWWVzLlxuIgogICAg\n    ++ICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTEKICAgICBlbHNlCi0tIAoyLjI4LjAKX19fX19fX19f\n    ++X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KZXhhbXBsZSBtYWlsaW5nIGxp\n    ++c3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmliZSBzZW5kIGFuIGVtYWlsIHRvIGxp\n    ++c3QtbGVhdmVAZXhhbXBsZS5vcmcK\n    ++\n    ++From nobody Mon Sep 17 00:00:00 2001\n    ++From: A U Thor <mail@example.com>\n    ++To: list@example.org\n    ++Subject: [PATCH v2] sample\n    ++Date: Mon,  3 Aug 2020 22:40:55 +0700\n    ++Message-Id: <msg-id2@example.com>\n    ++Content-Type: text/plain; charset=\"utf-8\"\n    ++Content-Transfer-Encoding: base64\n    ++\n    ++T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw\n    ++YXR0ZXJucy4NCg0KLS0tDQogY29uZmlndXJlIHwgMiArLQ0KIDEgZmlsZSBjaGFuZ2VkLCAxIGlu\n    ++c2VydGlvbigrKSwgMSBkZWxldGlvbigtKQ0KDQpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29u\n    ++ZmlndXJlDQppbmRleCBkYjM1MzhiMy4uZjdjMWMwOTUgMTAwNzU1DQotLS0gYS9jb25maWd1cmUN\n    ++CisrKyBiL2NvbmZpZ3VyZQ0KQEAgLTgxNCw3ICs4MTQsNyBAQCBpZiBbICRoYXZlX3B5dGhvbjMg\n    ++LWVxIDEgXTsgdGhlbg0KICAgICBwcmludGYgIg1DaGVja2luZyBmb3IgcHl0aG9uMyBweXRlc3Qg\n    ++KD49IDMuMCkuLi4gIg0KICAgICBjb25mPSQobWt0ZW1wKQ0KICAgICBwcmludGYgIltweXRlc3Rd\n    ++XG5taW52ZXJzaW9uPTMuMFxuIiA+ICRjb25mDQotICAgIGlmIHB5dGVzdC0zIC1jICRjb25mIC0t\n    ++dmVyc2lvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4NCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVz\n    ++dCAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQogICAgICAgICBwcmlu\n    ++dGYgIlllcy5cbiINCiAgICAgICAgIGhhdmVfcHl0aG9uM19weXRlc3Q9MQ0KICAgICBlbHNlDQot\n    ++LSANCjIuMjguMA0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\n    ++X18KZXhhbXBsZSBtYWlsaW5nIGxpc3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmli\n    ++ZSBzZW5kIGFuIGVtYWlsIHRvIGxpc3QtbGVhdmVAZXhhbXBsZS5vcmcK\n4:  d5b2da370d ! 4:  8aeb960dfd mailinfo: allow squelching quoted CR warning\n    @@ Metadata\n     Author: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n     \n      ## Commit message ##\n    -    mailinfo: allow squelching quoted CR warning\n    +    mailinfo: allow squelching quoted CRLF warning\n     \n    -    In previous change, Git starts to warn for quoted CR in decoded\n    +    In previous change, Git starts to warn for quoted CRLF in decoded\n         base64/QP email. Despite those warnings are usually helpful,\n    -    quoted CR could be part of some users' workflow.\n    +    quoted CRLF could be part of some users' workflow.\n     \n         Let's give them an option to turn off the warning completely.\n     \n    @@ mailinfo.c: static void handle_filter_flowed(struct mailinfo *mi, struct strbuf\n     -\tif (mi->have_quoted_cr)\n     +\tif (mi->have_quoted_cr &&\n     +\t    mi->quoted_cr == quoted_cr_warn)\n    - \t\twarning(\"quoted CR detected\");\n    + \t\twarning(_(\"quoted CRLF detected\"));\n      }\n      \n     @@ mailinfo.c: int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n    @@ mailinfo.h: struct mailinfo {\n      void clear_mailinfo(struct mailinfo *);\n     \n      ## t/t5100-mailinfo.sh ##\n    -@@ t/t5100-mailinfo.sh: check_quoted_cr_mail () {\n    - \ttest_cmp \"$DATA/quoted-cr-info\" quoted-cr-info\n    - }\n    - \n    --test_expect_success 'mailinfo warn CR in base64 encoded email' '\n    -+test_expect_success 'mailinfo handle CR in base64 encoded email' '\n    - \tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n    - \tsed \"s/%%/$(printf \\\\015)/\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n    - \tcheck_quoted_cr_mail &&\n    --\tgrep \"quoted CR detected\" quoted-cr-err\n    -+\tgrep \"quoted CR detected\" quoted-cr-err &&\n    -+\tcheck_quoted_cr_mail --quoted-cr=nowarn &&\n    -+\ttest_must_be_empty quoted-cr-err\n    +@@ t/t5100-mailinfo.sh: test_expect_success 'mailinfo warn CR in base64 encoded email' '\n    + \tcheck_quoted_cr_mail quoted-cr/0001 &&\n    + \ttest_must_be_empty quoted-cr/0001.err &&\n    + \tcheck_quoted_cr_mail quoted-cr/0002 &&\n    +-\tgrep \"quoted CRLF detected\" quoted-cr/0002.err\n    ++\tgrep \"quoted CRLF detected\" quoted-cr/0002.err &&\n    ++\tcheck_quoted_cr_mail quoted-cr/0001 --quoted-cr=nowarn &&\n    ++\ttest_must_be_empty quoted-cr/0001.err &&\n    ++\tcheck_quoted_cr_mail quoted-cr/0002 --quoted-cr=nowarn &&\n    ++\ttest_must_be_empty quoted-cr/0002.err\n      '\n      \n      test_done\n5:  9e96d4bf5e ! 5:  448daac5b3 mailinfo: allow stripping quoted CR without warning\n    @@ mailinfo.h\n      struct mailinfo {\n     \n      ## t/t5100-mailinfo.sh ##\n    -@@ t/t5100-mailinfo.sh: test_expect_success 'mailinfo handle CR in base64 encoded email' '\n    - \tcheck_quoted_cr_mail &&\n    - \tgrep \"quoted CR detected\" quoted-cr-err &&\n    - \tcheck_quoted_cr_mail --quoted-cr=nowarn &&\n    -+\ttest_must_be_empty quoted-cr-err &&\n    -+\tsed \"s/%%//\" \"$DATA/quoted-cr-msg\" >expect-cr-msg &&\n    -+\tsed \"s/%%//\" \"$DATA/quoted-cr-patch\" >expect-cr-patch &&\n    -+\tcheck_quoted_cr_mail --quoted-cr=strip &&\n    - \ttest_must_be_empty quoted-cr-err\n    +@@ t/t5100-mailinfo.sh: test_expect_success 'mailinfo warn CR in base64 encoded email' '\n    + \tcheck_quoted_cr_mail quoted-cr/0001 --quoted-cr=nowarn &&\n    + \ttest_must_be_empty quoted-cr/0001.err &&\n    + \tcheck_quoted_cr_mail quoted-cr/0002 --quoted-cr=nowarn &&\n    ++\ttest_must_be_empty quoted-cr/0002.err &&\n    ++\tcp quoted-cr/0001-expected.msg quoted-cr/0002-expected.msg &&\n    ++\tcp quoted-cr/0001-expected.patch quoted-cr/0002-expected.patch &&\n    ++\tcheck_quoted_cr_mail quoted-cr/0001 --quoted-cr=strip &&\n    ++\ttest_must_be_empty quoted-cr/0001.err &&\n    ++\tcheck_quoted_cr_mail quoted-cr/0002 --quoted-cr=strip &&\n    + \ttest_must_be_empty quoted-cr/0002.err\n      '\n      \n6:  d6aa12acc0 ! 6:  e6dbc9d336 am: learn to process quoted lines that ends with CRLF\n    @@ t/t4258-am-quoted-cr.sh (new)\n     +test_expect_success 'am warn if quoted-cr is found' '\n     +\tgit reset --hard one &&\n     +\ttest_must_fail git am \"$DATA/mbox\" 2>err &&\n    -+\tgrep \"quoted CR detected\" err\n    ++\tgrep \"quoted CRLF detected\" err\n     +'\n     +\n    -+test_expect_success 'am strip if quoted-cr is found' '\n    ++test_expect_success 'am --quoted-cr=strip' '\n     +\ttest_might_fail git am --abort &&\n     +\tgit reset --hard one &&\n     +\tgit am --quoted-cr=strip \"$DATA/mbox\" &&\n     +\tgit diff --exit-code HEAD two\n     +'\n     +\n    -+test_expect_success 'am strip if quoted-cr is found' '\n    ++test_expect_success 'am with config mailinfo.quotecr=strip' '\n     +\ttest_might_fail git am --abort &&\n     +\tgit reset --hard one &&\n     +\ttest_config mailinfo.quotedCr strip &&\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423968","messageId":"fac95392df0b1428c32874a1faff0c86d58af0bb.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620580178.git.congdanhqx@gmail.com","subject":"[PATCH v4 1/6] mailinfo: load default metainfo_charset lazily","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:08Z","receivedAt":"2021-05-09T17:12:31Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In a later change, we will use parse_option to parse mailinfo's options.\nIn mailinfo, both \"-u\", \"-n\", and \"--encoding\" try to set the same\nfield, with \"-u\" reset that field to some default value from\nconfiguration variable \"i18n.commitEncoding\".\n\nLet's delay the setting of that field until we finish processing all\noptions. By doing that, \"i18n.commitEncoding\" can be parsed on demand.\nMore importantly, it cleans the way for using parse_option.\n\nThis change introduces some inconsistent brackets \"{}\" in \"if/else if\"\nconstruct, however, we will rewrite them in the next few changes.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n builtin/mailinfo.c | 40 +++++++++++++++++++++++++++++++---------\n 1 file changed, 31 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex cfb667a594..77f96177cc 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -11,17 +11,25 @@\n static const char mailinfo_usage[] =\n \t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n \n+struct metainfo_charset\n+{\n+\tenum {\n+\t\tCHARSET_DEFAULT,\n+\t\tCHARSET_NO_REENCODE,\n+\t\tCHARSET_EXPLICIT,\n+\t} policy;\n+\tconst char *charset;\n+};\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *def_charset;\n+\tstruct metainfo_charset meta_charset;\n \tstruct mailinfo mi;\n \tint status;\n \tchar *msgfile, *patchfile;\n \n \tsetup_mailinfo(&mi);\n-\n-\tdef_charset = get_commit_output_encoding();\n-\tmi.metainfo_charset = def_charset;\n+\tmeta_charset.policy = CHARSET_DEFAULT;\n \n \twhile (1 < argc && argv[1][0] == '-') {\n \t\tif (!strcmp(argv[1], \"-k\"))\n@@ -31,12 +39,13 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \t\telse if (!strcmp(argv[1], \"-m\") || !strcmp(argv[1], \"--message-id\"))\n \t\t\tmi.add_message_id = 1;\n \t\telse if (!strcmp(argv[1], \"-u\"))\n-\t\t\tmi.metainfo_charset = def_charset;\n+\t\t\tmeta_charset.policy = CHARSET_DEFAULT;\n \t\telse if (!strcmp(argv[1], \"-n\"))\n-\t\t\tmi.metainfo_charset = NULL;\n-\t\telse if (starts_with(argv[1], \"--encoding=\"))\n-\t\t\tmi.metainfo_charset = argv[1] + 11;\n-\t\telse if (!strcmp(argv[1], \"--scissors\"))\n+\t\t\tmeta_charset.policy = CHARSET_NO_REENCODE;\n+\t\telse if (starts_with(argv[1], \"--encoding=\")) {\n+\t\t\tmeta_charset.policy = CHARSET_EXPLICIT;\n+\t\t\tmeta_charset.charset = argv[1] + 11;\n+\t\t} else if (!strcmp(argv[1], \"--scissors\"))\n \t\t\tmi.use_scissors = 1;\n \t\telse if (!strcmp(argv[1], \"--no-scissors\"))\n \t\t\tmi.use_scissors = 0;\n@@ -50,6 +59,19 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tif (argc != 3)\n \t\tusage(mailinfo_usage);\n \n+\tswitch (meta_charset.policy) {\n+\tcase CHARSET_DEFAULT:\n+\t\tmi.metainfo_charset = get_commit_output_encoding();\n+\t\tbreak;\n+\tcase CHARSET_NO_REENCODE:\n+\t\tmi.metainfo_charset = NULL;\n+\t\tbreak;\n+\tcase CHARSET_EXPLICIT:\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid meta_charset.policy\");\n+\t}\n+\n \tmi.input = stdin;\n \tmi.output = stdout;\n \n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423969","messageId":"1fb08bb37dc534296b3bf89efc9bfaad67a9b068.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620580178.git.congdanhqx@gmail.com","subject":"[PATCH v4 2/6] mailinfo: stop parsing options manually","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:09Z","receivedAt":"2021-05-09T17:12:31Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In a later change, mailinfo will learn more options, let's switch to our\nrobust parse_options framework before that step.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n builtin/mailinfo.c | 75 +++++++++++++++++++++++++++-------------------\n 1 file changed, 45 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex 77f96177cc..f55549a097 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -7,9 +7,13 @@\n #include \"utf8.h\"\n #include \"strbuf.h\"\n #include \"mailinfo.h\"\n+#include \"parse-options.h\"\n \n-static const char mailinfo_usage[] =\n-\t\"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info\";\n+static const char * const mailinfo_usage[] = {\n+\t/* TRANSLATORS: keep <> in \"<\" mail \">\" info. */\n+\tN_(\"git mailinfo [<options>] <msg> <patch> < mail >info\"),\n+\tNULL,\n+};\n \n struct metainfo_charset\n {\n@@ -21,6 +25,19 @@ struct metainfo_charset\n \tconst char *charset;\n };\n \n+static int parse_opt_explicit_encoding(const struct option *opt,\n+\t\t\t\t       const char *arg, int unset)\n+{\n+\tstruct metainfo_charset *meta_charset = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tmeta_charset->policy = CHARSET_EXPLICIT;\n+\tmeta_charset->charset = arg;\n+\n+\treturn 0;\n+}\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n \tstruct metainfo_charset meta_charset;\n@@ -28,36 +45,34 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tint status;\n \tchar *msgfile, *patchfile;\n \n+\tstruct option options[] = {\n+\t\tOPT_BOOL('k', NULL, &mi.keep_subject, N_(\"keep subject\")),\n+\t\tOPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,\n+\t\t\t N_(\"keep non patch brackets in subject\")),\n+\t\tOPT_BOOL('m', \"message-id\", &mi.add_message_id,\n+\t\t\t N_(\"copy Message-ID to the end of commit message\")),\n+\t\tOPT_SET_INT_F('u', NULL, &meta_charset.policy,\n+\t\t\t      N_(\"re-code metadata to i18n.commitEncoding\"),\n+\t\t\t      CHARSET_DEFAULT, PARSE_OPT_NONEG),\n+\t\tOPT_SET_INT_F('n', NULL, &meta_charset.policy,\n+\t\t\t      N_(\"disable charset re-coding of metadata\"),\n+\t\t\t      CHARSET_NO_REENCODE, PARSE_OPT_NONEG),\n+\t\tOPT_CALLBACK_F(0, \"encoding\", &meta_charset, N_(\"encoding\"),\n+\t\t\t       N_(\"re-code metadata to this encoding\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_explicit_encoding),\n+\t\tOPT_BOOL(0, \"scissors\", &mi.use_scissors, N_(\"use scissors\")),\n+\t\tOPT_HIDDEN_BOOL(0, \"inbody-headers\", &mi.use_inbody_headers,\n+\t\t\t N_(\"use headers in message's body\")),\n+\t\tOPT_END()\n+\t};\n+\n \tsetup_mailinfo(&mi);\n \tmeta_charset.policy = CHARSET_DEFAULT;\n \n-\twhile (1 < argc && argv[1][0] == '-') {\n-\t\tif (!strcmp(argv[1], \"-k\"))\n-\t\t\tmi.keep_subject = 1;\n-\t\telse if (!strcmp(argv[1], \"-b\"))\n-\t\t\tmi.keep_non_patch_brackets_in_subject = 1;\n-\t\telse if (!strcmp(argv[1], \"-m\") || !strcmp(argv[1], \"--message-id\"))\n-\t\t\tmi.add_message_id = 1;\n-\t\telse if (!strcmp(argv[1], \"-u\"))\n-\t\t\tmeta_charset.policy = CHARSET_DEFAULT;\n-\t\telse if (!strcmp(argv[1], \"-n\"))\n-\t\t\tmeta_charset.policy = CHARSET_NO_REENCODE;\n-\t\telse if (starts_with(argv[1], \"--encoding=\")) {\n-\t\t\tmeta_charset.policy = CHARSET_EXPLICIT;\n-\t\t\tmeta_charset.charset = argv[1] + 11;\n-\t\t} else if (!strcmp(argv[1], \"--scissors\"))\n-\t\t\tmi.use_scissors = 1;\n-\t\telse if (!strcmp(argv[1], \"--no-scissors\"))\n-\t\t\tmi.use_scissors = 0;\n-\t\telse if (!strcmp(argv[1], \"--no-inbody-headers\"))\n-\t\t\tmi.use_inbody_headers = 0;\n-\t\telse\n-\t\t\tusage(mailinfo_usage);\n-\t\targc--; argv++;\n-\t}\n+\targc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);\n \n-\tif (argc != 3)\n-\t\tusage(mailinfo_usage);\n+\tif (argc != 2)\n+\t\tusage_with_options(mailinfo_usage, options);\n \n \tswitch (meta_charset.policy) {\n \tcase CHARSET_DEFAULT:\n@@ -75,8 +90,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \tmi.input = stdin;\n \tmi.output = stdout;\n \n-\tmsgfile = prefix_filename(prefix, argv[1]);\n-\tpatchfile = prefix_filename(prefix, argv[2]);\n+\tmsgfile = prefix_filename(prefix, argv[0]);\n+\tpatchfile = prefix_filename(prefix, argv[1]);\n \n \tstatus = !!mailinfo(&mi, msgfile, patchfile);\n \tclear_mailinfo(&mi);\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423970","messageId":"50404ffe7425a32fd26e19fc406f80693985e644.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620580178.git.congdanhqx@gmail.com","subject":"[PATCH v4 3/6] mailinfo: warn if CRLF found in decoded base64/QP email","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:10Z","receivedAt":"2021-05-09T17:12:32Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"When SMTP servers receive 8-bit email messages, possibly with only\nLF as line ending, some of them decide to change said LF to CRLF.\n\nSome mailing list softwares, when receive 8-bit email messages,\ndecide to encode those messages in base64 or quoted-printable.\n\nIf an email is transfered through above mail servers, then distributed\nby such mailing list softwares, the recipients will receive an email\ncontains a patch mungled with CRLF encoded inside another encoding.\n\nThus, such CR (in CRLF) couldn't be dropped by \"mailsplit\".\nHence, the mailed patch couldn't be applied cleanly.\nSuch accidents have been observed in the wild [1].\n\nInstead of silently rejecting those messages, let's give our users\nsome warnings if such CR (as part of CRLF) is found.\n\n[1]: https://nmbug.notmuchmail.org/nmweb/show/m2lf9ejegj.fsf%40guru.guru-group.fi\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n mailinfo.c              | 14 ++++++++++++\n mailinfo.h              |  1 +\n t/t5100-mailinfo.sh     | 30 ++++++++++++++++++++++++++\n t/t5100/quoted-cr-info  |  5 +++++\n t/t5100/quoted-cr-msg   |  2 ++\n t/t5100/quoted-cr-patch | 22 +++++++++++++++++++\n t/t5100/quoted-cr.mbox  | 47 +++++++++++++++++++++++++++++++++++++++++\n 7 files changed, 121 insertions(+)\n create mode 100644 t/t5100/quoted-cr-info\n create mode 100644 t/t5100/quoted-cr-msg\n create mode 100644 t/t5100/quoted-cr-patch\n create mode 100644 t/t5100/quoted-cr.mbox\n\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 5681d9130d..c8caee4f55 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -994,6 +994,11 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \tconst char *rest;\n \n \tif (!mi->format_flowed) {\n+\t\tif (len >= 2 &&\n+\t\t    line->buf[len - 2] == '\\r' &&\n+\t\t    line->buf[len - 1] == '\\n') {\n+\t\t\tmi->have_quoted_cr = 1;\n+\t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n \t}\n@@ -1033,6 +1038,12 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \thandle_filter(mi, line);\n }\n \n+static void summarize_quoted_cr(struct mailinfo *mi)\n+{\n+\tif (mi->have_quoted_cr)\n+\t\twarning(_(\"quoted CRLF detected\"));\n+}\n+\n static void handle_body(struct mailinfo *mi, struct strbuf *line)\n {\n \tstruct strbuf prev = STRBUF_INIT;\n@@ -1051,6 +1062,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \t\t\t\thandle_filter(mi, &prev);\n \t\t\t\tstrbuf_reset(&prev);\n \t\t\t}\n+\t\t\tsummarize_quoted_cr(mi);\n+\t\t\tmi->have_quoted_cr = 0;\n \t\t\tif (!handle_boundary(mi, line))\n \t\t\t\tgoto handle_body_out;\n \t\t}\n@@ -1100,6 +1113,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)\n \n \tif (prev.len)\n \t\thandle_filter(mi, &prev);\n+\tsummarize_quoted_cr(mi);\n \n \tflush_inbody_header_accum(mi);\n \ndiff --git a/mailinfo.h b/mailinfo.h\nindex 79b1d6774e..b394ef9bce 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -24,6 +24,7 @@ struct mailinfo {\n \tstruct strbuf charset;\n \tunsigned int format_flowed:1;\n \tunsigned int delsp:1;\n+\tunsigned int have_quoted_cr:1;\n \tchar *message_id;\n \tenum  {\n \t\tTE_DONTCARE, TE_QP, TE_BASE64\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 147e616533..ac6fbfe596 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -228,4 +228,34 @@ test_expect_success 'mailinfo handles unusual header whitespace' '\n \ttest_cmp expect actual\n '\n \n+check_quoted_cr_mail () {\n+\tmail=\"$1\" && shift &&\n+\tgit mailinfo -u \"$@\" \"$mail.msg\" \"$mail.patch\" \\\n+\t\t<\"$mail\" >\"$mail.info\" 2>\"$mail.err\" &&\n+\ttest_cmp \"$mail-expected.msg\" \"$mail.msg\" &&\n+\ttest_cmp \"$mail-expected.patch\" \"$mail.patch\" &&\n+\ttest_cmp \"$DATA/quoted-cr-info\" \"$mail.info\"\n+}\n+\n+test_expect_success 'split base64 email with quoted-cr' '\n+\tmkdir quoted-cr &&\n+\tgit mailsplit -oquoted-cr \"$DATA/quoted-cr.mbox\" >quoted-cr/last &&\n+\ttest $(cat quoted-cr/last) = 2\n+'\n+\n+test_expect_success 'mailinfo warn CR in base64 encoded email' '\n+\tsed -e \"s/%%$//\" -e \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-msg\" \\\n+\t\t>quoted-cr/0001-expected.msg &&\n+\tsed \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-msg\" \\\n+\t\t>quoted-cr/0002-expected.msg &&\n+\tsed -e \"s/%%$//\" -e \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-patch\" \\\n+\t\t>quoted-cr/0001-expected.patch &&\n+\tsed \"s/%%/$(printf \\\\015)/g\" \"$DATA/quoted-cr-patch\" \\\n+\t\t>quoted-cr/0002-expected.patch &&\n+\tcheck_quoted_cr_mail quoted-cr/0001 &&\n+\ttest_must_be_empty quoted-cr/0001.err &&\n+\tcheck_quoted_cr_mail quoted-cr/0002 &&\n+\tgrep \"quoted CRLF detected\" quoted-cr/0002.err\n+'\n+\n test_done\ndiff --git a/t/t5100/quoted-cr-info b/t/t5100/quoted-cr-info\nnew file mode 100644\nindex 0000000000..dab2228b70\n--- /dev/null\n+++ b/t/t5100/quoted-cr-info\n@@ -0,0 +1,5 @@\n+Author: A U Thor\n+Email: mail@example.com\n+Subject: sample\n+Date: Mon, 3 Aug 2020 22:40:55 +0700\n+\ndiff --git a/t/t5100/quoted-cr-msg b/t/t5100/quoted-cr-msg\nnew file mode 100644\nindex 0000000000..89b05a0784\n--- /dev/null\n+++ b/t/t5100/quoted-cr-msg\n@@ -0,0 +1,2 @@\n+On different distro, %%pytest is suffixed with different patterns.%%\n+%%\ndiff --git a/t/t5100/quoted-cr-patch b/t/t5100/quoted-cr-patch\nnew file mode 100644\nindex 0000000000..65b13eeef7\n--- /dev/null\n+++ b/t/t5100/quoted-cr-patch\n@@ -0,0 +1,22 @@\n+---%%\n+ configure | 2 +-%%\n+ 1 file changed, 1 insertion(+), 1 deletion(-)%%\n+%%\n+diff --git a/configure b/configure%%\n+index db3538b3..f7c1c095 100755%%\n+--- a/configure%%\n++++ b/configure%%\n+@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%\n+     printf \"%%Checking for python3 pytest (>= 3.0)... \"%%\n+     conf=$(mktemp)%%\n+     printf \"[pytest]\\nminversion=3.0\\n\" > $conf%%\n+-    if pytest-3 -c $conf --version >/dev/null 2>&1; then%%\n++    if \"$python\" -m pytest -c $conf --version >/dev/null 2>&1; then%%\n+         printf \"Yes.\\n\"%%\n+         have_python3_pytest=1%%\n+     else%%\n+-- %%\n+2.28.0%%\n+_______________________________________________\n+example mailing list -- list@example.org\n+To unsubscribe send an email to list-leave@example.org\ndiff --git a/t/t5100/quoted-cr.mbox b/t/t5100/quoted-cr.mbox\nnew file mode 100644\nindex 0000000000..909021bb7a\n--- /dev/null\n+++ b/t/t5100/quoted-cr.mbox\n@@ -0,0 +1,47 @@\n+From nobody Mon Sep 17 00:00:00 2001\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw\n+YXR0ZXJucy4KCi0tLQogY29uZmlndXJlIHwgMiArLQogMSBmaWxlIGNoYW5nZWQsIDEgaW5zZXJ0\n+aW9uKCspLCAxIGRlbGV0aW9uKC0pCgpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29uZmlndXJl\n+CmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUKLS0tIGEvY29uZmlndXJlCisrKyBiL2Nv\n+bmZpZ3VyZQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAtZXEgMSBdOyB0\n+aGVuCiAgICAgcHJpbnRmICINQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+PSAzLjApLi4u\n+ICIKICAgICBjb25mPSQobWt0ZW1wKQogICAgIHByaW50ZiAiW3B5dGVzdF1cbm1pbnZlcnNpb249\n+My4wXG4iID4gJGNvbmYKLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYv\n+bnVsbCAyPiYxOyB0aGVuCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVzdCAtYyAkY29uZiAtLXZl\n+cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAgICAgIHByaW50ZiAiWWVzLlxuIgogICAg\n+ICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTEKICAgICBlbHNlCi0tIAoyLjI4LjAKX19fX19fX19f\n+X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KZXhhbXBsZSBtYWlsaW5nIGxp\n+c3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmliZSBzZW5kIGFuIGVtYWlsIHRvIGxp\n+c3QtbGVhdmVAZXhhbXBsZS5vcmcK\n+\n+From nobody Mon Sep 17 00:00:00 2001\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id2@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw\n+YXR0ZXJucy4NCg0KLS0tDQogY29uZmlndXJlIHwgMiArLQ0KIDEgZmlsZSBjaGFuZ2VkLCAxIGlu\n+c2VydGlvbigrKSwgMSBkZWxldGlvbigtKQ0KDQpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29u\n+ZmlndXJlDQppbmRleCBkYjM1MzhiMy4uZjdjMWMwOTUgMTAwNzU1DQotLS0gYS9jb25maWd1cmUN\n+CisrKyBiL2NvbmZpZ3VyZQ0KQEAgLTgxNCw3ICs4MTQsNyBAQCBpZiBbICRoYXZlX3B5dGhvbjMg\n+LWVxIDEgXTsgdGhlbg0KICAgICBwcmludGYgIg1DaGVja2luZyBmb3IgcHl0aG9uMyBweXRlc3Qg\n+KD49IDMuMCkuLi4gIg0KICAgICBjb25mPSQobWt0ZW1wKQ0KICAgICBwcmludGYgIltweXRlc3Rd\n+XG5taW52ZXJzaW9uPTMuMFxuIiA+ICRjb25mDQotICAgIGlmIHB5dGVzdC0zIC1jICRjb25mIC0t\n+dmVyc2lvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4NCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVz\n+dCAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQogICAgICAgICBwcmlu\n+dGYgIlllcy5cbiINCiAgICAgICAgIGhhdmVfcHl0aG9uM19weXRlc3Q9MQ0KICAgICBlbHNlDQot\n+LSANCjIuMjguMA0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\n+X18KZXhhbXBsZSBtYWlsaW5nIGxpc3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmli\n+ZSBzZW5kIGFuIGVtYWlsIHRvIGxpc3QtbGVhdmVAZXhhbXBsZS5vcmcK\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423971","messageId":"8aeb960dfdc6525764486ebd4e560efd96a03bc8.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620580178.git.congdanhqx@gmail.com","subject":"[PATCH v4 4/6] mailinfo: allow squelching quoted CRLF warning","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:11Z","receivedAt":"2021-05-09T17:12:34Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous change, Git starts to warn for quoted CRLF in decoded\nbase64/QP email. Despite those warnings are usually helpful,\nquoted CRLF could be part of some users' workflow.\n\nLet's give them an option to turn off the warning completely.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-mailinfo.txt | 20 +++++++++++++++++++-\n builtin/mailinfo.c             | 12 ++++++++++++\n mailinfo.c                     | 20 +++++++++++++++++++-\n mailinfo.h                     |  7 +++++++\n t/t5100-mailinfo.sh            |  6 +++++-\n 5 files changed, 62 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\nindex d343f040f5..824947a070 100644\n--- a/Documentation/git-mailinfo.txt\n+++ b/Documentation/git-mailinfo.txt\n@@ -9,7 +9,9 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message\n SYNOPSIS\n --------\n [verse]\n-'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>\n+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n]\n+\t       [--[no-]scissors] [--quoted-cr=<action>]\n+\t       <msg> <patch>\n \n \n DESCRIPTION\n@@ -89,6 +91,22 @@ This can be enabled by default with the configuration option mailinfo.scissors.\n --no-scissors::\n \tIgnore scissors lines. Useful for overriding mailinfo.scissors settings.\n \n+--quoted-cr=<action>::\n+\tAction when processes email messages sent with base64 or\n+\tquoted-printable encoding, and the decoded lines end with a CRLF\n+\tinstead of a simple LF.\n++\n+The valid actions are:\n++\n+--\n+*\t`nowarn`: Git will do nothing when such a CRLF is found.\n+*\t`warn`: Git will issue a warning for each message if such a CRLF is\n+\tfound.\n+--\n++\n+The default action could be set by configuration option `mailinfo.quotedCR`.\n+If no such configuration option has been set, `warn` will be used.\n+\n <msg>::\n \tThe commit log message extracted from e-mail, usually\n \texcept the title line which comes from e-mail Subject.\ndiff --git a/builtin/mailinfo.c b/builtin/mailinfo.c\nindex f55549a097..01d16ef9e5 100644\n--- a/builtin/mailinfo.c\n+++ b/builtin/mailinfo.c\n@@ -38,6 +38,15 @@ static int parse_opt_explicit_encoding(const struct option *opt,\n \treturn 0;\n }\n \n+static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)\n+{\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)\n+\t\treturn error(_(\"bad action '%s' for '%s'\"), arg, \"--quoted-cr\");\n+\treturn 0;\n+}\n+\n int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n {\n \tstruct metainfo_charset meta_charset;\n@@ -61,6 +70,9 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)\n \t\t\t       N_(\"re-code metadata to this encoding\"),\n \t\t\t       PARSE_OPT_NONEG, parse_opt_explicit_encoding),\n \t\tOPT_BOOL(0, \"scissors\", &mi.use_scissors, N_(\"use scissors\")),\n+\t\tOPT_CALLBACK_F(0, \"quoted-cr\", &mi.quoted_cr, N_(\"<action>\"),\n+\t\t\t       N_(\"action when quoted CR is found\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_quoted_cr),\n \t\tOPT_HIDDEN_BOOL(0, \"inbody-headers\", &mi.use_inbody_headers,\n \t\t\t N_(\"use headers in message's body\")),\n \t\tOPT_END()\ndiff --git a/mailinfo.c b/mailinfo.c\nindex c8caee4f55..a784552c7b 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1040,7 +1040,8 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \n static void summarize_quoted_cr(struct mailinfo *mi)\n {\n-\tif (mi->have_quoted_cr)\n+\tif (mi->have_quoted_cr &&\n+\t    mi->quoted_cr == quoted_cr_warn)\n \t\twarning(_(\"quoted CRLF detected\"));\n }\n \n@@ -1220,6 +1221,17 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)\n \treturn mi->input_error;\n }\n \n+int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n+{\n+\tif (!strcmp(actionstr, \"nowarn\"))\n+\t\t*action = quoted_cr_nowarn;\n+\telse if (!strcmp(actionstr, \"warn\"))\n+\t\t*action = quoted_cr_warn;\n+\telse\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n@@ -1230,6 +1242,11 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"mailinfo.quotedcr\")) {\n+\t\tif (mailinfo_parse_quoted_cr_action(value, &mi->quoted_cr) != 0)\n+\t\t\treturn error(_(\"bad action '%s' for '%s'\"), value, var);\n+\t\treturn 0;\n+\t}\n \t/* perhaps others here */\n \treturn 0;\n }\n@@ -1242,6 +1259,7 @@ void setup_mailinfo(struct mailinfo *mi)\n \tstrbuf_init(&mi->charset, 0);\n \tstrbuf_init(&mi->log_message, 0);\n \tstrbuf_init(&mi->inbody_header_accum, 0);\n+\tmi->quoted_cr = quoted_cr_warn;\n \tmi->header_stage = 1;\n \tmi->use_inbody_headers = 1;\n \tmi->content_top = mi->content;\ndiff --git a/mailinfo.h b/mailinfo.h\nindex b394ef9bce..768d06ac2a 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -5,6 +5,11 @@\n \n #define MAX_BOUNDARIES 5\n \n+enum quoted_cr_action {\n+\tquoted_cr_nowarn,\n+\tquoted_cr_warn,\n+};\n+\n struct mailinfo {\n \tFILE *input;\n \tFILE *output;\n@@ -14,6 +19,7 @@ struct mailinfo {\n \tstruct strbuf email;\n \tint keep_subject;\n \tint keep_non_patch_brackets_in_subject;\n+\tint quoted_cr; /* enum quoted_cr_action */\n \tint add_message_id;\n \tint use_scissors;\n \tint use_inbody_headers;\n@@ -40,6 +46,7 @@ struct mailinfo {\n \tint input_error;\n };\n \n+int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action);\n void setup_mailinfo(struct mailinfo *);\n int mailinfo(struct mailinfo *, const char *msg, const char *patch);\n void clear_mailinfo(struct mailinfo *);\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex ac6fbfe596..1ecefa381d 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -255,7 +255,11 @@ test_expect_success 'mailinfo warn CR in base64 encoded email' '\n \tcheck_quoted_cr_mail quoted-cr/0001 &&\n \ttest_must_be_empty quoted-cr/0001.err &&\n \tcheck_quoted_cr_mail quoted-cr/0002 &&\n-\tgrep \"quoted CRLF detected\" quoted-cr/0002.err\n+\tgrep \"quoted CRLF detected\" quoted-cr/0002.err &&\n+\tcheck_quoted_cr_mail quoted-cr/0001 --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr/0001.err &&\n+\tcheck_quoted_cr_mail quoted-cr/0002 --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr/0002.err\n '\n \n test_done\n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423972","messageId":"448daac5b3d58f948184d63046ab4aa02be9b036.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620580178.git.congdanhqx@gmail.com","subject":"[PATCH v4 5/6] mailinfo: allow stripping quoted CR without warning","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:12Z","receivedAt":"2021-05-09T17:12:35Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous changes, we've turned on warning for quoted CR in base64 or\nquoted-printable email messages. Some projects see those quoted CR a lot,\nthey know that it happens most of the time, and they find it's desirable\nto always strip those CR.\n\nThose projects in question usually fall back to use other tools to handle\npatches when receive such patches.\n\nLet's help those projects handle those patches by stripping those\nexcessive CR.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-mailinfo.txt | 1 +\n mailinfo.c                     | 7 +++++++\n mailinfo.h                     | 1 +\n t/t5100-mailinfo.sh            | 6 ++++++\n 4 files changed, 15 insertions(+)\n\ndiff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt\nindex 824947a070..3fcfd965fd 100644\n--- a/Documentation/git-mailinfo.txt\n+++ b/Documentation/git-mailinfo.txt\n@@ -102,6 +102,7 @@ The valid actions are:\n *\t`nowarn`: Git will do nothing when such a CRLF is found.\n *\t`warn`: Git will issue a warning for each message if such a CRLF is\n \tfound.\n+*\t`strip`: Git will convert those CRLF to LF.\n --\n +\n The default action could be set by configuration option `mailinfo.quotedCR`.\ndiff --git a/mailinfo.c b/mailinfo.c\nindex a784552c7b..ed863c3a95 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -998,6 +998,11 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,\n \t\t    line->buf[len - 2] == '\\r' &&\n \t\t    line->buf[len - 1] == '\\n') {\n \t\t\tmi->have_quoted_cr = 1;\n+\t\t\tif (mi->quoted_cr == quoted_cr_strip) {\n+\t\t\t\tstrbuf_setlen(line, len - 2);\n+\t\t\t\tstrbuf_addch(line, '\\n');\n+\t\t\t\tlen--;\n+\t\t\t}\n \t\t}\n \t\thandle_filter(mi, line);\n \t\treturn;\n@@ -1227,6 +1232,8 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \t\t*action = quoted_cr_nowarn;\n \telse if (!strcmp(actionstr, \"warn\"))\n \t\t*action = quoted_cr_warn;\n+\telse if (!strcmp(actionstr, \"strip\"))\n+\t\t*action = quoted_cr_strip;\n \telse\n \t\treturn -1;\n \treturn 0;\ndiff --git a/mailinfo.h b/mailinfo.h\nindex 768d06ac2a..2ddf8be90f 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -8,6 +8,7 @@\n enum quoted_cr_action {\n \tquoted_cr_nowarn,\n \tquoted_cr_warn,\n+\tquoted_cr_strip,\n };\n \n struct mailinfo {\ndiff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh\nindex 1ecefa381d..141b29f031 100755\n--- a/t/t5100-mailinfo.sh\n+++ b/t/t5100-mailinfo.sh\n@@ -259,6 +259,12 @@ test_expect_success 'mailinfo warn CR in base64 encoded email' '\n \tcheck_quoted_cr_mail quoted-cr/0001 --quoted-cr=nowarn &&\n \ttest_must_be_empty quoted-cr/0001.err &&\n \tcheck_quoted_cr_mail quoted-cr/0002 --quoted-cr=nowarn &&\n+\ttest_must_be_empty quoted-cr/0002.err &&\n+\tcp quoted-cr/0001-expected.msg quoted-cr/0002-expected.msg &&\n+\tcp quoted-cr/0001-expected.patch quoted-cr/0002-expected.patch &&\n+\tcheck_quoted_cr_mail quoted-cr/0001 --quoted-cr=strip &&\n+\ttest_must_be_empty quoted-cr/0001.err &&\n+\tcheck_quoted_cr_mail quoted-cr/0002 --quoted-cr=strip &&\n \ttest_must_be_empty quoted-cr/0002.err\n '\n \n-- \n2.31.1.448.g9c2f8508d1\n\n"},{"id":"423973","messageId":"e6dbc9d336bbe2ba7089a2b0a43bea2d6a64b72a.1620580178.git.congdanhqx@gmail.com","threadId":"55533","inReplyTo":"cover.1620580178.git.congdanhqx@gmail.com","subject":"[PATCH v4 6/6] am: learn to process quoted lines that ends with CRLF","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-09T17:12:13Z","receivedAt":"2021-05-09T17:12:37Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"In previous changes, mailinfo has learnt to process lines that decoded\nfrom base64 or quoted-printable, and ends with CRLF.\n\nLet's teach \"am\" that new trick, too.\n\nSigned-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>\n---\n Documentation/git-am.txt               |  4 ++\n builtin/am.c                           | 51 ++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 +++\n mailinfo.h                             |  1 +\n t/t4258-am-quoted-cr.sh                | 37 +++++++++++++++++++\n t/t4258/mbox                           | 12 ++++++\n 6 files changed, 110 insertions(+)\n create mode 100755 t/t4258-am-quoted-cr.sh\n create mode 100644 t/t4258/mbox\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex decd8ae122..8714dfcb76 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n \t [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n+\t [--quoted-cr=<action>]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -59,6 +60,9 @@ OPTIONS\n --no-scissors::\n \tIgnore scissors lines (see linkgit:git-mailinfo[1]).\n \n+--quoted-cr=<action>::\n+\tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8355e3566f..0b2d886c81 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -116,6 +116,7 @@ struct am_state {\n \tint keep; /* enum keep_type */\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n+\tint quoted_cr; /* enum quoted_cr_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -145,6 +146,7 @@ static void am_state_init(struct am_state *state)\n \tgit_config_get_bool(\"am.messageid\", &state->message_id);\n \n \tstate->scissors = SCISSORS_UNSET;\n+\tstate->quoted_cr = quoted_cr_unset;\n \n \tstrvec_init(&state->git_apply_opts);\n \n@@ -165,6 +167,16 @@ static void am_state_release(struct am_state *state)\n \tstrvec_clear(&state->git_apply_opts);\n }\n \n+static int am_option_parse_quoted_cr(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)\n+\t\treturn error(_(\"bad action '%s' for '%s'\"), arg, \"--quoted-cr\");\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -397,6 +409,12 @@ static void am_load(struct am_state *state)\n \telse\n \t\tstate->scissors = SCISSORS_UNSET;\n \n+\tread_state_file(&sb, state, \"quoted-cr\", 1);\n+\tif (!*sb.buf)\n+\t\tstate->quoted_cr = quoted_cr_unset;\n+\telse if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)\n+\t\tdie(_(\"could not parse %s\"), am_path(state, \"quoted-cr\"));\n+\n \tread_state_file(&sb, state, \"apply-opt\", 1);\n \tstrvec_clear(&state->git_apply_opts);\n \tif (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)\n@@ -1002,6 +1020,24 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t}\n \twrite_state_text(state, \"scissors\", str);\n \n+\tswitch (state->quoted_cr) {\n+\tcase quoted_cr_unset:\n+\t\tstr = \"\";\n+\t\tbreak;\n+\tcase quoted_cr_nowarn:\n+\t\tstr = \"nowarn\";\n+\t\tbreak;\n+\tcase quoted_cr_warn:\n+\t\tstr = \"warn\";\n+\t\tbreak;\n+\tcase quoted_cr_strip:\n+\t\tstr = \"strip\";\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid value for state->quoted_cr\");\n+\t}\n+\twrite_state_text(state, \"quoted-cr\", str);\n+\n \tsq_quote_argv(&sb, state->git_apply_opts.v);\n \twrite_state_text(state, \"apply-opt\", sb.buf);\n \n@@ -1162,6 +1198,18 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tBUG(\"invalid value for state->scissors\");\n \t}\n \n+\tswitch (state->quoted_cr) {\n+\tcase quoted_cr_unset:\n+\t\tbreak;\n+\tcase quoted_cr_nowarn:\n+\tcase quoted_cr_warn:\n+\tcase quoted_cr_strip:\n+\t\tmi.quoted_cr = state->quoted_cr;\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"invalid value for state->quoted_cr\");\n+\t}\n+\n \tmi.input = xfopen(mail, \"r\");\n \tmi.output = xfopen(am_path(state, \"info\"), \"w\");\n \tif (mailinfo(&mi, am_path(state, \"msg\"), am_path(state, \"patch\")))\n@@ -2242,6 +2290,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\t0, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('c', \"scissors\", &state.scissors,\n \t\t\tN_(\"strip everything before a scissors line\")),\n+\t\tOPT_CALLBACK_F(0, \"quoted-cr\", &state.quoted_cr, N_(\"action\"),\n+\t\t\t       N_(\"pass it through git-mailinfo\"),\n+\t\t\t       PARSE_OPT_NONEG, am_option_parse_quoted_cr),\n \t\tOPT_PASSTHRU_ARGV(0, \"whitespace\", &state.git_apply_opts, N_(\"action\"),\n \t\t\tN_(\"pass it through git-apply\"),\n \t\t\t0),\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 49e76e9d08..edf635095e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1333,6 +1333,7 @@ __git_whitespacelist=\"nowarn warn error error-all fix\"\n __git_patchformat=\"mbox stgit stgit-series hg mboxrd\"\n __git_showcurrentpatch=\"diff raw\"\n __git_am_inprogress_options=\"--skip --continue --resolved --abort --quit --show-current-patch\"\n+__git_quoted_cr=\"nowarn warn strip\"\n \n _git_am ()\n {\n@@ -1354,6 +1355,10 @@ _git_am ()\n \t\t__gitcomp \"$__git_showcurrentpatch\" \"\" \"${cur##--show-current-patch=}\"\n \t\treturn\n \t\t;;\n+\t--quoted-cr=*)\n+\t\t__gitcomp \"$__git_quoted_cr\" \"\" \"${cur##--quoted-cr=}\"\n+\t\treturn\n+\t\t;;\n \t--*)\n \t\t__gitcomp_builtin am \"\" \\\n \t\t\t\"$__git_am_inprogress_options\"\ndiff --git a/mailinfo.h b/mailinfo.h\nindex 2ddf8be90f..f2ffd0349e 100644\n--- a/mailinfo.h\n+++ b/mailinfo.h\n@@ -6,6 +6,7 @@\n #define MAX_BOUNDARIES 5\n \n enum quoted_cr_action {\n+\tquoted_cr_unset = -1,\n \tquoted_cr_nowarn,\n \tquoted_cr_warn,\n \tquoted_cr_strip,\ndiff --git a/t/t4258-am-quoted-cr.sh b/t/t4258-am-quoted-cr.sh\nnew file mode 100755\nindex 0000000000..fb5071f914\n--- /dev/null\n+++ b/t/t4258-am-quoted-cr.sh\n@@ -0,0 +1,37 @@\n+#!/bin/sh\n+\n+test_description='test am --quoted-cr=<action>'\n+\n+. ./test-lib.sh\n+\n+DATA=\"$TEST_DIRECTORY/t4258\"\n+\n+test_expect_success 'setup' '\n+\ttest_write_lines one two three >text &&\n+\ttest_commit one text &&\n+\ttest_write_lines one owt three >text &&\n+\ttest_commit two text\n+'\n+\n+test_expect_success 'am warn if quoted-cr is found' '\n+\tgit reset --hard one &&\n+\ttest_must_fail git am \"$DATA/mbox\" 2>err &&\n+\tgrep \"quoted CRLF detected\" err\n+'\n+\n+test_expect_success 'am --quoted-cr=strip' '\n+\ttest_might_fail git am --abort &&\n+\tgit reset --hard one &&\n+\tgit am --quoted-cr=strip \"$DATA/mbox\" &&\n+\tgit diff --exit-code HEAD two\n+'\n+\n+test_expect_success 'am with config mailinfo.quotecr=strip' '\n+\ttest_might_fail git am --abort &&\n+\tgit reset --hard one &&\n+\ttest_config mailinfo.quotedCr strip &&\n+\tgit am \"$DATA/mbox\" &&\n+\tgit diff --exit-code HEAD two\n+'\n+\n+test_done\ndiff --git a/t/t4258/mbox b/t/t4258/mbox\nnew file mode 100644\nindex 0000000000..c62819f3d2\n--- /dev/null\n+++ b/t/t4258/mbox\n@@ -0,0 +1,12 @@\n+From: A U Thor <mail@example.com>\n+To: list@example.org\n+Subject: [PATCH v2] sample\n+Date: Mon,  3 Aug 2020 22:40:55 +0700\n+Message-Id: <msg-id@example.com>\n+Content-Type: text/plain; charset=\"utf-8\"\n+Content-Transfer-Encoding: base64\n+\n+VGhpcyBpcyBjb21taXQgbWVzc2FnZS4NCi0tLQ0KIHRleHQgfCAyICstDQogMSBmaWxlIGNoYW5n\n+ZWQsIDEgaW5zZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS90ZXh0IGIv\n+dGV4dA0KaW5kZXggNTYyNmFiZi4uZjcxOWVmZCAxMDA2NDQNCi0tLSBhL3RleHQNCisrKyBiL3Rl\n+eHQNCkBAIC0xICsxIEBADQotb25lDQordHdvDQotLSANCjIuMzEuMQoK\n-- \n2.31.1.448.g9c2f8508d1\n\n"}]}