1#!/usr/bin/perl 2# 3# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com> 4# Copyright 2005 Ryan Anderson <ryan@michonline.com> 5# 6# GPL v2 (See COPYING) 7# 8# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com> 9# 10# Sends a collection of emails to the given email addresses, disturbingly fast. 11# 12# Supports two formats: 13# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches) 14# 2. The original format support by Greg's script: 15# first line of the message is who to CC, 16# and second line is the subject of the message. 17# 18 19use5.008; 20use strict; 21use warnings; 22use POSIX qw/strftime/; 23use Term::ReadLine; 24use Getopt::Long; 25use Text::ParseWords; 26use Term::ANSIColor; 27use File::Temp qw/ tempdir tempfile /; 28use File::Spec::Functions qw(catdir catfile); 29use Git::LoadCPAN::Error qw(:try); 30use Cwd qw(abs_path cwd); 31use Git; 32use Git::I18N; 33use Net::Domain (); 34use Net::SMTP (); 35use Git::LoadCPAN::Mail::Address; 36 37Getopt::Long::Configure qw/ pass_through /; 38 39package FakeTerm; 40sub new { 41my($class,$reason) =@_; 42returnbless \$reason,shift; 43} 44subreadline{ 45my$self=shift; 46die"Cannot use readline on FakeTerm:$$self"; 47} 48package main; 49 50 51sub usage { 52print<<EOT; 53git send-email [options] <file | directory | rev-list options > 54git send-email --dump-aliases 55 56 Composing: 57 --from <str> * Email From: 58 --[no-]to <str> * Email To: 59 --[no-]cc <str> * Email Cc: 60 --[no-]bcc <str> * Email Bcc: 61 --subject <str> * Email "Subject:" 62 --reply-to <str> * Email "Reply-To:" 63 --in-reply-to <str> * Email "In-Reply-To:" 64 --[no-]xmailer * Add "X-Mailer:" header (default). 65 --[no-]annotate * Review each patch that will be sent in an editor. 66 --compose * Open an editor for introduction. 67 --compose-encoding <str> * Encoding to assume for introduction. 68 --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared 69 --transfer-encoding <str> * Transfer encoding to use (quoted-printable, 8bit, base64) 70 71 Sending: 72 --envelope-sender <str> * Email envelope sender. 73 --smtp-server <str:int> * Outgoing SMTP server to use. The port 74 is optional. Default 'localhost'. 75 --smtp-server-option <str> * Outgoing SMTP server option to use. 76 --smtp-server-port <int> * Outgoing SMTP server port. 77 --smtp-user <str> * Username for SMTP-AUTH. 78 --smtp-pass <str> * Password for SMTP-AUTH; not necessary. 79 --smtp-encryption <str> * tls or ssl; anything else disables. 80 --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. 81 --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). 82 Pass an empty string to disable certificate 83 verification. 84 --smtp-domain <str> * The domain name sent to HELO/EHLO handshake 85 --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or 86 "none" to disable authentication. 87 This setting forces to use one of the listed mechanisms. 88 --no-smtp-auth Disable SMTP authentication. Shorthand for 89 `--smtp-auth=none` 90 --smtp-debug <0|1> * Disable, enable Net::SMTP debug. 91 92 --batch-size <int> * send max <int> message per connection. 93 --relogin-delay <int> * delay <int> seconds between two successive login. 94 This option can only be used with --batch-size 95 96 Automating: 97 --identity <str> * Use the sendemail.<id> options. 98 --to-cmd <str> * Email To: via `<str> \$patch_path` 99 --cc-cmd <str> * Email Cc: via `<str> \$patch_path` 100 --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, misc-by, all. 101 --[no-]cc-cover * Email Cc: addresses in the cover letter. 102 --[no-]to-cover * Email To: addresses in the cover letter. 103 --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. 104 --[no-]suppress-from * Send to self. Default off. 105 --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off. 106 --[no-]thread * Use In-Reply-To: field. Default on. 107 108 Administering: 109 --confirm <str> * Confirm recipients before sending; 110 auto, cc, compose, always, or never. 111 --quiet * Output one line of info per email. 112 --dry-run * Don't actually send the emails. 113 --[no-]validate * Perform patch sanity checks. Default on. 114 --[no-]format-patch * understand any non optional arguments as 115 `git format-patch` ones. 116 --force * Send even if safety checks would prevent it. 117 118 Information: 119 --dump-aliases * Dump configured aliases and exit. 120 121EOT 122exit(1); 123} 124 125sub completion_helper { 126print Git::command('format-patch','--git-completion-helper'); 127exit(0); 128} 129 130# most mail servers generate the Date: header, but not all... 131sub format_2822_time { 132my($time) =@_; 133my@localtm=localtime($time); 134my@gmttm=gmtime($time); 135my$localmin=$localtm[1] +$localtm[2] *60; 136my$gmtmin=$gmttm[1] +$gmttm[2] *60; 137if($localtm[0] !=$gmttm[0]) { 138die __("local zone differs from GMT by a non-minute interval\n"); 139} 140if((($gmttm[6] +1) %7) ==$localtm[6]) { 141$localmin+=1440; 142}elsif((($gmttm[6] -1) %7) ==$localtm[6]) { 143$localmin-=1440; 144}elsif($gmttm[6] !=$localtm[6]) { 145die __("local time offset greater than or equal to 24 hours\n"); 146} 147my$offset=$localmin-$gmtmin; 148my$offhour=$offset/60; 149my$offmin=abs($offset%60); 150if(abs($offhour) >=24) { 151die __("local time offset greater than or equal to 24 hours\n"); 152} 153 154returnsprintf("%s,%2d%s%d%02d:%02d:%02d%s%02d%02d", 155qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]], 156$localtm[3], 157qw(Jan Feb Mar Apr May Jun 158 Jul Aug Sep Oct Nov Dec)[$localtm[4]], 159$localtm[5]+1900, 160$localtm[2], 161$localtm[1], 162$localtm[0], 163($offset>=0) ?'+':'-', 164abs($offhour), 165$offmin, 166); 167} 168 169my$have_email_valid=eval{require Email::Valid;1}; 170my$smtp; 171my$auth; 172my$num_sent=0; 173 174# Regexes for RFC 2047 productions. 175my$re_token=qr/[^][()<>@,;:\\"\/?.=\000-\037\177-\377]+/; 176my$re_encoded_text=qr/[^? \000-\037\177-\377]+/; 177my$re_encoded_word=qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/; 178 179# Variables we fill in automatically, or via prompting: 180my(@to,@cc,@xh,$envelope_sender, 181$initial_in_reply_to,$reply_to,$initial_subject,@files, 182$author,$sender,$smtp_authpass,$annotate,$compose,$time); 183# Things we either get from config, *or* are overridden on the 184# command-line. 185my($no_cc,$no_to,$no_bcc,$no_identity); 186my(@config_to,@getopt_to); 187my(@config_cc,@getopt_cc); 188my(@config_bcc,@getopt_bcc); 189 190# Example reply to: 191#$initial_in_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; 192 193my$repo=eval{ Git->repository() }; 194my@repo=$repo? ($repo) : (); 195my$term=eval{ 196$ENV{"GIT_SEND_EMAIL_NOTTY"} 197? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT 198: new Term::ReadLine 'git-send-email'; 199}; 200if($@) { 201$term= new FakeTerm "$@: going non-interactive"; 202} 203 204# Behavior modification variables 205my($quiet,$dry_run) = (0,0); 206my$format_patch; 207my$compose_filename; 208my$force=0; 209my$dump_aliases=0; 210 211# Handle interactive edition of files. 212my$multiedit; 213my$editor; 214 215sub do_edit { 216if(!defined($editor)) { 217$editor= Git::command_oneline('var','GIT_EDITOR'); 218} 219if(defined($multiedit) && !$multiedit) { 220map{ 221system('sh','-c',$editor.' "$@"',$editor,$_); 222if(($?&127) || ($?>>8)) { 223die(__("the editor exited uncleanly, aborting everything")); 224} 225}@_; 226}else{ 227system('sh','-c',$editor.' "$@"',$editor,@_); 228if(($?&127) || ($?>>8)) { 229die(__("the editor exited uncleanly, aborting everything")); 230} 231} 232} 233 234# Variables with corresponding config settings 235my($suppress_from,$signed_off_by_cc); 236my($cover_cc,$cover_to); 237my($to_cmd,$cc_cmd); 238my($smtp_server,$smtp_server_port,@smtp_server_options); 239my($smtp_authuser,$smtp_encryption,$smtp_ssl_cert_path); 240my($batch_size,$relogin_delay); 241my($identity,$aliasfiletype,@alias_files,$smtp_domain,$smtp_auth); 242my($confirm); 243my(@suppress_cc); 244my($auto_8bit_encoding); 245my($compose_encoding); 246# Variables with corresponding config settings & hardcoded defaults 247my($debug_net_smtp) =0;# Net::SMTP, see send_message() 248my$thread=1; 249my$chain_reply_to=0; 250my$use_xmailer=1; 251my$validate=1; 252my$target_xfer_encoding='auto'; 253 254my%config_bool_settings= ( 255"thread"=> \$thread, 256"chainreplyto"=> \$chain_reply_to, 257"suppressfrom"=> \$suppress_from, 258"signedoffbycc"=> \$signed_off_by_cc, 259"cccover"=> \$cover_cc, 260"tocover"=> \$cover_to, 261"signedoffcc"=> \$signed_off_by_cc, 262"validate"=> \$validate, 263"multiedit"=> \$multiedit, 264"annotate"=> \$annotate, 265"xmailer"=> \$use_xmailer, 266); 267 268my%config_settings= ( 269"smtpserver"=> \$smtp_server, 270"smtpserverport"=> \$smtp_server_port, 271"smtpserveroption"=> \@smtp_server_options, 272"smtpuser"=> \$smtp_authuser, 273"smtppass"=> \$smtp_authpass, 274"smtpdomain"=> \$smtp_domain, 275"smtpauth"=> \$smtp_auth, 276"smtpbatchsize"=> \$batch_size, 277"smtprelogindelay"=> \$relogin_delay, 278"to"=> \@config_to, 279"tocmd"=> \$to_cmd, 280"cc"=> \@config_cc, 281"cccmd"=> \$cc_cmd, 282"aliasfiletype"=> \$aliasfiletype, 283"bcc"=> \@config_bcc, 284"suppresscc"=> \@suppress_cc, 285"envelopesender"=> \$envelope_sender, 286"confirm"=> \$confirm, 287"from"=> \$sender, 288"assume8bitencoding"=> \$auto_8bit_encoding, 289"composeencoding"=> \$compose_encoding, 290"transferencoding"=> \$target_xfer_encoding, 291); 292 293my%config_path_settings= ( 294"aliasesfile"=> \@alias_files, 295"smtpsslcertpath"=> \$smtp_ssl_cert_path, 296); 297 298# Handle Uncouth Termination 299sub signal_handler { 300 301# Make text normal 302print color("reset"),"\n"; 303 304# SMTP password masked 305system"stty echo"; 306 307# tmp files from --compose 308if(defined$compose_filename) { 309if(-e $compose_filename) { 310printf __("'%s' contains an intermediate version ". 311"of the email you were composing.\n"), 312$compose_filename; 313} 314if(-e ($compose_filename.".final")) { 315printf __("'%s.final' contains the composed email.\n"), 316$compose_filename; 317} 318} 319 320exit; 321}; 322 323$SIG{TERM} = \&signal_handler; 324$SIG{INT} = \&signal_handler; 325 326# Read our sendemail.* config 327sub read_config { 328my($configured,$prefix) =@_; 329 330foreachmy$setting(keys%config_bool_settings) { 331my$target=$config_bool_settings{$setting}; 332my$v= Git::config_bool(@repo,"$prefix.$setting"); 333next unlessdefined$v; 334next if$configured->{$setting}++; 335$$target=$v; 336} 337 338foreachmy$setting(keys%config_path_settings) { 339my$target=$config_path_settings{$setting}; 340if(ref($target)eq"ARRAY") { 341my@values= Git::config_path(@repo,"$prefix.$setting"); 342next unless@values; 343next if$configured->{$setting}++; 344@$target=@values; 345} 346else{ 347my$v= Git::config_path(@repo,"$prefix.$setting"); 348next unlessdefined$v; 349next if$configured->{$setting}++; 350$$target=$v; 351} 352} 353 354foreachmy$setting(keys%config_settings) { 355my$target=$config_settings{$setting}; 356if(ref($target)eq"ARRAY") { 357my@values= Git::config(@repo,"$prefix.$setting"); 358next unless@values; 359next if$configured->{$setting}++; 360@$target=@values; 361} 362else{ 363my$v= Git::config(@repo,"$prefix.$setting"); 364next unlessdefined$v; 365next if$configured->{$setting}++; 366$$target=$v; 367} 368} 369 370if(!defined$smtp_encryption) { 371my$setting="$prefix.smtpencryption"; 372my$enc= Git::config(@repo,$setting); 373return unlessdefined$enc; 374return if$configured->{$setting}++; 375if(defined$enc) { 376$smtp_encryption=$enc; 377}elsif(Git::config_bool(@repo,"$prefix.smtpssl")) { 378$smtp_encryption='ssl'; 379} 380} 381} 382 383# sendemail.identity yields to --identity. We must parse this 384# special-case first before the rest of the config is read. 385$identity= Git::config(@repo,"sendemail.identity"); 386my$rc= GetOptions( 387"identity=s"=> \$identity, 388"no-identity"=> \$no_identity, 389); 390usage()unless$rc; 391undef$identityif$no_identity; 392 393# Now we know enough to read the config 394{ 395my%configured; 396 read_config(\%configured,"sendemail.$identity")ifdefined$identity; 397 read_config(\%configured,"sendemail"); 398} 399 400# Begin by accumulating all the variables (defined above), that we will end up 401# needing, first, from the command line: 402 403my$help; 404my$git_completion_helper; 405$rc= GetOptions("h"=> \$help, 406"dump-aliases"=> \$dump_aliases); 407usage()unless$rc; 408die __("--dump-aliases incompatible with other options\n") 409if!$helpand$dump_aliasesand@ARGV; 410$rc= GetOptions( 411"sender|from=s"=> \$sender, 412"in-reply-to=s"=> \$initial_in_reply_to, 413"reply-to=s"=> \$reply_to, 414"subject=s"=> \$initial_subject, 415"to=s"=> \@getopt_to, 416"to-cmd=s"=> \$to_cmd, 417"no-to"=> \$no_to, 418"cc=s"=> \@getopt_cc, 419"no-cc"=> \$no_cc, 420"bcc=s"=> \@getopt_bcc, 421"no-bcc"=> \$no_bcc, 422"chain-reply-to!"=> \$chain_reply_to, 423"no-chain-reply-to"=>sub{$chain_reply_to=0}, 424"smtp-server=s"=> \$smtp_server, 425"smtp-server-option=s"=> \@smtp_server_options, 426"smtp-server-port=s"=> \$smtp_server_port, 427"smtp-user=s"=> \$smtp_authuser, 428"smtp-pass:s"=> \$smtp_authpass, 429"smtp-ssl"=>sub{$smtp_encryption='ssl'}, 430"smtp-encryption=s"=> \$smtp_encryption, 431"smtp-ssl-cert-path=s"=> \$smtp_ssl_cert_path, 432"smtp-debug:i"=> \$debug_net_smtp, 433"smtp-domain:s"=> \$smtp_domain, 434"smtp-auth=s"=> \$smtp_auth, 435"no-smtp-auth"=>sub{$smtp_auth='none'}, 436"annotate!"=> \$annotate, 437"no-annotate"=>sub{$annotate=0}, 438"compose"=> \$compose, 439"quiet"=> \$quiet, 440"cc-cmd=s"=> \$cc_cmd, 441"suppress-from!"=> \$suppress_from, 442"no-suppress-from"=>sub{$suppress_from=0}, 443"suppress-cc=s"=> \@suppress_cc, 444"signed-off-cc|signed-off-by-cc!"=> \$signed_off_by_cc, 445"no-signed-off-cc|no-signed-off-by-cc"=>sub{$signed_off_by_cc=0}, 446"cc-cover|cc-cover!"=> \$cover_cc, 447"no-cc-cover"=>sub{$cover_cc=0}, 448"to-cover|to-cover!"=> \$cover_to, 449"no-to-cover"=>sub{$cover_to=0}, 450"confirm=s"=> \$confirm, 451"dry-run"=> \$dry_run, 452"envelope-sender=s"=> \$envelope_sender, 453"thread!"=> \$thread, 454"no-thread"=>sub{$thread=0}, 455"validate!"=> \$validate, 456"no-validate"=>sub{$validate=0}, 457"transfer-encoding=s"=> \$target_xfer_encoding, 458"format-patch!"=> \$format_patch, 459"no-format-patch"=>sub{$format_patch=0}, 460"8bit-encoding=s"=> \$auto_8bit_encoding, 461"compose-encoding=s"=> \$compose_encoding, 462"force"=> \$force, 463"xmailer!"=> \$use_xmailer, 464"no-xmailer"=>sub{$use_xmailer=0}, 465"batch-size=i"=> \$batch_size, 466"relogin-delay=i"=> \$relogin_delay, 467"git-completion-helper"=> \$git_completion_helper, 468); 469 470# Munge any "either config or getopt, not both" variables 471my@initial_to=@getopt_to?@getopt_to: ($no_to? () :@config_to); 472my@initial_cc=@getopt_cc?@getopt_cc: ($no_cc? () :@config_cc); 473my@initial_bcc=@getopt_bcc?@getopt_bcc: ($no_bcc? () :@config_bcc); 474 475usage()if$help; 476completion_helper()if$git_completion_helper; 477unless($rc) { 478 usage(); 479} 480 481die __("Cannot run git format-patch from outside a repository\n") 482if$format_patchand not$repo; 483 484die __("`batch-size` and `relogin` must be specified together ". 485"(via command-line or configuration option)\n") 486ifdefined$relogin_delayand not defined$batch_size; 487 488# 'default' encryption is none -- this only prevents a warning 489$smtp_encryption=''unless(defined$smtp_encryption); 490 491# Set CC suppressions 492my(%suppress_cc); 493if(@suppress_cc) { 494foreachmy$entry(@suppress_cc) { 495# Please update $__git_send_email_suppresscc_options 496# in git-completion.bash when you add new options. 497die sprintf(__("Unknown --suppress-cc field: '%s'\n"),$entry) 498unless$entry=~/^(?:all|cccmd|cc|author|self|sob|body|bodycc|misc-by)$/; 499$suppress_cc{$entry} =1; 500} 501} 502 503if($suppress_cc{'all'}) { 504foreachmy$entry(qw (cccmd cc author self sob body bodycc misc-by)) { 505$suppress_cc{$entry} =1; 506} 507delete$suppress_cc{'all'}; 508} 509 510# If explicit old-style ones are specified, they trump --suppress-cc. 511$suppress_cc{'self'} =$suppress_fromifdefined$suppress_from; 512$suppress_cc{'sob'} = !$signed_off_by_ccifdefined$signed_off_by_cc; 513 514if($suppress_cc{'body'}) { 515foreachmy$entry(qw (sob bodycc misc-by)) { 516$suppress_cc{$entry} =1; 517} 518delete$suppress_cc{'body'}; 519} 520 521# Set confirm's default value 522my$confirm_unconfigured= !defined$confirm; 523if($confirm_unconfigured) { 524$confirm=scalar%suppress_cc?'compose':'auto'; 525}; 526# Please update $__git_send_email_confirm_options in 527# git-completion.bash when you add new options. 528die sprintf(__("Unknown --confirm setting: '%s'\n"),$confirm) 529unless$confirm=~/^(?:auto|cc|compose|always|never)/; 530 531# Debugging, print out the suppressions. 532if(0) { 533print"suppressions:\n"; 534foreachmy$entry(keys%suppress_cc) { 535printf" %-5s ->$suppress_cc{$entry}\n",$entry; 536} 537} 538 539my($repoauthor,$repocommitter); 540($repoauthor) = Git::ident_person(@repo,'author'); 541($repocommitter) = Git::ident_person(@repo,'committer'); 542 543sub parse_address_line { 544returnmap{$_->format} Mail::Address->parse($_[0]); 545} 546 547sub split_addrs { 548return quotewords('\s*,\s*',1,@_); 549} 550 551my%aliases; 552 553sub parse_sendmail_alias { 554local$_=shift; 555if(/"/) { 556printf STDERR __("warning: sendmail alias with quotes is not supported:%s\n"),$_; 557}elsif(/:include:/) { 558printf STDERR __("warning: `:include:` not supported:%s\n"),$_; 559}elsif(/[\/|]/) { 560printf STDERR __("warning: `/file` or `|pipe` redirection not supported:%s\n"),$_; 561}elsif(/^(\S+?)\s*:\s*(.+)$/) { 562my($alias,$addr) = ($1,$2); 563$aliases{$alias} = [ split_addrs($addr) ]; 564}else{ 565printf STDERR __("warning: sendmail line is not recognized:%s\n"),$_; 566} 567} 568 569sub parse_sendmail_aliases { 570my$fh=shift; 571my$s=''; 572while(<$fh>) { 573chomp; 574next if/^\s*$/||/^\s*#/; 575$s.=$_,next if$s=~s/\\$//||s/^\s+//; 576 parse_sendmail_alias($s)if$s; 577$s=$_; 578} 579$s=~s/\\$//;# silently tolerate stray '\' on last line 580 parse_sendmail_alias($s)if$s; 581} 582 583my%parse_alias= ( 584# multiline formats can be supported in the future 585 mutt =>sub{my$fh=shift;while(<$fh>) { 586if(/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) { 587my($alias,$addr) = ($1,$2); 588$addr=~s/#.*$//;# mutt allows # comments 589# commas delimit multiple addresses 590my@addr= split_addrs($addr); 591 592# quotes may be escaped in the file, 593# unescape them so we do not double-escape them later. 594s/\\"/"/gforeach@addr; 595$aliases{$alias} = \@addr 596}}}, 597 mailrc =>sub{my$fh=shift;while(<$fh>) { 598if(/^alias\s+(\S+)\s+(.*?)\s*$/) { 599# spaces delimit multiple addresses 600$aliases{$1} = [ quotewords('\s+',0,$2) ]; 601}}}, 602 pine =>sub{my$fh=shift;my$f='\t[^\t]*'; 603for(my$x='';defined($x);$x=$_) { 604chomp$x; 605$x.=$1while(defined($_= <$fh>) &&/^ +(.*)$/); 606$x=~/^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ornext; 607$aliases{$1} = [ split_addrs($2) ]; 608}}, 609 elm =>sub{my$fh=shift; 610while(<$fh>) { 611if(/^(\S+)\s+=\s+[^=]+=\s(\S+)/) { 612my($alias,$addr) = ($1,$2); 613$aliases{$alias} = [ split_addrs($addr) ]; 614} 615} }, 616 sendmail => \&parse_sendmail_aliases, 617 gnus =>sub{my$fh=shift;while(<$fh>) { 618if(/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) { 619$aliases{$1} = [$2]; 620}}} 621# Please update _git_config() in git-completion.bash when you 622# add new MUAs. 623); 624 625if(@alias_filesand$aliasfiletypeand defined$parse_alias{$aliasfiletype}) { 626foreachmy$file(@alias_files) { 627open my$fh,'<',$fileor die"opening$file:$!\n"; 628$parse_alias{$aliasfiletype}->($fh); 629close$fh; 630} 631} 632 633if($dump_aliases) { 634print"$_\n"for(sort keys%aliases); 635exit(0); 636} 637 638# is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if 639# $f is a revision list specification to be passed to format-patch. 640sub is_format_patch_arg { 641return unless$repo; 642my$f=shift; 643try{ 644$repo->command('rev-parse','--verify','--quiet',$f); 645if(defined($format_patch)) { 646return$format_patch; 647} 648die sprintf(__ <<EOF,$f,$f); 649File '%s' exists but it could also be the range of commits 650to produce patches for. Please disambiguate by... 651 652 * Saying "./%s" if you mean a file; or 653 * Giving --format-patch option if you mean a range. 654EOF 655} catch Git::Error::Command with { 656# Not a valid revision. Treat it as a filename. 657return0; 658} 659} 660 661# Now that all the defaults are set, process the rest of the command line 662# arguments and collect up the files that need to be processed. 663my@rev_list_opts; 664while(defined(my$f=shift@ARGV)) { 665if($feq"--") { 666push@rev_list_opts,"--",@ARGV; 667@ARGV= (); 668}elsif(-d $fand!is_format_patch_arg($f)) { 669opendir my$dh,$f 670or die sprintf(__("Failed to opendir%s:%s"),$f,$!); 671 672push@files,grep{ -f $_}map{ catfile($f,$_) } 673sort readdir$dh; 674closedir$dh; 675}elsif((-f $for-p $f)and!is_format_patch_arg($f)) { 676push@files,$f; 677}else{ 678push@rev_list_opts,$f; 679} 680} 681 682if(@rev_list_opts) { 683die __("Cannot run git format-patch from outside a repository\n") 684unless$repo; 685push@files,$repo->command('format-patch','-o', tempdir(CLEANUP =>1),@rev_list_opts); 686} 687 688@files= handle_backup_files(@files); 689 690if($validate) { 691foreachmy$f(@files) { 692unless(-p $f) { 693my$error= validate_patch($f,$target_xfer_encoding); 694$errorand die sprintf(__("fatal:%s:%s\nwarning: no patches were sent\n"), 695$f,$error); 696} 697} 698} 699 700if(@files) { 701unless($quiet) { 702print$_,"\n"for(@files); 703} 704}else{ 705print STDERR __("\nNo patch files specified!\n\n"); 706 usage(); 707} 708 709sub get_patch_subject { 710my$fn=shift; 711open(my$fh,'<',$fn); 712while(my$line= <$fh>) { 713next unless($line=~/^Subject: (.*)$/); 714close$fh; 715return"GIT:$1\n"; 716} 717close$fh; 718die sprintf(__("No subject line in%s?"),$fn); 719} 720 721if($compose) { 722# Note that this does not need to be secure, but we will make a small 723# effort to have it be unique 724$compose_filename= ($repo? 725 tempfile(".gitsendemail.msg.XXXXXX", DIR =>$repo->repo_path()) : 726 tempfile(".gitsendemail.msg.XXXXXX", DIR =>"."))[1]; 727open my$c,">",$compose_filename 728or die sprintf(__("Failed to open for writing%s:%s"),$compose_filename,$!); 729 730 731my$tpl_sender=$sender||$repoauthor||$repocommitter||''; 732my$tpl_subject=$initial_subject||''; 733my$tpl_in_reply_to=$initial_in_reply_to||''; 734my$tpl_reply_to=$reply_to||''; 735 736print$c<<EOT1, Git::prefix_lines("GIT: ", __ <<EOT2), <<EOT3; 737From$tpl_sender# This line is ignored. 738EOT1 739Lines beginning in "GIT:" will be removed. 740Consider including an overall diffstat or table of contents 741for the patch you are writing. 742 743Clear the body content if you don't wish to send a summary. 744EOT2 745From:$tpl_sender 746Reply-To:$tpl_reply_to 747Subject:$tpl_subject 748In-Reply-To:$tpl_in_reply_to 749 750EOT3 751 for my$f(@files) { 752 print$cget_patch_subject($f); 753 } 754 close$c; 755 756 if ($annotate) { 757 do_edit($compose_filename,@files); 758 } else { 759 do_edit($compose_filename); 760 } 761 762 open$c, "<",$compose_filename 763 or die sprintf(__("Failed to open%s:%s"),$compose_filename,$!); 764 765 if (!defined$compose_encoding) { 766$compose_encoding= "UTF-8"; 767 } 768 769 my%parsed_email; 770 while (my$line= <$c>) { 771 next if$line=~ m/^GIT:/; 772 parse_header_line($line, \%parsed_email); 773 if ($line=~ /^$/) { 774$parsed_email{'body'} = filter_body($c); 775 } 776 } 777 close$c; 778 779 open my$c2, ">",$compose_filename. ".final" 780 or die sprintf(__("Failed to open%s.final:%s"),$compose_filename,$!); 781 782 783 if ($parsed_email{'From'}) { 784$sender= delete($parsed_email{'From'}); 785 } 786 if ($parsed_email{'In-Reply-To'}) { 787$initial_in_reply_to= delete($parsed_email{'In-Reply-To'}); 788 } 789 if ($parsed_email{'Reply-To'}) { 790$reply_to= delete($parsed_email{'Reply-To'}); 791 } 792 if ($parsed_email{'Subject'}) { 793$initial_subject= delete($parsed_email{'Subject'}); 794 print$c2"Subject: " . 795 quote_subject($initial_subject,$compose_encoding) . 796 "\n"; 797 } 798 799 if ($parsed_email{'MIME-Version'}) { 800 print$c2"MIME-Version:$parsed_email{'MIME-Version'}\n", 801 "Content-Type:$parsed_email{'Content-Type'};\n", 802 "Content-Transfer-Encoding:$parsed_email{'Content-Transfer-Encoding'}\n"; 803 delete($parsed_email{'MIME-Version'}); 804 delete($parsed_email{'Content-Type'}); 805 delete($parsed_email{'Content-Transfer-Encoding'}); 806 } elsif (file_has_nonascii($compose_filename)) { 807 my$content_type= (delete($parsed_email{'Content-Type'}) or 808 "text/plain; charset=$compose_encoding"); 809 print$c2"MIME-Version: 1.0\n", 810 "Content-Type:$content_type\n", 811 "Content-Transfer-Encoding: 8bit\n"; 812 } 813 # Preserve unknown headers 814 foreach my$key(keys%parsed_email) { 815 next if$keyeq 'body'; 816 print$c2"$key:$parsed_email{$key}"; 817 } 818 819 if ($parsed_email{'body'}) { 820 print$c2"\n$parsed_email{'body'}\n"; 821 delete($parsed_email{'body'}); 822 } else { 823 print __("Summary email is empty, skipping it\n"); 824$compose= -1; 825 } 826 827 close$c2; 828 829} elsif ($annotate) { 830 do_edit(@files); 831} 832 833sub ask { 834 my ($prompt,%arg) =@_; 835 my$valid_re=$arg{valid_re}; 836 my$default=$arg{default}; 837 my$confirm_only=$arg{confirm_only}; 838 my$resp; 839 my$i= 0; 840 return defined$default?$default: undef 841 unless defined$term->IN and defined fileno($term->IN) and 842 defined$term->OUT and defined fileno($term->OUT); 843 while ($i++< 10) { 844$resp=$term->readline($prompt); 845 if (!defined$resp) { # EOF 846 print "\n"; 847 return defined$default?$default: undef; 848 } 849 if ($respeq '' and defined$default) { 850 return$default; 851 } 852 if (!defined$valid_reor$resp=~ /$valid_re/) { 853 return$resp; 854 } 855 if ($confirm_only) { 856 my$yesno=$term->readline( 857 # TRANSLATORS: please keep [y/N] as is. 858 sprintf(__("Are you sure you want to use <%s> [y/N]? "),$resp)); 859 if (defined$yesno&&$yesno=~ /y/i) { 860 return$resp; 861 } 862 } 863 } 864 return; 865} 866 867sub parse_header_line { 868 my$lines= shift; 869 my$parsed_line= shift; 870 my$addr_pat= join "|", qw(To Cc Bcc); 871 872foreach(split(/\n/,$lines)) { 873if(/^($addr_pat):\s*(.+)$/i) { 874$parsed_line->{$1} = [ parse_address_line($2) ]; 875}elsif(/^([^:]*):\s*(.+)\s*$/i) { 876$parsed_line->{$1} =$2; 877} 878} 879} 880 881sub filter_body { 882my$c=shift; 883my$body=""; 884while(my$body_line= <$c>) { 885if($body_line!~m/^GIT:/) { 886$body.=$body_line; 887} 888} 889return$body; 890} 891 892 893my%broken_encoding; 894 895sub file_declares_8bit_cte { 896my$fn=shift; 897open(my$fh,'<',$fn); 898while(my$line= <$fh>) { 899last if($line=~/^$/); 900return1if($line=~/^Content-Transfer-Encoding: .*8bit.*$/); 901} 902close$fh; 903return0; 904} 905 906foreachmy$f(@files) { 907next unless(body_or_subject_has_nonascii($f) 908&& !file_declares_8bit_cte($f)); 909$broken_encoding{$f} =1; 910} 911 912if(!defined$auto_8bit_encoding&&scalar%broken_encoding) { 913print __("The following files are 8bit, but do not declare ". 914"a Content-Transfer-Encoding.\n"); 915foreachmy$f(sort keys%broken_encoding) { 916print"$f\n"; 917} 918$auto_8bit_encoding= ask(__("Which 8bit encoding should I declare [UTF-8]? "), 919 valid_re =>qr/.{4}/, confirm_only =>1, 920default=>"UTF-8"); 921} 922 923if(!$force) { 924formy$f(@files) { 925if(get_patch_subject($f) =~/\Q*** SUBJECT HERE ***\E/) { 926die sprintf(__("Refusing to send because the patch\n\t%s\n" 927."has the template subject '*** SUBJECT HERE ***'. " 928."Pass --force if you really want to send.\n"),$f); 929} 930} 931} 932 933if(defined$sender) { 934$sender=~s/^\s+|\s+$//g; 935($sender) = expand_aliases($sender); 936}else{ 937$sender=$repoauthor||$repocommitter||''; 938} 939 940# $sender could be an already sanitized address 941# (e.g. sendemail.from could be manually sanitized by user). 942# But it's a no-op to run sanitize_address on an already sanitized address. 943$sender= sanitize_address($sender); 944 945my$to_whom= __("To whom should the emails be sent (if anyone)?"); 946my$prompting=0; 947if(!@initial_to&& !defined$to_cmd) { 948my$to= ask("$to_whom", 949default=>"", 950 valid_re =>qr/\@.*\./, confirm_only =>1); 951push@initial_to, parse_address_line($to)ifdefined$to;# sanitized/validated later 952$prompting++; 953} 954 955sub expand_aliases { 956returnmap{ expand_one_alias($_) }@_; 957} 958 959my%EXPANDED_ALIASES; 960sub expand_one_alias { 961my$alias=shift; 962if($EXPANDED_ALIASES{$alias}) { 963die sprintf(__("fatal: alias '%s' expands to itself\n"),$alias); 964} 965local$EXPANDED_ALIASES{$alias} =1; 966return$aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) :$alias; 967} 968 969@initial_to= process_address_list(@initial_to); 970@initial_cc= process_address_list(@initial_cc); 971@initial_bcc= process_address_list(@initial_bcc); 972 973if($thread&& !defined$initial_in_reply_to&&$prompting) { 974$initial_in_reply_to= ask( 975 __("Message-ID to be used as In-Reply-To for the first email (if any)? "), 976default=>"", 977 valid_re =>qr/\@.*\./, confirm_only =>1); 978} 979if(defined$initial_in_reply_to) { 980$initial_in_reply_to=~s/^\s*<?//; 981$initial_in_reply_to=~s/>?\s*$//; 982$initial_in_reply_to="<$initial_in_reply_to>"if$initial_in_reply_tone''; 983} 984 985if(defined$reply_to) { 986$reply_to=~s/^\s+|\s+$//g; 987($reply_to) = expand_aliases($reply_to); 988$reply_to= sanitize_address($reply_to); 989} 990 991if(!defined$smtp_server) { 992my@sendmail_paths=qw( /usr/sbin/sendmail /usr/lib/sendmail ); 993push@sendmail_paths,map{"$_/sendmail"}split/:/,$ENV{PATH}; 994foreach(@sendmail_paths) { 995if(-x $_) { 996$smtp_server=$_; 997last; 998} 999}1000$smtp_server||='localhost';# could be 127.0.0.1, too... *shrug*1001}10021003if($compose&&$compose>0) {1004@files= ($compose_filename.".final",@files);1005}10061007# Variables we set as part of the loop over files1008our($message_id,%mail,$subject,$in_reply_to,$references,$message,1009$needs_confirm,$message_num,$ask_default);10101011sub extract_valid_address {1012my$address=shift;1013my$local_part_regexp=qr/[^<>"\s@]+/;1014my$domain_regexp=qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/;10151016# check for a local address:1017return$addressif($address=~/^($local_part_regexp)$/);10181019$address=~s/^\s*<(.*)>\s*$/$1/;1020if($have_email_valid) {1021returnscalar Email::Valid->address($address);1022}10231024# less robust/correct than the monster regexp in Email::Valid,1025# but still does a 99% job, and one less dependency1026return$1if$address=~/($local_part_regexp\@$domain_regexp)/;1027return;1028}10291030sub extract_valid_address_or_die {1031my$address=shift;1032$address= extract_valid_address($address);1033die sprintf(__("error: unable to extract a valid address from:%s\n"),$address)1034if!$address;1035return$address;1036}10371038sub validate_address {1039my$address=shift;1040while(!extract_valid_address($address)) {1041printf STDERR __("error: unable to extract a valid address from:%s\n"),$address;1042# TRANSLATORS: Make sure to include [q] [d] [e] in your1043# translation. The program will only accept English input1044# at this point.1045$_= ask(__("What to do with this address? ([q]uit|[d]rop|[e]dit): "),1046 valid_re =>qr/^(?:quit|q|drop|d|edit|e)/i,1047default=>'q');1048if(/^d/i) {1049returnundef;1050}elsif(/^q/i) {1051 cleanup_compose_files();1052exit(0);1053}1054$address= ask("$to_whom",1055default=>"",1056 valid_re =>qr/\@.*\./, confirm_only =>1);1057}1058return$address;1059}10601061sub validate_address_list {1062return(grep{defined$_}1063map{ validate_address($_) }@_);1064}10651066# Usually don't need to change anything below here.10671068# we make a "fake" message id by taking the current number1069# of seconds since the beginning of Unix time and tacking on1070# a random number to the end, in case we are called quicker than1071# 1 second since the last time we were called.10721073# We'll setup a template for the message id, using the "from" address:10741075my($message_id_stamp,$message_id_serial);1076sub make_message_id {1077my$uniq;1078if(!defined$message_id_stamp) {1079$message_id_stamp= strftime("%Y%m%d%H%M%S.$$",gmtime(time));1080$message_id_serial=0;1081}1082$message_id_serial++;1083$uniq="$message_id_stamp-$message_id_serial";10841085my$du_part;1086for($sender,$repocommitter,$repoauthor) {1087$du_part= extract_valid_address(sanitize_address($_));1088last if(defined$du_partand$du_partne'');1089}1090if(not defined$du_partor$du_parteq'') {1091require Sys::Hostname;1092$du_part='user@'. Sys::Hostname::hostname();1093}1094my$message_id_template="<%s-%s>";1095$message_id=sprintf($message_id_template,$uniq,$du_part);1096#print "new message id = $message_id\n"; # Was useful for debugging1097}1098109911001101$time=time-scalar$#files;11021103sub unquote_rfc2047 {1104local($_) =@_;1105my$charset;1106my$sep=qr/[ \t]+/;1107 s{$re_encoded_word(?:$sep$re_encoded_word)*}{1108my@words=split$sep,$&;1109foreach(@words) {1110m/$re_encoded_word/;1111$charset=$1;1112my$encoding=$2;1113my$text=$3;1114if($encodingeq'q'||$encodingeq'Q') {1115$_=$text;1116s/_/ /g;1117s/=([0-9A-F]{2})/chr(hex($1))/egi;1118}else{1119# other encodings not supported yet1120}1121}1122join'',@words;1123}eg;1124returnwantarray? ($_,$charset) :$_;1125}11261127sub quote_rfc2047 {1128local$_=shift;1129my$encoding=shift||'UTF-8';1130s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X",ord($1))/eg;1131s/(.*)/=\?$encoding\?q\?$1\?=/;1132return$_;1133}11341135sub is_rfc2047_quoted {1136my$s=shift;1137length($s) <=75&&1138$s=~m/^(?:"[[:ascii:]]*"|$re_encoded_word)$/o;1139}11401141sub subject_needs_rfc2047_quoting {1142my$s=shift;11431144return($s=~/[^[:ascii:]]/) || ($s=~/=\?/);1145}11461147sub quote_subject {1148local$subject=shift;1149my$encoding=shift||'UTF-8';11501151if(subject_needs_rfc2047_quoting($subject)) {1152return quote_rfc2047($subject,$encoding);1153}1154return$subject;1155}11561157# use the simplest quoting being able to handle the recipient1158sub sanitize_address {1159my($recipient) =@_;11601161# remove garbage after email address1162$recipient=~s/(.*>).*$/$1/;11631164my($recipient_name,$recipient_addr) = ($recipient=~/^(.*?)\s*(<.*)/);11651166if(not$recipient_name) {1167return$recipient;1168}11691170# if recipient_name is already quoted, do nothing1171if(is_rfc2047_quoted($recipient_name)) {1172return$recipient;1173}11741175# remove non-escaped quotes1176$recipient_name=~s/(^|[^\\])"/$1/g;11771178# rfc2047 is needed if a non-ascii char is included1179if($recipient_name=~/[^[:ascii:]]/) {1180$recipient_name= quote_rfc2047($recipient_name);1181}11821183# double quotes are needed if specials or CTLs are included1184elsif($recipient_name=~/[][()<>@,;:\\".\000-\037\177]/) {1185$recipient_name=~s/([\\\r])/\\$1/g;1186$recipient_name=qq["$recipient_name"];1187}11881189return"$recipient_name$recipient_addr";11901191}11921193sub strip_garbage_one_address {1194my($addr) =@_;1195chomp$addr;1196if($addr=~/^(("[^"]*"|[^"<]*)? *<[^>]*>).*/) {1197# "Foo Bar" <foobar@example.com> [possibly garbage here]1198# Foo Bar <foobar@example.com> [possibly garbage here]1199return$1;1200}1201if($addr=~/^(<[^>]*>).*/) {1202# <foo@example.com> [possibly garbage here]1203# if garbage contains other addresses, they are ignored.1204return$1;1205}1206if($addr=~/^([^"#,\s]*)/) {1207# address without quoting: remove anything after the address1208return$1;1209}1210return$addr;1211}12121213sub sanitize_address_list {1214return(map{ sanitize_address($_) }@_);1215}12161217sub process_address_list {1218my@addr_list=map{ parse_address_line($_) }@_;1219@addr_list= expand_aliases(@addr_list);1220@addr_list= sanitize_address_list(@addr_list);1221@addr_list= validate_address_list(@addr_list);1222return@addr_list;1223}12241225# Returns the local Fully Qualified Domain Name (FQDN) if available.1226#1227# Tightly configured MTAa require that a caller sends a real DNS1228# domain name that corresponds the IP address in the HELO/EHLO1229# handshake. This is used to verify the connection and prevent1230# spammers from trying to hide their identity. If the DNS and IP don't1231# match, the receiveing MTA may deny the connection.1232#1233# Here is a deny example of Net::SMTP with the default "localhost.localdomain"1234#1235# Net::SMTP=GLOB(0x267ec28)>>> EHLO localhost.localdomain1236# Net::SMTP=GLOB(0x267ec28)<<< 550 EHLO argument does not match calling host1237#1238# This maildomain*() code is based on ideas in Perl library Test::Reporter1239# /usr/share/perl5/Test/Reporter/Mail/Util.pm ==> sub _maildomain ()12401241sub valid_fqdn {1242my$domain=shift;1243returndefined$domain&& !($^Oeq'darwin'&&$domain=~/\.local$/) &&$domain=~/\./;1244}12451246sub maildomain_net {1247my$maildomain;12481249my$domain= Net::Domain::domainname();1250$maildomain=$domainif valid_fqdn($domain);12511252return$maildomain;1253}12541255sub maildomain_mta {1256my$maildomain;12571258formy$host(qw(mailhost localhost)) {1259my$smtp= Net::SMTP->new($host);1260if(defined$smtp) {1261my$domain=$smtp->domain;1262$smtp->quit;12631264$maildomain=$domainif valid_fqdn($domain);12651266last if$maildomain;1267}1268}12691270return$maildomain;1271}12721273sub maildomain {1274return maildomain_net() || maildomain_mta() ||'localhost.localdomain';1275}12761277sub smtp_host_string {1278if(defined$smtp_server_port) {1279return"$smtp_server:$smtp_server_port";1280}else{1281return$smtp_server;1282}1283}12841285# Returns 1 if authentication succeeded or was not necessary1286# (smtp_user was not specified), and 0 otherwise.12871288sub smtp_auth_maybe {1289if(!defined$smtp_authuser||$auth|| (defined$smtp_auth&&$smtp_autheq"none")) {1290return1;1291}12921293# Workaround AUTH PLAIN/LOGIN interaction defect1294# with Authen::SASL::Cyrus1295eval{1296require Authen::SASL;1297 Authen::SASL->import(qw(Perl));1298};12991300# Check mechanism naming as defined in:1301# https://tools.ietf.org/html/rfc4422#page-81302if($smtp_auth&&$smtp_auth!~/^(\b[A-Z0-9-_]{1,20}\s*)*$/) {1303die"invalid smtp auth: '${smtp_auth}'";1304}13051306# TODO: Authentication may fail not because credentials were1307# invalid but due to other reasons, in which we should not1308# reject credentials.1309$auth= Git::credential({1310'protocol'=>'smtp',1311'host'=> smtp_host_string(),1312'username'=>$smtp_authuser,1313# if there's no password, "git credential fill" will1314# give us one, otherwise it'll just pass this one.1315'password'=>$smtp_authpass1316},sub{1317my$cred=shift;13181319if($smtp_auth) {1320my$sasl= Authen::SASL->new(1321 mechanism =>$smtp_auth,1322 callback => {1323 user =>$cred->{'username'},1324 pass =>$cred->{'password'},1325 authname =>$cred->{'username'},1326}1327);13281329return!!$smtp->auth($sasl);1330}13311332return!!$smtp->auth($cred->{'username'},$cred->{'password'});1333});13341335return$auth;1336}13371338sub ssl_verify_params {1339eval{1340require IO::Socket::SSL;1341 IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/);1342};1343if($@) {1344print STDERR "Not using SSL_VERIFY_PEER due to out-of-date IO::Socket::SSL.\n";1345return;1346}13471348if(!defined$smtp_ssl_cert_path) {1349# use the OpenSSL defaults1350return(SSL_verify_mode => SSL_VERIFY_PEER());1351}13521353if($smtp_ssl_cert_patheq"") {1354return(SSL_verify_mode => SSL_VERIFY_NONE());1355}elsif(-d $smtp_ssl_cert_path) {1356return(SSL_verify_mode => SSL_VERIFY_PEER(),1357 SSL_ca_path =>$smtp_ssl_cert_path);1358}elsif(-f $smtp_ssl_cert_path) {1359return(SSL_verify_mode => SSL_VERIFY_PEER(),1360 SSL_ca_file =>$smtp_ssl_cert_path);1361}else{1362die sprintf(__("CA path\"%s\"does not exist"),$smtp_ssl_cert_path);1363}1364}13651366sub file_name_is_absolute {1367my($path) =@_;13681369# msys does not grok DOS drive-prefixes1370if($^Oeq'msys') {1371return($path=~ m#^/# || $path =~ m#^[a-zA-Z]\:#)1372}13731374require File::Spec::Functions;1375return File::Spec::Functions::file_name_is_absolute($path);1376}13771378# Prepares the email, then asks the user what to do.1379#1380# If the user chooses to send the email, it's sent and 1 is returned.1381# If the user chooses not to send the email, 0 is returned.1382# If the user decides they want to make further edits, -1 is returned and the1383# caller is expected to call send_message again after the edits are performed.1384#1385# If an error occurs sending the email, this just dies.13861387sub send_message {1388my@recipients= unique_email_list(@to);1389@cc= (grep{my$cc= extract_valid_address_or_die($_);1390not grep{$cceq$_||$_=~/<\Q${cc}\E>$/}@recipients1391}1392@cc);1393my$to=join(",\n\t",@recipients);1394@recipients= unique_email_list(@recipients,@cc,@initial_bcc);1395@recipients= (map{ extract_valid_address_or_die($_) }@recipients);1396my$date= format_2822_time($time++);1397my$gitversion='@@GIT_VERSION@@';1398if($gitversion=~m/..GIT_VERSION../) {1399$gitversion= Git::version();1400}14011402my$cc=join(",\n\t", unique_email_list(@cc));1403my$ccline="";1404if($ccne'') {1405$ccline="\nCc:$cc";1406}1407 make_message_id()unlessdefined($message_id);14081409my$header="From:$sender1410To:$to${ccline}1411Subject:$subject1412Date:$date1413Message-Id:$message_id1414";1415if($use_xmailer) {1416$header.="X-Mailer: git-send-email$gitversion\n";1417}1418if($in_reply_to) {14191420$header.="In-Reply-To:$in_reply_to\n";1421$header.="References:$references\n";1422}1423if($reply_to) {1424$header.="Reply-To:$reply_to\n";1425}1426if(@xh) {1427$header.=join("\n",@xh) ."\n";1428}14291430my@sendmail_parameters= ('-i',@recipients);1431my$raw_from=$sender;1432if(defined$envelope_sender&&$envelope_senderne"auto") {1433$raw_from=$envelope_sender;1434}1435$raw_from= extract_valid_address($raw_from);1436unshift(@sendmail_parameters,1437'-f',$raw_from)if(defined$envelope_sender);14381439if($needs_confirm&& !$dry_run) {1440print"\n$header\n";1441if($needs_confirmeq"inform") {1442$confirm_unconfigured=0;# squelch this message for the rest of this run1443$ask_default="y";# assume yes on EOF since user hasn't explicitly asked for confirmation1444print __ <<EOF ;1445 The Cc list above has been expanded by additional1446 addresses found in the patch commit message. By default1447 send-email prompts before sending whenever this occurs.1448 This behavior is controlled by the sendemail.confirm1449 configuration setting.14501451 For additional information, run 'git send-email --help'.1452 To retain the current behavior, but squelch this message,1453 run 'git config --global sendemail.confirm auto'.14541455EOF1456}1457# TRANSLATORS: Make sure to include [y] [n] [e] [q] [a] in your1458# translation. The program will only accept English input1459# at this point.1460$_= ask(__("Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): "),1461 valid_re =>qr/^(?:yes|y|no|n|edit|e|quit|q|all|a)/i,1462default=>$ask_default);1463die __("Send this email reply required")unlessdefined$_;1464if(/^n/i) {1465return0;1466}elsif(/^e/i) {1467return-1;1468}elsif(/^q/i) {1469 cleanup_compose_files();1470exit(0);1471}elsif(/^a/i) {1472$confirm='never';1473}1474}14751476unshift(@sendmail_parameters,@smtp_server_options);14771478if($dry_run) {1479# We don't want to send the email.1480}elsif(file_name_is_absolute($smtp_server)) {1481my$pid=open my$sm,'|-';1482defined$pidor die$!;1483if(!$pid) {1484exec($smtp_server,@sendmail_parameters)or die$!;1485}1486print$sm"$header\n$message";1487close$smor die$!;1488}else{14891490if(!defined$smtp_server) {1491die __("The required SMTP server is not properly defined.")1492}14931494require Net::SMTP;1495my$use_net_smtp_ssl= version->parse($Net::SMTP::VERSION) < version->parse("2.34");1496$smtp_domain||= maildomain();14971498if($smtp_encryptioneq'ssl') {1499$smtp_server_port||=465;# ssmtp1500require IO::Socket::SSL;15011502# Suppress "variable accessed once" warning.1503{1504no warnings 'once';1505$IO::Socket::SSL::DEBUG =1;1506}15071508# Net::SMTP::SSL->new() does not forward any SSL options1509 IO::Socket::SSL::set_client_defaults(1510 ssl_verify_params());15111512if($use_net_smtp_ssl) {1513require Net::SMTP::SSL;1514$smtp||= Net::SMTP::SSL->new($smtp_server,1515 Hello =>$smtp_domain,1516 Port =>$smtp_server_port,1517 Debug =>$debug_net_smtp);1518}1519else{1520$smtp||= Net::SMTP->new($smtp_server,1521 Hello =>$smtp_domain,1522 Port =>$smtp_server_port,1523 Debug =>$debug_net_smtp,1524 SSL =>1);1525}1526}1527elsif(!$smtp) {1528$smtp_server_port||=25;1529$smtp||= Net::SMTP->new($smtp_server,1530 Hello =>$smtp_domain,1531 Debug =>$debug_net_smtp,1532 Port =>$smtp_server_port);1533if($smtp_encryptioneq'tls'&&$smtp) {1534if($use_net_smtp_ssl) {1535$smtp->command('STARTTLS');1536$smtp->response();1537if($smtp->code!=220) {1538die sprintf(__("Server does not support STARTTLS!%s"),$smtp->message);1539}1540require Net::SMTP::SSL;1541$smtp= Net::SMTP::SSL->start_SSL($smtp,1542 ssl_verify_params())1543or die sprintf(__("STARTTLS failed!%s"), IO::Socket::SSL::errstr());1544}1545else{1546$smtp->starttls(ssl_verify_params())1547or die sprintf(__("STARTTLS failed!%s"), IO::Socket::SSL::errstr());1548}1549# Send EHLO again to receive fresh1550# supported commands1551$smtp->hello($smtp_domain);1552}1553}15541555if(!$smtp) {1556die __("Unable to initialize SMTP properly. Check config and use --smtp-debug."),1557" VALUES: server=$smtp_server",1558"encryption=$smtp_encryption",1559"hello=$smtp_domain",1560defined$smtp_server_port?" port=$smtp_server_port":"";1561}15621563 smtp_auth_maybe or die$smtp->message;15641565$smtp->mail($raw_from)or die$smtp->message;1566$smtp->to(@recipients)or die$smtp->message;1567$smtp->dataor die$smtp->message;1568$smtp->datasend("$header\n")or die$smtp->message;1569my@lines=split/^/,$message;1570foreachmy$line(@lines) {1571$smtp->datasend("$line")or die$smtp->message;1572}1573$smtp->dataend()or die$smtp->message;1574$smtp->code=~/250|200/or die sprintf(__("Failed to send%s\n"),$subject).$smtp->message;1575}1576if($quiet) {1577printf($dry_run? __("Dry-Sent%s\n") : __("Sent%s\n"),$subject);1578}else{1579print($dry_run? __("Dry-OK. Log says:\n") : __("OK. Log says:\n"));1580if(!file_name_is_absolute($smtp_server)) {1581print"Server:$smtp_server\n";1582print"MAIL FROM:<$raw_from>\n";1583foreachmy$entry(@recipients) {1584print"RCPT TO:<$entry>\n";1585}1586}else{1587print"Sendmail:$smtp_server".join(' ',@sendmail_parameters)."\n";1588}1589print$header,"\n";1590if($smtp) {1591print __("Result: "),$smtp->code,' ',1592($smtp->message=~/\n([^\n]+\n)$/s),"\n";1593}else{1594print __("Result: OK\n");1595}1596}15971598return1;1599}16001601$in_reply_to=$initial_in_reply_to;1602$references=$initial_in_reply_to||'';1603$subject=$initial_subject;1604$message_num=0;16051606# Prepares the email, prompts the user, sends it out1607# Returns 0 if an edit was done and the function should be called again, or 11608# otherwise.1609sub process_file {1610my($t) =@_;16111612open my$fh,"<",$tor die sprintf(__("can't open file%s"),$t);16131614my$author=undef;1615my$sauthor=undef;1616my$author_encoding;1617my$has_content_type;1618my$body_encoding;1619my$xfer_encoding;1620my$has_mime_version;1621@to= ();1622@cc= ();1623@xh= ();1624my$input_format=undef;1625my@header= ();1626$message="";1627$message_num++;1628# First unfold multiline header fields1629while(<$fh>) {1630last if/^\s*$/;1631if(/^\s+\S/and@header) {1632chomp($header[$#header]);1633s/^\s+/ /;1634$header[$#header] .=$_;1635}else{1636push(@header,$_);1637}1638}1639# Now parse the header1640foreach(@header) {1641if(/^From /) {1642$input_format='mbox';1643next;1644}1645chomp;1646if(!defined$input_format&&/^[-A-Za-z]+:\s/) {1647$input_format='mbox';1648}16491650if(defined$input_format&&$input_formateq'mbox') {1651if(/^Subject:\s+(.*)$/i) {1652$subject=$1;1653}1654elsif(/^From:\s+(.*)$/i) {1655($author,$author_encoding) = unquote_rfc2047($1);1656$sauthor= sanitize_address($author);1657next if$suppress_cc{'author'};1658next if$suppress_cc{'self'}and$sauthoreq$sender;1659printf(__("(mbox) Adding cc:%sfrom line '%s'\n"),1660$1,$_)unless$quiet;1661push@cc,$1;1662}1663elsif(/^To:\s+(.*)$/i) {1664foreachmy$addr(parse_address_line($1)) {1665printf(__("(mbox) Adding to:%sfrom line '%s'\n"),1666$addr,$_)unless$quiet;1667push@to,$addr;1668}1669}1670elsif(/^Cc:\s+(.*)$/i) {1671foreachmy$addr(parse_address_line($1)) {1672my$qaddr= unquote_rfc2047($addr);1673my$saddr= sanitize_address($qaddr);1674if($saddreq$sender) {1675next if($suppress_cc{'self'});1676}else{1677next if($suppress_cc{'cc'});1678}1679printf(__("(mbox) Adding cc:%sfrom line '%s'\n"),1680$addr,$_)unless$quiet;1681push@cc,$addr;1682}1683}1684elsif(/^Content-type:/i) {1685$has_content_type=1;1686if(/charset="?([^ "]+)/) {1687$body_encoding=$1;1688}1689push@xh,$_;1690}1691elsif(/^MIME-Version/i) {1692$has_mime_version=1;1693push@xh,$_;1694}1695elsif(/^Message-Id: (.*)/i) {1696$message_id=$1;1697}1698elsif(/^Content-Transfer-Encoding: (.*)/i) {1699$xfer_encoding=$1ifnot defined$xfer_encoding;1700}1701elsif(/^In-Reply-To: (.*)/i) {1702$in_reply_to=$1;1703}1704elsif(/^References: (.*)/i) {1705$references=$1;1706}1707elsif(!/^Date:\s/i&&/^[-A-Za-z]+:\s+\S/) {1708push@xh,$_;1709}1710}else{1711# In the traditional1712# "send lots of email" format,1713# line 1 = cc1714# line 2 = subject1715# So let's support that, too.1716$input_format='lots';1717if(@cc==0&& !$suppress_cc{'cc'}) {1718printf(__("(non-mbox) Adding cc:%sfrom line '%s'\n"),1719$_,$_)unless$quiet;1720push@cc,$_;1721}elsif(!defined$subject) {1722$subject=$_;1723}1724}1725}1726# Now parse the message body1727while(<$fh>) {1728$message.=$_;1729if(/^([a-z][a-z-]*-by|Cc): (.*)/i) {1730chomp;1731my($what,$c) = ($1,$2);1732# strip garbage for the address we'll use:1733$c= strip_garbage_one_address($c);1734# sanitize a bit more to decide whether to suppress the address:1735my$sc= sanitize_address($c);1736if($sceq$sender) {1737next if($suppress_cc{'self'});1738}else{1739if($what=~/^Signed-off-by$/i) {1740next if$suppress_cc{'sob'};1741}elsif($what=~/-by$/i) {1742next if$suppress_cc{'misc-by'};1743}elsif($what=~/Cc/i) {1744next if$suppress_cc{'bodycc'};1745}1746}1747if($c!~/.+@.+|<.+>/) {1748printf("(body) Ignoring%sfrom line '%s'\n",1749$what,$_)unless$quiet;1750next;1751}1752push@cc,$c;1753printf(__("(body) Adding cc:%sfrom line '%s'\n"),1754$c,$_)unless$quiet;1755}1756}1757close$fh;17581759push@to, recipients_cmd("to-cmd","to",$to_cmd,$t)1760ifdefined$to_cmd;1761push@cc, recipients_cmd("cc-cmd","cc",$cc_cmd,$t)1762ifdefined$cc_cmd&& !$suppress_cc{'cccmd'};17631764if($broken_encoding{$t} && !$has_content_type) {1765$xfer_encoding='8bit'ifnot defined$xfer_encoding;1766$has_content_type=1;1767push@xh,"Content-Type: text/plain; charset=$auto_8bit_encoding";1768$body_encoding=$auto_8bit_encoding;1769}17701771if($broken_encoding{$t} && !is_rfc2047_quoted($subject)) {1772$subject= quote_subject($subject,$auto_8bit_encoding);1773}17741775if(defined$sauthorand$sauthorne$sender) {1776$message="From:$author\n\n$message";1777if(defined$author_encoding) {1778if($has_content_type) {1779if($body_encodingeq$author_encoding) {1780# ok, we already have the right encoding1781}1782else{1783# uh oh, we should re-encode1784}1785}1786else{1787$xfer_encoding='8bit'ifnot defined$xfer_encoding;1788$has_content_type=1;1789push@xh,1790"Content-Type: text/plain; charset=$author_encoding";1791}1792}1793}1794$xfer_encoding='8bit'ifnot defined$xfer_encoding;1795($message,$xfer_encoding) = apply_transfer_encoding(1796$message,$xfer_encoding,$target_xfer_encoding);1797push@xh,"Content-Transfer-Encoding:$xfer_encoding";1798unshift@xh,'MIME-Version: 1.0'unless$has_mime_version;17991800$needs_confirm= (1801$confirmeq"always"or1802($confirm=~/^(?:auto|cc)$/&&@cc)or1803($confirm=~/^(?:auto|compose)$/&&$compose&&$message_num==1));1804$needs_confirm="inform"if($needs_confirm&&$confirm_unconfigured&&@cc);18051806@to= process_address_list(@to);1807@cc= process_address_list(@cc);18081809@to= (@initial_to,@to);1810@cc= (@initial_cc,@cc);18111812if($message_num==1) {1813if(defined$cover_ccand$cover_cc) {1814@initial_cc=@cc;1815}1816if(defined$cover_toand$cover_to) {1817@initial_to=@to;1818}1819}18201821my$message_was_sent= send_message();1822if($message_was_sent== -1) {1823 do_edit($t);1824return0;1825}18261827# set up for the next message1828if($thread&&$message_was_sent&&1829($chain_reply_to|| !defined$in_reply_to||length($in_reply_to) ==0||1830$message_num==1)) {1831$in_reply_to=$message_id;1832if(length$references>0) {1833$references.="\n$message_id";1834}else{1835$references="$message_id";1836}1837}1838$message_id=undef;1839$num_sent++;1840if(defined$batch_size&&$num_sent==$batch_size) {1841$num_sent=0;1842$smtp->quitifdefined$smtp;1843undef$smtp;1844undef$auth;1845sleep($relogin_delay)ifdefined$relogin_delay;1846}18471848return1;1849}18501851foreachmy$t(@files) {1852while(!process_file($t)) {1853# user edited the file1854}1855}18561857# Execute a command (e.g. $to_cmd) to get a list of email addresses1858# and return a results array1859sub recipients_cmd {1860my($prefix,$what,$cmd,$file) =@_;18611862my@addresses= ();1863open my$fh,"-|","$cmd\Q$file\E"1864or die sprintf(__("(%s) Could not execute '%s'"),$prefix,$cmd);1865while(my$address= <$fh>) {1866$address=~s/^\s*//g;1867$address=~s/\s*$//g;1868$address= sanitize_address($address);1869next if($addresseq$senderand$suppress_cc{'self'});1870push@addresses,$address;1871printf(__("(%s) Adding%s:%sfrom: '%s'\n"),1872$prefix,$what,$address,$cmd)unless$quiet;1873}1874close$fh1875or die sprintf(__("(%s) failed to close pipe to '%s'"),$prefix,$cmd);1876return@addresses;1877}18781879cleanup_compose_files();18801881sub cleanup_compose_files {1882unlink($compose_filename,$compose_filename.".final")if$compose;1883}18841885$smtp->quitif$smtp;18861887sub apply_transfer_encoding {1888my$message=shift;1889my$from=shift;1890my$to=shift;18911892return($message,$to)if($fromeq$toand$fromne'7bit');18931894require MIME::QuotedPrint;1895require MIME::Base64;18961897$message= MIME::QuotedPrint::decode($message)1898if($fromeq'quoted-printable');1899$message= MIME::Base64::decode($message)1900if($fromeq'base64');19011902$to= ($message=~/(?:.{999,}|\r)/) ?'quoted-printable':'8bit'1903if$toeq'auto';19041905die __("cannot send message as 7bit")1906if($toeq'7bit'and$message=~/[^[:ascii:]]/);1907return($message,$to)1908if($toeq'7bit'or$toeq'8bit');1909return(MIME::QuotedPrint::encode($message,"\n",0),$to)1910if($toeq'quoted-printable');1911return(MIME::Base64::encode($message,"\n"),$to)1912if($toeq'base64');1913die __("invalid transfer encoding");1914}19151916sub unique_email_list {1917my%seen;1918my@emails;19191920foreachmy$entry(@_) {1921my$clean= extract_valid_address_or_die($entry);1922$seen{$clean} ||=0;1923next if$seen{$clean}++;1924push@emails,$entry;1925}1926return@emails;1927}19281929sub validate_patch {1930my($fn,$xfer_encoding) =@_;19311932if($repo) {1933my$validate_hook= catfile(catdir($repo->repo_path(),'hooks'),1934'sendemail-validate');1935my$hook_error;1936if(-x $validate_hook) {1937my$target= abs_path($fn);1938# The hook needs a correct cwd and GIT_DIR.1939my$cwd_save= cwd();1940chdir($repo->wc_path()or$repo->repo_path())1941or die("chdir:$!");1942local$ENV{"GIT_DIR"} =$repo->repo_path();1943$hook_error="rejected by sendemail-validate hook"1944ifsystem($validate_hook,$target);1945chdir($cwd_save)or die("chdir:$!");1946}1947return$hook_errorif$hook_error;1948}19491950# Any long lines will be automatically fixed if we use a suitable transfer1951# encoding.1952unless($xfer_encoding=~/^(?:auto|quoted-printable|base64)$/) {1953open(my$fh,'<',$fn)1954or die sprintf(__("unable to open%s:%s\n"),$fn,$!);1955while(my$line= <$fh>) {1956if(length($line) >998) {1957returnsprintf(__("%s: patch contains a line longer than 998 characters"), $.);1958}1959}1960}1961return;1962}19631964sub handle_backup {1965my($last,$lastlen,$file,$known_suffix) =@_;1966my($suffix,$skip);19671968$skip=0;1969if(defined$last&&1970($lastlen<length($file)) &&1971(substr($file,0,$lastlen)eq$last) &&1972($suffix=substr($file,$lastlen)) !~/^[a-z0-9]/i) {1973if(defined$known_suffix&&$suffixeq$known_suffix) {1974printf(__("Skipping%swith backup suffix '%s'.\n"),$file,$known_suffix);1975$skip=1;1976}else{1977# TRANSLATORS: please keep "[y|N]" as is.1978my$answer= ask(sprintf(__("Do you really want to send%s?[y|N]: "),$file),1979 valid_re =>qr/^(?:y|n)/i,1980default=>'n');1981$skip= ($answerne'y');1982if($skip) {1983$known_suffix=$suffix;1984}1985}1986}1987return($skip,$known_suffix);1988}19891990sub handle_backup_files {1991my@file=@_;1992my($last,$lastlen,$known_suffix,$skip,@result);1993formy$file(@file) {1994($skip,$known_suffix) = handle_backup($last,$lastlen,1995$file,$known_suffix);1996push@result,$fileunless$skip;1997$last=$file;1998$lastlen=length($file);1999}2000return@result;2001}20022003sub file_has_nonascii {2004my$fn=shift;2005open(my$fh,'<',$fn)2006or die sprintf(__("unable to open%s:%s\n"),$fn,$!);2007while(my$line= <$fh>) {2008return1if$line=~/[^[:ascii:]]/;2009}2010return0;2011}20122013sub body_or_subject_has_nonascii {2014my$fn=shift;2015open(my$fh,'<',$fn)2016or die sprintf(__("unable to open%s:%s\n"),$fn,$!);2017while(my$line= <$fh>) {2018last if$line=~/^$/;2019return1if$line=~/^Subject.*[^[:ascii:]]/;2020}2021while(my$line= <$fh>) {2022return1if$line=~/[^[:ascii:]]/;2023}2024return0;2025}