summaryrefslogtreecommitdiff
path: root/git-send-email.perl
diff options
context:
space:
mode:
Diffstat (limited to 'git-send-email.perl')
-rwxr-xr-xgit-send-email.perl172
1 files changed, 134 insertions, 38 deletions
diff --git a/git-send-email.perl b/git-send-email.perl
index 98ab33aae7..94c7f76a15 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -56,6 +56,7 @@ git send-email [options] <file | directory | rev-list options >
--in-reply-to <str> * Email "In-Reply-To:"
--annotate * Review each patch that will be sent in an editor.
--compose * Open an editor for introduction.
+ --compose-encoding <str> * Encoding to assume for introduction.
--8bit-encoding <str> * Encoding to assume 8bit mails if undeclared
Sending:
@@ -198,6 +199,7 @@ my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
my ($validate, $confirm);
my (@suppress_cc);
my ($auto_8bit_encoding);
+my ($compose_encoding);
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
@@ -210,6 +212,7 @@ my %config_bool_settings = (
"signedoffbycc" => [\$signed_off_by_cc, undef],
"signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
"validate" => [\$validate, 1],
+ "multiedit" => [\$multiedit, undef]
);
my %config_settings = (
@@ -225,13 +228,16 @@ my %config_settings = (
"cccmd" => \$cc_cmd,
"aliasfiletype" => \$aliasfiletype,
"bcc" => \@bcclist,
- "aliasesfile" => \@alias_files,
"suppresscc" => \@suppress_cc,
"envelopesender" => \$envelope_sender,
- "multiedit" => \$multiedit,
"confirm" => \$confirm,
"from" => \$sender,
"assume8bitencoding" => \$auto_8bit_encoding,
+ "composeencoding" => \$compose_encoding,
+);
+
+my %config_path_settings = (
+ "aliasesfile" => \@alias_files,
);
# Help users prepare for 1.7.0
@@ -275,7 +281,9 @@ $SIG{INT} = \&signal_handler;
# Begin by accumulating all the variables (defined above), that we will end up
# needing, first, from the command line:
-my $rc = GetOptions("sender|from=s" => \$sender,
+my $help;
+my $rc = GetOptions("h" => \$help,
+ "sender|from=s" => \$sender,
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
"to=s" => \@initial_to,
@@ -310,9 +318,11 @@ my $rc = GetOptions("sender|from=s" => \$sender,
"validate!" => \$validate,
"format-patch!" => \$format_patch,
"8bit-encoding=s" => \$auto_8bit_encoding,
+ "compose-encoding=s" => \$compose_encoding,
"force" => \$force,
);
+usage() if $help;
unless ($rc) {
usage();
}
@@ -330,6 +340,19 @@ sub read_config {
$$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target);
}
+ foreach my $setting (keys %config_path_settings) {
+ my $target = $config_path_settings{$setting};
+ if (ref($target) eq "ARRAY") {
+ unless (@$target) {
+ my @values = Git::config_path(@repo, "$prefix.$setting");
+ @$target = @values if (@values && defined $values[0]);
+ }
+ }
+ else {
+ $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target);
+ }
+ }
+
foreach my $setting (keys %config_settings) {
my $target = $config_settings{$setting};
next if $setting eq "to" and defined $no_to;
@@ -613,6 +636,9 @@ EOT
my $need_8bit_cte = file_has_nonascii($compose_filename);
my $in_body = 0;
my $summary_empty = 1;
+ if (!defined $compose_encoding) {
+ $compose_encoding = "UTF-8";
+ }
while(<$c>) {
next if m/^GIT:/;
if ($in_body) {
@@ -622,7 +648,7 @@ EOT
if ($need_8bit_cte) {
print $c2 "MIME-Version: 1.0\n",
"Content-Type: text/plain; ",
- "charset=UTF-8\n",
+ "charset=$compose_encoding\n",
"Content-Transfer-Encoding: 8bit\n";
}
} elsif (/^MIME-Version:/i) {
@@ -631,9 +657,7 @@ EOT
$initial_subject = $1;
my $subject = $initial_subject;
$_ = "Subject: " .
- ($subject =~ /[^[:ascii:]]/ ?
- quote_rfc2047($subject) :
- $subject) .
+ quote_subject($subject, $compose_encoding) .
"\n";
} elsif (/^In-Reply-To:\s*(.+)\s*$/i) {
$initial_reply_to = $1;
@@ -662,6 +686,7 @@ sub ask {
my ($prompt, %arg) = @_;
my $valid_re = $arg{valid_re};
my $default = $arg{default};
+ my $confirm_only = $arg{confirm_only};
my $resp;
my $i = 0;
return defined $default ? $default : undef
@@ -679,6 +704,12 @@ sub ask {
if (!defined $valid_re or $resp =~ /$valid_re/) {
return $resp;
}
+ if ($confirm_only) {
+ my $yesno = $term->readline("Are you sure you want to use <$resp> [y/N]? ");
+ if (defined $yesno && $yesno =~ /y/i) {
+ return $resp;
+ }
+ }
}
return undef;
}
@@ -722,17 +753,15 @@ if (!$force) {
}
}
-my $prompting = 0;
if (!defined $sender) {
$sender = $repoauthor || $repocommitter || '';
- $sender = ask("Who should the emails appear to be from? [$sender] ",
- default => $sender);
- print "Emails will be sent from: ", $sender, "\n";
- $prompting++;
}
+my $prompting = 0;
if (!@initial_to && !defined $to_cmd) {
- my $to = ask("Who should the emails be sent to? ");
+ my $to = ask("Who should the emails be sent to (if any)? ",
+ default => "",
+ valid_re => qr/\@.*\./, confirm_only => 1);
push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
$prompting++;
}
@@ -752,13 +781,17 @@ sub expand_one_alias {
}
@initial_to = expand_aliases(@initial_to);
-@initial_to = (map { sanitize_address($_) } @initial_to);
+@initial_to = validate_address_list(sanitize_address_list(@initial_to));
@initial_cc = expand_aliases(@initial_cc);
+@initial_cc = validate_address_list(sanitize_address_list(@initial_cc));
@bcclist = expand_aliases(@bcclist);
+@bcclist = validate_address_list(sanitize_address_list(@bcclist));
if ($thread && !defined $initial_reply_to && $prompting) {
$initial_reply_to = ask(
- "Message-ID to be used as In-Reply-To for the first email? ");
+ "Message-ID to be used as In-Reply-To for the first email (if any)? ",
+ default => "",
+ valid_re => qr/\@.*\./, confirm_only => 1);
}
if (defined $initial_reply_to) {
$initial_reply_to =~ s/^\s*<?//;
@@ -795,12 +828,45 @@ sub extract_valid_address {
$address =~ s/^\s*<(.*)>\s*$/$1/;
if ($have_email_valid) {
return scalar Email::Valid->address($address);
- } else {
- # less robust/correct than the monster regexp in Email::Valid,
- # but still does a 99% job, and one less dependency
- $address =~ /($local_part_regexp\@$domain_regexp)/;
- return $1;
}
+
+ # less robust/correct than the monster regexp in Email::Valid,
+ # but still does a 99% job, and one less dependency
+ return $1 if $address =~ /($local_part_regexp\@$domain_regexp)/;
+ return undef;
+}
+
+sub extract_valid_address_or_die {
+ my $address = shift;
+ $address = extract_valid_address($address);
+ die "error: unable to extract a valid address from: $address\n"
+ if !$address;
+ return $address;
+}
+
+sub validate_address {
+ my $address = shift;
+ while (!extract_valid_address($address)) {
+ print STDERR "error: unable to extract a valid address from: $address\n";
+ $_ = ask("What to do with this address? ([q]uit|[d]rop|[e]dit): ",
+ valid_re => qr/^(?:quit|q|drop|d|edit|e)/i,
+ default => 'q');
+ if (/^d/i) {
+ return undef;
+ } elsif (/^q/i) {
+ cleanup_compose_files();
+ exit(0);
+ }
+ $address = ask("Who should the email be sent to (if any)? ",
+ default => "",
+ valid_re => qr/\@.*\./, confirm_only => 1);
+ }
+ return $address;
+}
+
+sub validate_address_list {
+ return (grep { defined $_ }
+ map { validate_address($_) } @_);
}
# Usually don't need to change anything below here.
@@ -843,11 +909,13 @@ $time = time - scalar $#files;
sub unquote_rfc2047 {
local ($_) = @_;
my $encoding;
- if (s/=\?([^?]+)\?q\?(.*)\?=/$2/g) {
+ s{=\?([^?]+)\?q\?(.*?)\?=}{
$encoding = $1;
- s/_/ /g;
- s/=([0-9A-F]{2})/chr(hex($1))/eg;
- }
+ my $e = $2;
+ $e =~ s/_/ /g;
+ $e =~ s/=([0-9A-F]{2})/chr(hex($1))/eg;
+ $e;
+ }eg;
return wantarray ? ($_, $encoding) : $_;
}
@@ -867,9 +935,29 @@ sub is_rfc2047_quoted {
$s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o;
}
+sub subject_needs_rfc2047_quoting {
+ my $s = shift;
+
+ return ($s =~ /[^[:ascii:]]/) || ($s =~ /=\?/);
+}
+
+sub quote_subject {
+ local $subject = shift;
+ my $encoding = shift || 'UTF-8';
+
+ if (subject_needs_rfc2047_quoting($subject)) {
+ return quote_rfc2047($subject, $encoding);
+ }
+ return $subject;
+}
+
# use the simplest quoting being able to handle the recipient
sub sanitize_address {
my ($recipient) = @_;
+
+ # remove garbage after email address
+ $recipient =~ s/(.*>).*$/$1/;
+
my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
if (not $recipient_name) {
@@ -897,6 +985,10 @@ sub sanitize_address {
}
+sub sanitize_address_list {
+ return (map { sanitize_address($_) } @_);
+}
+
# Returns the local Fully Qualified Domain Name (FQDN) if available.
#
# Tightly configured MTAa require that a caller sends a real DNS
@@ -959,14 +1051,13 @@ sub maildomain {
sub send_message {
my @recipients = unique_email_list(@to);
- @cc = (grep { my $cc = extract_valid_address($_);
+ @cc = (grep { my $cc = extract_valid_address_or_die($_);
not grep { $cc eq $_ || $_ =~ /<\Q${cc}\E>$/ } @recipients
}
- map { sanitize_address($_) }
@cc);
my $to = join (",\n\t", @recipients);
@recipients = unique_email_list(@recipients,@cc,@bcclist);
- @recipients = (map { extract_valid_address($_) } @recipients);
+ @recipients = (map { extract_valid_address_or_die($_) } @recipients);
my $date = format_2822_time($time++);
my $gitversion = '@@GIT_VERSION@@';
if ($gitversion =~ m/..GIT_VERSION../) {
@@ -1079,7 +1170,7 @@ X-Mailer: git-send-email $gitversion
$smtp_encryption = '';
# Send EHLO again to receive fresh
# supported commands
- $smtp->hello();
+ $smtp->hello($smtp_domain);
} else {
die "Server does not support STARTTLS! ".$smtp->message;
}
@@ -1095,6 +1186,12 @@ X-Mailer: git-send-email $gitversion
}
if (defined $smtp_authuser) {
+ # Workaround AUTH PLAIN/LOGIN interaction defect
+ # with Authen::SASL::Cyrus
+ eval {
+ require Authen::SASL;
+ Authen::SASL->import(qw(Perl));
+ };
if (!defined $smtp_authpass) {
@@ -1203,7 +1300,7 @@ foreach my $t (@files) {
foreach my $addr (parse_address_line($1)) {
printf("(mbox) Adding to: %s from line '%s'\n",
$addr, $_) unless $quiet;
- push @to, sanitize_address($addr);
+ push @to, $addr;
}
}
elsif (/^Cc:\s+(.*)$/) {
@@ -1282,7 +1379,7 @@ foreach my $t (@files) {
}
if ($broken_encoding{$t} && !is_rfc2047_quoted($subject)) {
- $subject = quote_rfc2047($subject, $auto_8bit_encoding);
+ $subject = quote_subject($subject, $auto_8bit_encoding);
}
if (defined $author and $author ne $sender) {
@@ -1312,6 +1409,9 @@ foreach my $t (@files) {
($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
$needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
+ @to = validate_address_list(sanitize_address_list(@to));
+ @cc = validate_address_list(sanitize_address_list(@cc));
+
@to = (@initial_to, @to);
@cc = (@initial_cc, @cc);
@@ -1367,14 +1467,10 @@ sub unique_email_list {
my @emails;
foreach my $entry (@_) {
- if (my $clean = extract_valid_address($entry)) {
- $seen{$clean} ||= 0;
- next if $seen{$clean}++;
- push @emails, $entry;
- } else {
- print STDERR "W: unable to extract a valid address",
- " from: $entry\n";
- }
+ my $clean = extract_valid_address_or_die($entry);
+ $seen{$clean} ||= 0;
+ next if $seen{$clean}++;
+ push @emails, $entry;
}
return @emails;
}