Skip to content

Commit 64477d2

Browse files
committed
Merge branch 'mc/send-email-header-cmd'
"git send-email" learned "--header-cmd=<cmd>" that can inject arbitrary e-mail header lines to the outgoing messages. * mc/send-email-header-cmd: send-email: detect empty blank lines in command output send-email: add --header-cmd, --no-header-cmd options send-email: extract execute_cmd from recipients_cmd
2 parents b14a730 + 3a7a18a commit 64477d2

File tree

4 files changed

+151
-20
lines changed

4 files changed

+151
-20
lines changed

Documentation/config/sendemail.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ sendemail.ccCmd::
6161
sendemail.chainReplyTo::
6262
sendemail.envelopeSender::
6363
sendemail.from::
64+
sendemail.headerCmd::
6465
sendemail.signedoffbycc::
6566
sendemail.smtpPass::
6667
sendemail.suppresscc::

Documentation/git-send-email.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,17 @@ Automating
320320
Output of this command must be single email address per line.
321321
Default is the value of `sendemail.ccCmd` configuration value.
322322

323+
--header-cmd=<command>::
324+
Specify a command that is executed once per outgoing message
325+
and output RFC 2822 style header lines to be inserted into
326+
them. When the `sendemail.headerCmd` configuration variable is
327+
set, its value is always used. When --header-cmd is provided
328+
at the command line, its value takes precedence over the
329+
`sendemail.headerCmd` configuration variable.
330+
331+
--no-header-cmd::
332+
Disable any header command in use.
333+
323334
--[no-]chain-reply-to::
324335
If this is set, each email will be sent as a reply to the previous
325336
email sent. If disabled with "--no-chain-reply-to", all emails after

git-send-email.perl

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ sub usage {
8787
8888
Automating:
8989
--identity <str> * Use the sendemail.<id> options.
90-
--to-cmd <str> * Email To: via `<str> \$patch_path`
91-
--cc-cmd <str> * Email Cc: via `<str> \$patch_path`
90+
--to-cmd <str> * Email To: via `<str> \$patch_path`.
91+
--cc-cmd <str> * Email Cc: via `<str> \$patch_path`.
92+
--header-cmd <str> * Add headers via `<str> \$patch_path`.
93+
--no-header-cmd * Disable any header command in use.
9294
--suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, misc-by, all.
9395
--[no-]cc-cover * Email Cc: addresses in the cover letter.
9496
--[no-]to-cover * Email To: addresses in the cover letter.
@@ -202,7 +204,7 @@ sub format_2822_time {
202204
$author,$sender,$smtp_authpass,$annotate,$compose,$time);
203205
# Things we either get from config, *or* are overridden on the
204206
# command-line.
205-
my ($no_cc, $no_to, $no_bcc, $no_identity);
207+
my ($no_cc, $no_to, $no_bcc, $no_identity, $no_header_cmd);
206208
my (@config_to, @getopt_to);
207209
my (@config_cc, @getopt_cc);
208210
my (@config_bcc, @getopt_bcc);
@@ -269,7 +271,7 @@ sub do_edit {
269271
# Variables with corresponding config settings
270272
my ($suppress_from, $signed_off_by_cc);
271273
my ($cover_cc, $cover_to);
272-
my ($to_cmd, $cc_cmd);
274+
my ($to_cmd, $cc_cmd, $header_cmd);
273275
my ($smtp_server, $smtp_server_port, @smtp_server_options);
274276
my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
275277
my ($batch_size, $relogin_delay);
@@ -318,6 +320,7 @@ sub do_edit {
318320
"tocmd" => \$to_cmd,
319321
"cc" => \@config_cc,
320322
"cccmd" => \$cc_cmd,
323+
"headercmd" => \$header_cmd,
321324
"aliasfiletype" => \$aliasfiletype,
322325
"bcc" => \@config_bcc,
323326
"suppresscc" => \@suppress_cc,
@@ -519,6 +522,8 @@ sub config_regexp {
519522
"compose" => \$compose,
520523
"quiet" => \$quiet,
521524
"cc-cmd=s" => \$cc_cmd,
525+
"header-cmd=s" => \$header_cmd,
526+
"no-header-cmd" => \$no_header_cmd,
522527
"suppress-from!" => \$suppress_from,
523528
"no-suppress-from" => sub {$suppress_from = 0},
524529
"suppress-cc=s" => \@suppress_cc,
@@ -1783,16 +1788,16 @@ sub pre_process_file {
17831788
$subject = $initial_subject;
17841789
$message = "";
17851790
$message_num++;
1786-
# First unfold multiline header fields
1791+
# Retrieve and unfold header fields.
1792+
my @header_lines = ();
17871793
while(<$fh>) {
17881794
last if /^\s*$/;
1789-
if (/^\s+\S/ and @header) {
1790-
chomp($header[$#header]);
1791-
s/^\s+/ /;
1792-
$header[$#header] .= $_;
1793-
} else {
1794-
push(@header, $_);
1795-
}
1795+
push(@header_lines, $_);
1796+
}
1797+
@header = unfold_headers(@header_lines);
1798+
# Add computed headers, if applicable.
1799+
unless ($no_header_cmd || ! $header_cmd) {
1800+
push @header, invoke_header_cmd($header_cmd, $t);
17961801
}
17971802
# Now parse the header
17981803
foreach(@header) {
@@ -2033,15 +2038,64 @@ sub process_file {
20332038
}
20342039
}
20352040

2041+
# Execute a command and return its output lines as an array. Blank
2042+
# lines which do not appear at the end of the output are reported as
2043+
# errors.
2044+
sub execute_cmd {
2045+
my ($prefix, $cmd, $file) = @_;
2046+
my @lines = ();
2047+
my $seen_blank_line = 0;
2048+
open my $fh, "-|", "$cmd \Q$file\E"
2049+
or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd);
2050+
while (my $line = <$fh>) {
2051+
die sprintf(__("(%s) Malformed output from '%s'"), $prefix, $cmd)
2052+
if $seen_blank_line;
2053+
if ($line =~ /^$/) {
2054+
$seen_blank_line = $line =~ /^$/;
2055+
next;
2056+
}
2057+
push @lines, $line;
2058+
}
2059+
close $fh
2060+
or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd);
2061+
return @lines;
2062+
}
2063+
2064+
# Process headers lines, unfolding multiline headers as defined by RFC
2065+
# 2822.
2066+
sub unfold_headers {
2067+
my @headers;
2068+
foreach(@_) {
2069+
last if /^\s*$/;
2070+
if (/^\s+\S/ and @headers) {
2071+
chomp($headers[$#headers]);
2072+
s/^\s+/ /;
2073+
$headers[$#headers] .= $_;
2074+
} else {
2075+
push(@headers, $_);
2076+
}
2077+
}
2078+
return @headers;
2079+
}
2080+
2081+
# Invoke the provided CMD with FILE as an argument, which should
2082+
# output RFC 2822 email headers. Fold multiline headers and return the
2083+
# headers as an array.
2084+
sub invoke_header_cmd {
2085+
my ($cmd, $file) = @_;
2086+
my @lines = execute_cmd("header-cmd", $header_cmd, $file);
2087+
return unfold_headers(@lines);
2088+
}
2089+
20362090
# Execute a command (e.g. $to_cmd) to get a list of email addresses
20372091
# and return a results array
20382092
sub recipients_cmd {
20392093
my ($prefix, $what, $cmd, $file, $quiet) = @_;
2040-
2094+
my @lines = ();
20412095
my @addresses = ();
2042-
open my $fh, "-|", "$cmd \Q$file\E"
2043-
or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd);
2044-
while (my $address = <$fh>) {
2096+
2097+
@lines = execute_cmd($prefix, $cmd, $file);
2098+
for my $address (@lines) {
20452099
$address =~ s/^\s*//g;
20462100
$address =~ s/\s*$//g;
20472101
$address = sanitize_address($address);
@@ -2050,8 +2104,6 @@ sub recipients_cmd {
20502104
printf(__("(%s) Adding %s: %s from: '%s'\n"),
20512105
$prefix, $what, $address, $cmd) unless $quiet;
20522106
}
2053-
close $fh
2054-
or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd);
20552107
return @addresses;
20562108
}
20572109

t/t9001-send-email.sh

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,13 +374,16 @@ test_expect_success $PREREQ,!AUTOIDENT 'broken implicit ident aborts send-email'
374374
)
375375
'
376376

377-
test_expect_success $PREREQ 'setup tocmd and cccmd scripts' '
377+
test_expect_success $PREREQ 'setup cmd scripts' '
378378
write_script tocmd-sed <<-\EOF &&
379379
sed -n -e "s/^tocmd--//p" "$1"
380380
EOF
381-
write_script cccmd-sed <<-\EOF
381+
write_script cccmd-sed <<-\EOF &&
382382
sed -n -e "s/^cccmd--//p" "$1"
383383
EOF
384+
write_script headercmd-sed <<-\EOF
385+
sed -n -e "s/^headercmd--//p" "$1"
386+
EOF
384387
'
385388

386389
test_expect_success $PREREQ 'tocmd works' '
@@ -410,6 +413,70 @@ test_expect_success $PREREQ 'cccmd works' '
410413
grep "^ [email protected]" msgtxt1
411414
'
412415

416+
test_expect_success $PREREQ 'headercmd works' '
417+
clean_fake_sendmail &&
418+
cp $patches headercmd.patch &&
419+
echo "headercmd--X-Debbugs-CC: [email protected]" >>headercmd.patch &&
420+
git send-email \
421+
--from="Example <[email protected]>" \
422+
423+
--header-cmd=./headercmd-sed \
424+
--smtp-server="$(pwd)/fake.sendmail" \
425+
headercmd.patch \
426+
&&
427+
grep "^X-Debbugs-CC: [email protected]" msgtxt1
428+
'
429+
430+
test_expect_success $PREREQ '--no-header-cmd works' '
431+
clean_fake_sendmail &&
432+
cp $patches headercmd.patch &&
433+
echo "headercmd--X-Debbugs-CC: [email protected]" >>headercmd.patch &&
434+
git send-email \
435+
--from="Example <[email protected]>" \
436+
437+
--header-cmd=./headercmd-sed \
438+
--no-header-cmd \
439+
--smtp-server="$(pwd)/fake.sendmail" \
440+
headercmd.patch \
441+
&&
442+
! grep "^X-Debbugs-CC: [email protected]" msgtxt1
443+
'
444+
445+
test_expect_success $PREREQ 'multiline fields are correctly unfolded' '
446+
clean_fake_sendmail &&
447+
cp $patches headercmd.patch &&
448+
write_script headercmd-multiline <<-\EOF &&
449+
echo "X-Debbugs-CC: [email protected]
450+
FoldedField: This is a tale
451+
best told using
452+
multiple lines."
453+
EOF
454+
git send-email \
455+
--from="Example <[email protected]>" \
456+
457+
--header-cmd=./headercmd-multiline \
458+
--smtp-server="$(pwd)/fake.sendmail" \
459+
headercmd.patch &&
460+
grep "^FoldedField: This is a tale best told using multiple lines.$" msgtxt1
461+
'
462+
463+
# Blank lines in the middle of the output of a command are invalid.
464+
test_expect_success $PREREQ 'malform output reported on blank lines in command output' '
465+
clean_fake_sendmail &&
466+
cp $patches headercmd.patch &&
467+
write_script headercmd-malformed-output <<-\EOF &&
468+
echo "X-Debbugs-CC: [email protected]
469+
470+
SomeOtherField: [email protected]"
471+
EOF
472+
! git send-email \
473+
--from="Example <[email protected]>" \
474+
475+
--header-cmd=./headercmd-malformed-output \
476+
--smtp-server="$(pwd)/fake.sendmail" \
477+
headercmd.patch
478+
'
479+
413480
test_expect_success $PREREQ 'reject long lines' '
414481
z8=zzzzzzzz &&
415482
z64=$z8$z8$z8$z8$z8$z8$z8$z8 &&

0 commit comments

Comments
 (0)