mailinfo: tests for RFC2047 examples
[gitweb.git] / git-send-email.perl
index b50239636dab4a5f64f4c3d4f8321fc954b3f1a1..449d938ba97cf1db47049a2f514bea1ed220044b 100755 (executable)
@@ -20,6 +20,7 @@
 use warnings;
 use Term::ReadLine;
 use Getopt::Long;
+use Text::ParseWords;
 use Data::Dumper;
 use Term::ANSIColor;
 use Git;
@@ -38,7 +39,7 @@ package main;
 
 sub usage {
        print <<EOT;
-git-send-email [options] <file | directory>...
+git send-email [options] <file | directory>...
 Options:
    --from         Specify the "From:" line of the email to be sent.
 
@@ -84,7 +85,10 @@ sub usage {
 
    --smtp-pass    The password for SMTP-AUTH.
 
-   --smtp-ssl     If set, connects to the SMTP server using SSL.
+   --smtp-encryption Specify 'tls' for STARTTLS encryption, or 'ssl' for SSL.
+                  Any other value disables the feature.
+
+   --smtp-ssl     Synonym for '--smtp-encryption=ssl'.  Deprecated.
 
    --suppress-cc  Suppress the specified category of auto-CC.  The category
                  can be one of 'author' for the patch author, 'self' to
@@ -184,7 +188,7 @@ sub format_2822_time {
 
 # Variables with corresponding config settings
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
-my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_ssl);
+my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
 my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
 my ($no_validate);
 my (@suppress_cc);
@@ -194,7 +198,6 @@ sub format_2822_time {
     "chainreplyto" => [\$chain_reply_to, 1],
     "suppressfrom" => [\$suppress_from, undef],
     "signedoffcc" => [\$signed_off_cc, undef],
-    "smtpssl" => [\$smtp_ssl, 0],
 );
 
 my %config_settings = (
@@ -203,11 +206,13 @@ sub format_2822_time {
     "smtpuser" => \$smtp_authuser,
     "smtppass" => \$smtp_authpass,
     "to" => \@to,
+    "cc" => \@initial_cc,
     "cccmd" => \$cc_cmd,
     "aliasfiletype" => \$aliasfiletype,
     "bcc" => \@bcclist,
     "aliasesfile" => \@alias_files,
     "suppresscc" => \@suppress_cc,
+    "envelopesender" => \$envelope_sender,
 );
 
 # Handle Uncouth Termination
@@ -247,7 +252,8 @@ sub signal_handler {
                    "smtp-server-port=s" => \$smtp_server_port,
                    "smtp-user=s" => \$smtp_authuser,
                    "smtp-pass:s" => \$smtp_authpass,
-                   "smtp-ssl!" => \$smtp_ssl,
+                   "smtp-ssl" => sub { $smtp_encryption = 'ssl' },
+                   "smtp-encryption=s" => \$smtp_encryption,
                    "identity=s" => \$identity,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
@@ -287,6 +293,15 @@ sub read_config {
                        $$target = Git::config(@repo, "$prefix.$setting") unless (defined $$target);
                }
        }
+
+       if (!defined $smtp_encryption) {
+               my $enc = Git::config(@repo, "$prefix.smtpencryption");
+               if (defined $enc) {
+                       $smtp_encryption = $enc;
+               } elsif (Git::config_bool(@repo, "$prefix.smtpssl")) {
+                       $smtp_encryption = 'ssl';
+               }
+       }
 }
 
 # read configuration from [sendemail "$identity"], fall back on [sendemail]
@@ -299,6 +314,9 @@ sub read_config {
        ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
 }
 
+# 'default' encryption is none -- this only prevents a warning
+$smtp_encryption = '' unless (defined $smtp_encryption);
+
 # Set CC suppressions
 my(%suppress_cc);
 if (@suppress_cc) {
@@ -346,6 +364,10 @@ sub read_config {
        die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
 }
 
+sub split_addrs {
+       return parse_line('\s*,\s*', 1, @_);
+}
+
 my %aliases;
 my %parse_alias = (
        # multiline formats can be supported in the future
@@ -354,7 +376,7 @@ sub read_config {
                        my ($alias, $addr) = ($1, $2);
                        $addr =~ s/#.*$//; # mutt allows # comments
                         # commas delimit multiple addresses
-                       $aliases{$alias} = [ split(/\s*,\s*/, $addr) ];
+                       $aliases{$alias} = [ split_addrs($addr) ];
                }}},
        mailrc => sub { my $fh = shift; while (<$fh>) {
                if (/^alias\s+(\S+)\s+(.*)$/) {
@@ -363,7 +385,7 @@ sub read_config {
                }}},
        pine => sub { my $fh = shift; while (<$fh>) {
                if (/^(\S+)\t.*\t(.*)$/) {
-                       $aliases{$1} = [ split(/\s*,\s*/, $2) ];
+                       $aliases{$1} = [ split_addrs($2) ];
                }}},
        gnus => sub { my $fh = shift; while (<$fh>) {
                if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
@@ -390,10 +412,9 @@ sub read_config {
 
                push @files, grep { -f $_ } map { +$f . "/" . $_ }
                                sort readdir(DH);
-
-       } elsif (-f $f) {
+               closedir(DH);
+       } elsif (-f $f or -p $f) {
                push @files, $f;
-
        } else {
                print STDERR "Skipping $f - not found.\n";
        }
@@ -401,8 +422,10 @@ sub read_config {
 
 if (!$no_validate) {
        foreach my $f (@files) {
-               my $error = validate_patch($f);
-               $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+               unless (-p $f) {
+                       my $error = validate_patch($f);
+                       $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+               }
        }
 }
 
@@ -440,7 +463,7 @@ sub read_config {
        }
 
        my $to = $_;
-       push @to, split /,/, $to;
+       push @to, split_addrs($to);
        $prompting++;
 }
 
@@ -520,8 +543,30 @@ sub expand_aliases {
        open(C,"<",$compose_filename)
                or die "Failed to open $compose_filename : " . $!;
 
+       my $need_8bit_cte = file_has_nonascii($compose_filename);
+       my $in_body = 0;
        while(<C>) {
                next if m/^GIT: /;
+               if (!$in_body && /^\n$/) {
+                       $in_body = 1;
+                       if ($need_8bit_cte) {
+                               print C2 "MIME-Version: 1.0\n",
+                                        "Content-Type: text/plain; ",
+                                          "charset=utf-8\n",
+                                        "Content-Transfer-Encoding: 8bit\n";
+                       }
+               }
+               if (!$in_body && /^MIME-Version:/i) {
+                       $need_8bit_cte = 0;
+               }
+               if (!$in_body && /^Subject: ?(.*)/i) {
+                       my $subject = $1;
+                       $_ = "Subject: " .
+                               ($subject =~ /[^[:ascii:]]/ ?
+                                quote_rfc2047($subject) :
+                                $subject) .
+                               "\n";
+               }
                print C2 $_;
        }
        close(C);
@@ -612,6 +657,14 @@ sub unquote_rfc2047 {
        return wantarray ? ($_, $encoding) : $_;
 }
 
+sub quote_rfc2047 {
+       local $_ = shift;
+       my $encoding = shift || 'utf-8';
+       s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
+       s/(.*)/=\?$encoding\?q\?$1\?=/;
+       return $_;
+}
+
 # use the simplest quoting being able to handle the recipient
 sub sanitize_address
 {
@@ -629,13 +682,12 @@ sub sanitize_address
 
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
-               $recipient_name =~ s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
-               $recipient_name =~ s/(.*)/=\?utf-8\?q\?$1\?=/;
+               $recipient_name = quote_rfc2047($recipient_name);
        }
 
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
-               $recipient_name =~ s/(["\\\r])/\\$1/;
+               $recipient_name =~ s/(["\\\r])/\\$1/g;
                $recipient_name = "\"$recipient_name\"";
        }
 
@@ -707,7 +759,7 @@ sub send_message
                        die "The required SMTP server is not properly defined."
                }
 
-               if ($smtp_ssl) {
+               if ($smtp_encryption eq 'ssl') {
                        $smtp_server_port ||= 465; # ssmtp
                        require Net::SMTP::SSL;
                        $smtp ||= Net::SMTP::SSL->new($smtp_server, Port => $smtp_server_port);
@@ -717,6 +769,21 @@ sub send_message
                        $smtp ||= Net::SMTP->new((defined $smtp_server_port)
                                                 ? "$smtp_server:$smtp_server_port"
                                                 : $smtp_server);
+                       if ($smtp_encryption eq 'tls') {
+                               require Net::SMTP::SSL;
+                               $smtp->command('STARTTLS');
+                               $smtp->response();
+                               if ($smtp->code == 220) {
+                                       $smtp = Net::SMTP::SSL->start_SSL($smtp)
+                                               or die "STARTTLS failed! ".$smtp->message;
+                                       $smtp_encryption = '';
+                                       # Send EHLO again to receive fresh
+                                       # supported commands
+                                       $smtp->hello();
+                               } else {
+                                       die "Server does not support STARTTLS! ".$smtp->message;
+                               }
+                       }
                }
 
                if (!$smtp) {
@@ -819,7 +886,7 @@ sub send_message
                                }
                                elsif (/^Content-type:/i) {
                                        $has_content_type = 1;
-                                       if (/charset="?[^ "]+/) {
+                                       if (/charset="?([^ "]+)/) {
                                                $body_encoding = $1;
                                        }
                                        push @xh, $_;
@@ -958,3 +1025,13 @@ sub validate_patch {
        }
        return undef;
 }
+
+sub file_has_nonascii {
+       my $fn = shift;
+       open(my $fh, '<', $fn)
+               or die "unable to open $fn: $!\n";
+       while (my $line = <$fh>) {
+               return 1 if $line =~ /[^[:ascii:]]/;
+       }
+       return 0;
+}