Merge branch 'bc/send-email-auto-cte'

The content-transfer-encoding of the message "git send-email" sends
out by default was 8bit, which can cause trouble when there is an
overlong line to bust RFC 5322/2822 limit.  A new option 'auto' to
automatically switch to quoted-printable when there is such a line
in the payload has been introduced and is made the default.

* bc/send-email-auto-cte:
  docs: correct RFC specifying email line length
  send-email: automatically determine transfer-encoding
  send-email: accept long lines with suitable transfer encoding
  send-email: add an auto option for transfer encoding
This commit is contained in:
Junio C Hamano
2018-07-24 14:50:47 -07:00
3 changed files with 91 additions and 29 deletions

View File

@ -137,15 +137,17 @@ Note that no attempts whatsoever are made to validate the encoding.
Specify encoding of compose message. Default is the value of the Specify encoding of compose message. Default is the value of the
'sendemail.composeencoding'; if that is unspecified, UTF-8 is assumed. 'sendemail.composeencoding'; if that is unspecified, UTF-8 is assumed.
--transfer-encoding=(7bit|8bit|quoted-printable|base64):: --transfer-encoding=(7bit|8bit|quoted-printable|base64|auto)::
Specify the transfer encoding to be used to send the message over SMTP. Specify the transfer encoding to be used to send the message over SMTP.
7bit will fail upon encountering a non-ASCII message. quoted-printable 7bit will fail upon encountering a non-ASCII message. quoted-printable
can be useful when the repository contains files that contain carriage can be useful when the repository contains files that contain carriage
returns, but makes the raw patch email file (as saved from a MUA) much returns, but makes the raw patch email file (as saved from a MUA) much
harder to inspect manually. base64 is even more fool proof, but also harder to inspect manually. base64 is even more fool proof, but also
even more opaque. Default is the value of the `sendemail.transferEncoding` even more opaque. auto will use 8bit when possible, and quoted-printable
configuration value; if that is unspecified, git will use 8bit and not otherwise.
add a Content-Transfer-Encoding header. +
Default is the value of the `sendemail.transferEncoding` configuration
value; if that is unspecified, default to `auto`.
--xmailer:: --xmailer::
--no-xmailer:: --no-xmailer::
@ -398,8 +400,11 @@ have been specified, in which case default to 'compose'.
+ +
-- --
* Invoke the sendemail-validate hook if present (see linkgit:githooks[5]). * Invoke the sendemail-validate hook if present (see linkgit:githooks[5]).
* Warn of patches that contain lines longer than 998 characters; this * Warn of patches that contain lines longer than
is due to SMTP limits as described by http://www.ietf.org/rfc/rfc2821.txt. 998 characters unless a suitable transfer encoding
('auto', 'base64', or 'quoted-printable') is used;
this is due to SMTP limits as described by
http://www.ietf.org/rfc/rfc5322.txt.
-- --
+ +
Default is the value of `sendemail.validate`; if this is not set, Default is the value of `sendemail.validate`; if this is not set,

View File

@ -231,7 +231,7 @@ my ($validate, $confirm);
my (@suppress_cc); my (@suppress_cc);
my ($auto_8bit_encoding); my ($auto_8bit_encoding);
my ($compose_encoding); my ($compose_encoding);
my ($target_xfer_encoding); my $target_xfer_encoding = 'auto';
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message() my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
@ -645,7 +645,7 @@ if (@rev_list_opts) {
if ($validate) { if ($validate) {
foreach my $f (@files) { foreach my $f (@files) {
unless (-p $f) { unless (-p $f) {
my $error = validate_patch($f); my $error = validate_patch($f, $target_xfer_encoding);
$error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"), $error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"),
$f, $error); $f, $error);
} }
@ -1737,18 +1737,11 @@ sub process_file {
} }
} }
} }
if (defined $target_xfer_encoding) { $xfer_encoding = '8bit' if not defined $xfer_encoding;
$xfer_encoding = '8bit' if not defined $xfer_encoding; ($message, $xfer_encoding) = apply_transfer_encoding(
$message = apply_transfer_encoding( $message, $xfer_encoding, $target_xfer_encoding);
$message, $xfer_encoding, $target_xfer_encoding); push @xh, "Content-Transfer-Encoding: $xfer_encoding";
$xfer_encoding = $target_xfer_encoding; unshift @xh, 'MIME-Version: 1.0' unless $has_mime_version;
}
if (defined $xfer_encoding) {
push @xh, "Content-Transfer-Encoding: $xfer_encoding";
}
if (defined $xfer_encoding or $has_content_type) {
unshift @xh, 'MIME-Version: 1.0' unless $has_mime_version;
}
$needs_confirm = ( $needs_confirm = (
$confirm eq "always" or $confirm eq "always" or
@ -1852,13 +1845,16 @@ sub apply_transfer_encoding {
$message = MIME::Base64::decode($message) $message = MIME::Base64::decode($message)
if ($from eq 'base64'); if ($from eq 'base64');
$to = ($message =~ /.{999,}/) ? 'quoted-printable' : '8bit'
if $to eq 'auto';
die __("cannot send message as 7bit") die __("cannot send message as 7bit")
if ($to eq '7bit' and $message =~ /[^[:ascii:]]/); if ($to eq '7bit' and $message =~ /[^[:ascii:]]/);
return $message return ($message, $to)
if ($to eq '7bit' or $to eq '8bit'); if ($to eq '7bit' or $to eq '8bit');
return MIME::QuotedPrint::encode($message, "\n", 0) return (MIME::QuotedPrint::encode($message, "\n", 0), $to)
if ($to eq 'quoted-printable'); if ($to eq 'quoted-printable');
return MIME::Base64::encode($message, "\n") return (MIME::Base64::encode($message, "\n"), $to)
if ($to eq 'base64'); if ($to eq 'base64');
die __("invalid transfer encoding"); die __("invalid transfer encoding");
} }
@ -1877,7 +1873,7 @@ sub unique_email_list {
} }
sub validate_patch { sub validate_patch {
my $fn = shift; my ($fn, $xfer_encoding) = @_;
if ($repo) { if ($repo) {
my $validate_hook = catfile(catdir($repo->repo_path(), 'hooks'), my $validate_hook = catfile(catdir($repo->repo_path(), 'hooks'),
@ -1897,11 +1893,15 @@ sub validate_patch {
return $hook_error if $hook_error; return $hook_error if $hook_error;
} }
open(my $fh, '<', $fn) # Any long lines will be automatically fixed if we use a suitable transfer
or die sprintf(__("unable to open %s: %s\n"), $fn, $!); # encoding.
while (my $line = <$fh>) { unless ($xfer_encoding =~ /^(?:auto|quoted-printable|base64)$/) {
if (length($line) > 998) { open(my $fh, '<', $fn)
return sprintf(__("%s: patch contains a line longer than 998 characters"), $.); or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
while (my $line = <$fh>) {
if (length($line) > 998) {
return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
}
} }
} }
return; return;

View File

@ -225,6 +225,8 @@ X-Mailer: X-MAILER-STRING
In-Reply-To: <unique-message-id@example.com> In-Reply-To: <unique-message-id@example.com>
References: <unique-message-id@example.com> References: <unique-message-id@example.com>
Reply-To: Reply <reply@example.com> Reply-To: Reply <reply@example.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -415,6 +417,7 @@ test_expect_success $PREREQ 'reject long lines' '
--from="Example <nobody@example.com>" \ --from="Example <nobody@example.com>" \
--to=nobody@example.com \ --to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \ --smtp-server="$(pwd)/fake.sendmail" \
--transfer-encoding=8bit \
$patches longline.patch \ $patches longline.patch \
2>errors && 2>errors &&
grep longline.patch errors grep longline.patch errors
@ -456,6 +459,42 @@ test_expect_success $PREREQ 'allow long lines with --no-validate' '
2>errors 2>errors
' '
test_expect_success $PREREQ 'short lines with auto encoding are 8bit' '
clean_fake_sendmail &&
git send-email \
--from="A <author@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--transfer-encoding=auto \
$patches &&
grep "Content-Transfer-Encoding: 8bit" msgtxt1
'
test_expect_success $PREREQ 'long lines with auto encoding are quoted-printable' '
clean_fake_sendmail &&
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--transfer-encoding=auto \
--no-validate \
longline.patch &&
grep "Content-Transfer-Encoding: quoted-printable" msgtxt1
'
for enc in auto quoted-printable base64
do
test_expect_success $PREREQ "--validate passes with encoding $enc" '
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--transfer-encoding=$enc \
--validate \
$patches longline.patch
'
done
test_expect_success $PREREQ 'Invalid In-Reply-To' ' test_expect_success $PREREQ 'Invalid In-Reply-To' '
clean_fake_sendmail && clean_fake_sendmail &&
git send-email \ git send-email \
@ -573,6 +612,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -617,6 +658,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -652,6 +695,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -678,6 +723,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -712,6 +759,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -743,6 +792,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -774,6 +825,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -809,6 +862,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF
@ -837,6 +892,8 @@ Subject: [PATCH 1/1] Second.
Date: DATE-STRING Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING X-Mailer: X-MAILER-STRING
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Result: OK Result: OK
EOF EOF