1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-04-25 12:35:11 +02:00

Merge branch 'dd/mailinfo-quoted-cr' into next

"git mailinfo" (hence "git am") learned the "--quoted-cr" option to
control how lines ending with CRLF wrapped in base64 or qp are
handled.

* dd/mailinfo-quoted-cr:
  am: learn to process quoted lines that ends with CRLF
  mailinfo: allow stripping quoted CR without warning
  mailinfo: allow squelching quoted CRLF warning
  mailinfo: warn if CRLF found in decoded base64/QP email
  mailinfo: stop parsing options manually
  mailinfo: load default metainfo_charset lazily
This commit is contained in:
Junio C Hamano 2021-05-12 08:20:29 +09:00
commit a4bcfd18b9
14 changed files with 375 additions and 33 deletions

View File

@ -15,6 +15,7 @@ SYNOPSIS
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
[--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
[--quoted-cr=<action>]
[(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
@ -59,6 +60,9 @@ OPTIONS
--no-scissors::
Ignore scissors lines (see linkgit:git-mailinfo[1]).
--quoted-cr=<action>::
This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
-m::
--message-id::
Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),

View File

@ -9,7 +9,9 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
SYNOPSIS
--------
[verse]
'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>
'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n]
[--[no-]scissors] [--quoted-cr=<action>]
<msg> <patch>
DESCRIPTION
@ -89,6 +91,23 @@ This can be enabled by default with the configuration option mailinfo.scissors.
--no-scissors::
Ignore scissors lines. Useful for overriding mailinfo.scissors settings.
--quoted-cr=<action>::
Action when processes email messages sent with base64 or
quoted-printable encoding, and the decoded lines end with a CRLF
instead of a simple LF.
+
The valid actions are:
+
--
* `nowarn`: Git will do nothing when such a CRLF is found.
* `warn`: Git will issue a warning for each message if such a CRLF is
found.
* `strip`: Git will convert those CRLF to LF.
--
+
The default action could be set by configuration option `mailinfo.quotedCR`.
If no such configuration option has been set, `warn` will be used.
<msg>::
The commit log message extracted from e-mail, usually
except the title line which comes from e-mail Subject.

View File

@ -116,6 +116,7 @@ struct am_state {
int keep; /* enum keep_type */
int message_id;
int scissors; /* enum scissors_type */
int quoted_cr; /* enum quoted_cr_action */
struct strvec git_apply_opts;
const char *resolvemsg;
int committer_date_is_author_date;
@ -145,6 +146,7 @@ static void am_state_init(struct am_state *state)
git_config_get_bool("am.messageid", &state->message_id);
state->scissors = SCISSORS_UNSET;
state->quoted_cr = quoted_cr_unset;
strvec_init(&state->git_apply_opts);
@ -165,6 +167,16 @@ static void am_state_release(struct am_state *state)
strvec_clear(&state->git_apply_opts);
}
static int am_option_parse_quoted_cr(const struct option *opt,
const char *arg, int unset)
{
BUG_ON_OPT_NEG(unset);
if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
return 0;
}
/**
* Returns path relative to the am_state directory.
*/
@ -397,6 +409,12 @@ static void am_load(struct am_state *state)
else
state->scissors = SCISSORS_UNSET;
read_state_file(&sb, state, "quoted-cr", 1);
if (!*sb.buf)
state->quoted_cr = quoted_cr_unset;
else if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)
die(_("could not parse %s"), am_path(state, "quoted-cr"));
read_state_file(&sb, state, "apply-opt", 1);
strvec_clear(&state->git_apply_opts);
if (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)
@ -1002,6 +1020,24 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
}
write_state_text(state, "scissors", str);
switch (state->quoted_cr) {
case quoted_cr_unset:
str = "";
break;
case quoted_cr_nowarn:
str = "nowarn";
break;
case quoted_cr_warn:
str = "warn";
break;
case quoted_cr_strip:
str = "strip";
break;
default:
BUG("invalid value for state->quoted_cr");
}
write_state_text(state, "quoted-cr", str);
sq_quote_argv(&sb, state->git_apply_opts.v);
write_state_text(state, "apply-opt", sb.buf);
@ -1162,6 +1198,18 @@ static int parse_mail(struct am_state *state, const char *mail)
BUG("invalid value for state->scissors");
}
switch (state->quoted_cr) {
case quoted_cr_unset:
break;
case quoted_cr_nowarn:
case quoted_cr_warn:
case quoted_cr_strip:
mi.quoted_cr = state->quoted_cr;
break;
default:
BUG("invalid value for state->quoted_cr");
}
mi.input = xfopen(mail, "r");
mi.output = xfopen(am_path(state, "info"), "w");
if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch")))
@ -2242,6 +2290,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
0, PARSE_OPT_NONEG),
OPT_BOOL('c', "scissors", &state.scissors,
N_("strip everything before a scissors line")),
OPT_CALLBACK_F(0, "quoted-cr", &state.quoted_cr, N_("action"),
N_("pass it through git-mailinfo"),
PARSE_OPT_NONEG, am_option_parse_quoted_cr),
OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
N_("pass it through git-apply"),
0),

View File

@ -7,54 +7,103 @@
#include "utf8.h"
#include "strbuf.h"
#include "mailinfo.h"
#include "parse-options.h"
static const char mailinfo_usage[] =
"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
static const char * const mailinfo_usage[] = {
/* TRANSLATORS: keep <> in "<" mail ">" info. */
N_("git mailinfo [<options>] <msg> <patch> < mail >info"),
NULL,
};
struct metainfo_charset
{
enum {
CHARSET_DEFAULT,
CHARSET_NO_REENCODE,
CHARSET_EXPLICIT,
} policy;
const char *charset;
};
static int parse_opt_explicit_encoding(const struct option *opt,
const char *arg, int unset)
{
struct metainfo_charset *meta_charset = opt->value;
BUG_ON_OPT_NEG(unset);
meta_charset->policy = CHARSET_EXPLICIT;
meta_charset->charset = arg;
return 0;
}
static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)
{
BUG_ON_OPT_NEG(unset);
if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
return 0;
}
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
const char *def_charset;
struct metainfo_charset meta_charset;
struct mailinfo mi;
int status;
char *msgfile, *patchfile;
struct option options[] = {
OPT_BOOL('k', NULL, &mi.keep_subject, N_("keep subject")),
OPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,
N_("keep non patch brackets in subject")),
OPT_BOOL('m', "message-id", &mi.add_message_id,
N_("copy Message-ID to the end of commit message")),
OPT_SET_INT_F('u', NULL, &meta_charset.policy,
N_("re-code metadata to i18n.commitEncoding"),
CHARSET_DEFAULT, PARSE_OPT_NONEG),
OPT_SET_INT_F('n', NULL, &meta_charset.policy,
N_("disable charset re-coding of metadata"),
CHARSET_NO_REENCODE, PARSE_OPT_NONEG),
OPT_CALLBACK_F(0, "encoding", &meta_charset, N_("encoding"),
N_("re-code metadata to this encoding"),
PARSE_OPT_NONEG, parse_opt_explicit_encoding),
OPT_BOOL(0, "scissors", &mi.use_scissors, N_("use scissors")),
OPT_CALLBACK_F(0, "quoted-cr", &mi.quoted_cr, N_("<action>"),
N_("action when quoted CR is found"),
PARSE_OPT_NONEG, parse_opt_quoted_cr),
OPT_HIDDEN_BOOL(0, "inbody-headers", &mi.use_inbody_headers,
N_("use headers in message's body")),
OPT_END()
};
setup_mailinfo(&mi);
meta_charset.policy = CHARSET_DEFAULT;
def_charset = get_commit_output_encoding();
mi.metainfo_charset = def_charset;
argc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "-k"))
mi.keep_subject = 1;
else if (!strcmp(argv[1], "-b"))
mi.keep_non_patch_brackets_in_subject = 1;
else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
mi.add_message_id = 1;
else if (!strcmp(argv[1], "-u"))
mi.metainfo_charset = def_charset;
else if (!strcmp(argv[1], "-n"))
mi.metainfo_charset = NULL;
else if (starts_with(argv[1], "--encoding="))
mi.metainfo_charset = argv[1] + 11;
else if (!strcmp(argv[1], "--scissors"))
mi.use_scissors = 1;
else if (!strcmp(argv[1], "--no-scissors"))
mi.use_scissors = 0;
else if (!strcmp(argv[1], "--no-inbody-headers"))
mi.use_inbody_headers = 0;
else
usage(mailinfo_usage);
argc--; argv++;
if (argc != 2)
usage_with_options(mailinfo_usage, options);
switch (meta_charset.policy) {
case CHARSET_DEFAULT:
mi.metainfo_charset = get_commit_output_encoding();
break;
case CHARSET_NO_REENCODE:
mi.metainfo_charset = NULL;
break;
case CHARSET_EXPLICIT:
break;
default:
BUG("invalid meta_charset.policy");
}
if (argc != 3)
usage(mailinfo_usage);
mi.input = stdin;
mi.output = stdout;
msgfile = prefix_filename(prefix, argv[1]);
patchfile = prefix_filename(prefix, argv[2]);
msgfile = prefix_filename(prefix, argv[0]);
patchfile = prefix_filename(prefix, argv[1]);
status = !!mailinfo(&mi, msgfile, patchfile);
clear_mailinfo(&mi);

View File

@ -1333,6 +1333,7 @@ __git_whitespacelist="nowarn warn error error-all fix"
__git_patchformat="mbox stgit stgit-series hg mboxrd"
__git_showcurrentpatch="diff raw"
__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
__git_quoted_cr="nowarn warn strip"
_git_am ()
{
@ -1354,6 +1355,10 @@ _git_am ()
__gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}"
return
;;
--quoted-cr=*)
__gitcomp "$__git_quoted_cr" "" "${cur##--quoted-cr=}"
return
;;
--*)
__gitcomp_builtin am "" \
"$__git_am_inprogress_options"

View File

@ -994,6 +994,16 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
const char *rest;
if (!mi->format_flowed) {
if (len >= 2 &&
line->buf[len - 2] == '\r' &&
line->buf[len - 1] == '\n') {
mi->have_quoted_cr = 1;
if (mi->quoted_cr == quoted_cr_strip) {
strbuf_setlen(line, len - 2);
strbuf_addch(line, '\n');
len--;
}
}
handle_filter(mi, line);
return;
}
@ -1033,6 +1043,13 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
handle_filter(mi, line);
}
static void summarize_quoted_cr(struct mailinfo *mi)
{
if (mi->have_quoted_cr &&
mi->quoted_cr == quoted_cr_warn)
warning(_("quoted CRLF detected"));
}
static void handle_body(struct mailinfo *mi, struct strbuf *line)
{
struct strbuf prev = STRBUF_INIT;
@ -1051,6 +1068,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
handle_filter(mi, &prev);
strbuf_reset(&prev);
}
summarize_quoted_cr(mi);
mi->have_quoted_cr = 0;
if (!handle_boundary(mi, line))
goto handle_body_out;
}
@ -1100,6 +1119,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
if (prev.len)
handle_filter(mi, &prev);
summarize_quoted_cr(mi);
flush_inbody_header_accum(mi);
@ -1206,6 +1226,19 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
return mi->input_error;
}
int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)
{
if (!strcmp(actionstr, "nowarn"))
*action = quoted_cr_nowarn;
else if (!strcmp(actionstr, "warn"))
*action = quoted_cr_warn;
else if (!strcmp(actionstr, "strip"))
*action = quoted_cr_strip;
else
return -1;
return 0;
}
static int git_mailinfo_config(const char *var, const char *value, void *mi_)
{
struct mailinfo *mi = mi_;
@ -1216,6 +1249,11 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)
mi->use_scissors = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "mailinfo.quotedcr")) {
if (mailinfo_parse_quoted_cr_action(value, &mi->quoted_cr) != 0)
return error(_("bad action '%s' for '%s'"), value, var);
return 0;
}
/* perhaps others here */
return 0;
}
@ -1228,6 +1266,7 @@ void setup_mailinfo(struct mailinfo *mi)
strbuf_init(&mi->charset, 0);
strbuf_init(&mi->log_message, 0);
strbuf_init(&mi->inbody_header_accum, 0);
mi->quoted_cr = quoted_cr_warn;
mi->header_stage = 1;
mi->use_inbody_headers = 1;
mi->content_top = mi->content;

View File

@ -5,6 +5,13 @@
#define MAX_BOUNDARIES 5
enum quoted_cr_action {
quoted_cr_unset = -1,
quoted_cr_nowarn,
quoted_cr_warn,
quoted_cr_strip,
};
struct mailinfo {
FILE *input;
FILE *output;
@ -14,6 +21,7 @@ struct mailinfo {
struct strbuf email;
int keep_subject;
int keep_non_patch_brackets_in_subject;
int quoted_cr; /* enum quoted_cr_action */
int add_message_id;
int use_scissors;
int use_inbody_headers;
@ -24,6 +32,7 @@ struct mailinfo {
struct strbuf charset;
unsigned int format_flowed:1;
unsigned int delsp:1;
unsigned int have_quoted_cr:1;
char *message_id;
enum {
TE_DONTCARE, TE_QP, TE_BASE64
@ -39,6 +48,7 @@ struct mailinfo {
int input_error;
};
int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action);
void setup_mailinfo(struct mailinfo *);
int mailinfo(struct mailinfo *, const char *msg, const char *patch);
void clear_mailinfo(struct mailinfo *);

37
t/t4258-am-quoted-cr.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/sh
test_description='test am --quoted-cr=<action>'
. ./test-lib.sh
DATA="$TEST_DIRECTORY/t4258"
test_expect_success 'setup' '
test_write_lines one two three >text &&
test_commit one text &&
test_write_lines one owt three >text &&
test_commit two text
'
test_expect_success 'am warn if quoted-cr is found' '
git reset --hard one &&
test_must_fail git am "$DATA/mbox" 2>err &&
grep "quoted CRLF detected" err
'
test_expect_success 'am --quoted-cr=strip' '
test_might_fail git am --abort &&
git reset --hard one &&
git am --quoted-cr=strip "$DATA/mbox" &&
git diff --exit-code HEAD two
'
test_expect_success 'am with config mailinfo.quotecr=strip' '
test_might_fail git am --abort &&
git reset --hard one &&
test_config mailinfo.quotedCr strip &&
git am "$DATA/mbox" &&
git diff --exit-code HEAD two
'
test_done

12
t/t4258/mbox Normal file
View File

@ -0,0 +1,12 @@
From: A U Thor <mail@example.com>
To: list@example.org
Subject: [PATCH v2] sample
Date: Mon, 3 Aug 2020 22:40:55 +0700
Message-Id: <msg-id@example.com>
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
VGhpcyBpcyBjb21taXQgbWVzc2FnZS4NCi0tLQ0KIHRleHQgfCAyICstDQogMSBmaWxlIGNoYW5n
ZWQsIDEgaW5zZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS90ZXh0IGIv
dGV4dA0KaW5kZXggNTYyNmFiZi4uZjcxOWVmZCAxMDA2NDQNCi0tLSBhL3RleHQNCisrKyBiL3Rl
eHQNCkBAIC0xICsxIEBADQotb25lDQordHdvDQotLSANCjIuMzEuMQoK

View File

@ -228,4 +228,44 @@ test_expect_success 'mailinfo handles unusual header whitespace' '
test_cmp expect actual
'
check_quoted_cr_mail () {
mail="$1" && shift &&
git mailinfo -u "$@" "$mail.msg" "$mail.patch" \
<"$mail" >"$mail.info" 2>"$mail.err" &&
test_cmp "$mail-expected.msg" "$mail.msg" &&
test_cmp "$mail-expected.patch" "$mail.patch" &&
test_cmp "$DATA/quoted-cr-info" "$mail.info"
}
test_expect_success 'split base64 email with quoted-cr' '
mkdir quoted-cr &&
git mailsplit -oquoted-cr "$DATA/quoted-cr.mbox" >quoted-cr/last &&
test $(cat quoted-cr/last) = 2
'
test_expect_success 'mailinfo warn CR in base64 encoded email' '
sed -e "s/%%$//" -e "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-msg" \
>quoted-cr/0001-expected.msg &&
sed "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-msg" \
>quoted-cr/0002-expected.msg &&
sed -e "s/%%$//" -e "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-patch" \
>quoted-cr/0001-expected.patch &&
sed "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-patch" \
>quoted-cr/0002-expected.patch &&
check_quoted_cr_mail quoted-cr/0001 &&
test_must_be_empty quoted-cr/0001.err &&
check_quoted_cr_mail quoted-cr/0002 &&
grep "quoted CRLF detected" quoted-cr/0002.err &&
check_quoted_cr_mail quoted-cr/0001 --quoted-cr=nowarn &&
test_must_be_empty quoted-cr/0001.err &&
check_quoted_cr_mail quoted-cr/0002 --quoted-cr=nowarn &&
test_must_be_empty quoted-cr/0002.err &&
cp quoted-cr/0001-expected.msg quoted-cr/0002-expected.msg &&
cp quoted-cr/0001-expected.patch quoted-cr/0002-expected.patch &&
check_quoted_cr_mail quoted-cr/0001 --quoted-cr=strip &&
test_must_be_empty quoted-cr/0001.err &&
check_quoted_cr_mail quoted-cr/0002 --quoted-cr=strip &&
test_must_be_empty quoted-cr/0002.err
'
test_done

5
t/t5100/quoted-cr-info Normal file
View File

@ -0,0 +1,5 @@
Author: A U Thor
Email: mail@example.com
Subject: sample
Date: Mon, 3 Aug 2020 22:40:55 +0700

2
t/t5100/quoted-cr-msg Normal file
View File

@ -0,0 +1,2 @@
On different distro, %%pytest is suffixed with different patterns.%%
%%

22
t/t5100/quoted-cr-patch Normal file
View File

@ -0,0 +1,22 @@
---%%
configure | 2 +-%%
1 file changed, 1 insertion(+), 1 deletion(-)%%
%%
diff --git a/configure b/configure%%
index db3538b3..f7c1c095 100755%%
--- a/configure%%
+++ b/configure%%
@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%
printf "%%Checking for python3 pytest (>= 3.0)... "%%
conf=$(mktemp)%%
printf "[pytest]\nminversion=3.0\n" > $conf%%
- if pytest-3 -c $conf --version >/dev/null 2>&1; then%%
+ if "$python" -m pytest -c $conf --version >/dev/null 2>&1; then%%
printf "Yes.\n"%%
have_python3_pytest=1%%
else%%
-- %%
2.28.0%%
_______________________________________________
example mailing list -- list@example.org
To unsubscribe send an email to list-leave@example.org

47
t/t5100/quoted-cr.mbox Normal file
View File

@ -0,0 +1,47 @@
From nobody Mon Sep 17 00:00:00 2001
From: A U Thor <mail@example.com>
To: list@example.org
Subject: [PATCH v2] sample
Date: Mon, 3 Aug 2020 22:40:55 +0700
Message-Id: <msg-id@example.com>
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw
YXR0ZXJucy4KCi0tLQogY29uZmlndXJlIHwgMiArLQogMSBmaWxlIGNoYW5nZWQsIDEgaW5zZXJ0
aW9uKCspLCAxIGRlbGV0aW9uKC0pCgpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29uZmlndXJl
CmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUKLS0tIGEvY29uZmlndXJlCisrKyBiL2Nv
bmZpZ3VyZQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAtZXEgMSBdOyB0
aGVuCiAgICAgcHJpbnRmICINQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+PSAzLjApLi4u
ICIKICAgICBjb25mPSQobWt0ZW1wKQogICAgIHByaW50ZiAiW3B5dGVzdF1cbm1pbnZlcnNpb249
My4wXG4iID4gJGNvbmYKLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYv
bnVsbCAyPiYxOyB0aGVuCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVzdCAtYyAkY29uZiAtLXZl
cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAgICAgIHByaW50ZiAiWWVzLlxuIgogICAg
ICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTEKICAgICBlbHNlCi0tIAoyLjI4LjAKX19fX19fX19f
X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KZXhhbXBsZSBtYWlsaW5nIGxp
c3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmliZSBzZW5kIGFuIGVtYWlsIHRvIGxp
c3QtbGVhdmVAZXhhbXBsZS5vcmcK
From nobody Mon Sep 17 00:00:00 2001
From: A U Thor <mail@example.com>
To: list@example.org
Subject: [PATCH v2] sample
Date: Mon, 3 Aug 2020 22:40:55 +0700
Message-Id: <msg-id2@example.com>
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw
YXR0ZXJucy4NCg0KLS0tDQogY29uZmlndXJlIHwgMiArLQ0KIDEgZmlsZSBjaGFuZ2VkLCAxIGlu
c2VydGlvbigrKSwgMSBkZWxldGlvbigtKQ0KDQpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29u
ZmlndXJlDQppbmRleCBkYjM1MzhiMy4uZjdjMWMwOTUgMTAwNzU1DQotLS0gYS9jb25maWd1cmUN
CisrKyBiL2NvbmZpZ3VyZQ0KQEAgLTgxNCw3ICs4MTQsNyBAQCBpZiBbICRoYXZlX3B5dGhvbjMg
LWVxIDEgXTsgdGhlbg0KICAgICBwcmludGYgIg1DaGVja2luZyBmb3IgcHl0aG9uMyBweXRlc3Qg
KD49IDMuMCkuLi4gIg0KICAgICBjb25mPSQobWt0ZW1wKQ0KICAgICBwcmludGYgIltweXRlc3Rd
XG5taW52ZXJzaW9uPTMuMFxuIiA+ICRjb25mDQotICAgIGlmIHB5dGVzdC0zIC1jICRjb25mIC0t
dmVyc2lvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4NCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVz
dCAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQogICAgICAgICBwcmlu
dGYgIlllcy5cbiINCiAgICAgICAgIGhhdmVfcHl0aG9uM19weXRlc3Q9MQ0KICAgICBlbHNlDQot
LSANCjIuMjguMA0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f
X18KZXhhbXBsZSBtYWlsaW5nIGxpc3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmli
ZSBzZW5kIGFuIGVtYWlsIHRvIGxpc3QtbGVhdmVAZXhhbXBsZS5vcmcK