File Coverage

blib/lib/Test/Reporter/Transport/Net/SMTP.pm
Criterion Covered Total %
statement 90 127 70.8
branch 31 80 38.7
condition 2 11 18.1
subroutine 11 12 91.6
pod 2 2 100.0
total 136 232 58.6


line stmt bran cond sub pod time code
1 2     2   27074 use strict;
  2         9  
  2         160  
2 2 50   2   16 BEGIN{ if (not $] < 5.006) { require warnings; warnings->import } }
  2         14  
  2         146  
3             package Test::Reporter::Transport::Net::SMTP;
4             our $VERSION = '1.59'; # VERSION
5              
6 2     2   2165 use Test::Reporter::Transport 1.58;
  2         364  
  2         228  
7             our @ISA = qw/Test::Reporter::Transport/;
8              
9 2     2   19 use Net::SMTP;
  2         3  
  2         5475  
10              
11             sub new {
12 4     4 1 238969 my ($class, @args) = @_;
13 4         28 bless { args => \@args } => $class;
14             }
15              
16             sub _net_class {
17 4     4   8 my ($self) = @_;
18 4 50       24 my $class = ref $self ? ref $self : $self;
19 4         18 my ($net_class) = ($class =~ /^Test::Reporter::Transport::(.+)\z/);
20 4         13 return $net_class;
21             }
22              
23             # Next two subs courtesy of Casey West, Ricardo SIGNES, and Email::Date
24             # Visit the Perl Email Project at: http://emailproject.perl.org/
25             sub _tz_diff {
26 4     4   8 my ($self, $time) = @_;
27              
28 4         91 my $diff = Time::Local::timegm(localtime $time)
29             - Time::Local::timegm(gmtime $time);
30              
31 4 50       279 my $direc = $diff < 0 ? '-' : '+';
32 4         4 $diff = abs $diff;
33 4         11 my $tz_hr = int( $diff / 3600 );
34 4         8 my $tz_mi = int( $diff / 60 - $tz_hr * 60 );
35              
36 4         12 return ($direc, $tz_hr, $tz_mi);
37             }
38              
39             sub _format_date {
40 4     4   8 my ($self, $time) = @_;
41 4 50       13 $time = time unless defined $time;
42              
43 4         152 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = (localtime $time);
44 4         13 my $day = (qw[Sun Mon Tue Wed Thu Fri Sat])[$wday];
45 4         11 my $month = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec])[$mon];
46 4         6 $year += 1900;
47              
48 4         20 my ($direc, $tz_hr, $tz_mi) = $self->_tz_diff($time);
49              
50 4         50 sprintf "%s, %d %s %d %02d:%02d:%02d %s%02d%02d",
51             $day, $mday, $month, $year, $hour, $min, $sec, $direc, $tz_hr, $tz_mi;
52             }
53              
54             # Taken with slight modifications from MIME::QuotedPrint::Perl 1.00 by Gisle Aas
55             sub _encode_qp_perl {
56 0     0   0 my ($res,$eol) = @_;
57 0 0       0 $eol = "\n" unless defined $eol;
58              
59 0         0 if (ord('A') == 193) { # on EBCDIC machines we need translation help
60             require Encode;
61             }
62              
63 0         0 my $RE_Z = "\\z";
64 0 0       0 $RE_Z = "\$" if $] < 5.005;
65              
66 0 0       0 if ($] >= 5.006) {
67 0         0 require bytes;
68 0 0 0     0 if (bytes::length($res) > length($res) ||
      0        
69             ($] >= 5.008 && $res =~ /[^\0-\xFF]/))
70             {
71 0         0 require Carp;
72 0         0 Carp::croak("The Quoted-Printable encoding is only defined for bytes");
73             }
74             }
75              
76             # Do not mention ranges such as $res =~ s/([^ \t\n!-<>-~])/sprintf("=%02X", ord($1))/eg;
77             # since that will not even compile on an EBCDIC machine (where ord('!') > ord('<')).
78 0         0 if (ord('A') == 193) { # EBCDIC style machine
79             if (ord('[') == 173) {
80             $res =~ s/([^ \t\n!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~])/sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('cp1047',$1))))/eg; # rule #2,#3
81             $res =~ s/([ \t]+)$/
82             join('', map { sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('cp1047',$_)))) }
83             split('', $1)
84             )/egm; # rule #3 (encode whitespace at eol)
85             }
86             elsif (ord('[') == 187) {
87             $res =~ s/([^ \t\n!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~])/sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('posix-bc',$1))))/eg; # rule #2,#3
88             $res =~ s/([ \t]+)$/
89             join('', map { sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('posix-bc',$_)))) }
90             split('', $1)
91             )/egm; # rule #3 (encode whitespace at eol)
92             }
93             elsif (ord('[') == 186) {
94             $res =~ s/([^ \t\n!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~])/sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('cp37',$1))))/eg; # rule #2,#3
95             $res =~ s/([ \t]+)$/
96             join('', map { sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('cp37',$_)))) }
97             split('', $1)
98             )/egm; # rule #3 (encode whitespace at eol)
99             }
100             }
101             else { # ASCII style machine
102 0         0 $res =~ s/([^ \t\n!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~])/sprintf("=%02X", ord($1))/eg; # rule #2,#3
  0         0  
103 0 0       0 $res =~ s/\n/=0A/g unless length($eol);
104 0         0 $res =~ s/([ \t]+)$/
105 0         0 join('', map { sprintf("=%02X", ord($_)) }
  0         0  
106             split('', $1)
107             )/egm; # rule #3 (encode whitespace at eol)
108             }
109              
110 0 0       0 return $res unless length($eol);
111              
112             # rule #5 (lines must be shorter than 76 chars, but we are not allowed
113             # to break =XX escapes. This makes things complicated :-( )
114 0         0 my $brokenlines = "";
115 0         0 $brokenlines .= "$1=$eol"
116             while $res =~ s/(.*?^[^\n]{73} (?:
117             [^=\n]{2} (?! [^=\n]{0,1} $) # 75 not followed by .?\n
118             |[^=\n] (?! [^=\n]{0,2} $) # 74 not followed by .?.?\n
119             | (?! [^=\n]{0,3} $) # 73 not followed by .?.?.?\n
120             ))//xsm;
121 0         0 $res =~ s/\n$RE_Z/$eol/o;
122              
123 0         0 "$brokenlines$res";
124             }
125              
126             sub _encode_qp {
127 4     4   8 my $text = shift;
128 4 50       14 if ( $] >= 5.007003 ) {
129 4         2153 require MIME::QuotedPrint;
130 4         4472 return MIME::QuotedPrint::encode_qp($text);
131             }
132             else {
133 0         0 return _encode_qp_perl($text);
134             }
135             }
136              
137             sub send {
138 4     4 1 28 my ($self, $report, $recipients) = @_;
139 4   50     36 $recipients ||= [];
140              
141 4         18 my $perl_version = $report->perl_version->{_version};
142 4         52 my $helo = $report->_maildomain(); # XXX: tight -- rjbs, 2008-04-06
143 4         434 my $from = $report->from();
144 4         96 my $via = $report->via();
145 4         357 my @tmprecipients = ();
146 4         10 my @bad = ();
147 4         6 my $smtp;
148              
149             my $mx;
150              
151 4         19 my $transport = $self->_net_class;
152              
153             # Sorry. Tight coupling happened before I got here. -- rjbs, 2008-04-06
154 4         7 for my $server (@{$report->{_mx}}) {
  4         12  
155 4         7 eval {
156 4         66 $smtp = $transport->new(
157             $server,
158             Hello => $helo,
159             Timeout => $report->timeout(),
160             Debug => $report->debug(),
161             $report->transport_args(),
162             );
163             };
164              
165 4 50       581 if (defined $smtp) {
166 4         9 $mx = $server;
167 4         9 last;
168             }
169             }
170              
171 4 50 33     35 die "Unable to connect to any MX's: $@" unless $mx && $smtp;
172              
173 4         10 my $cc_str;
174 4 50       14 if (@$recipients) {
175 0 0       0 if ($mx =~ /(?:^|\.)(?:perl|cpan)\.org$/) {
176 0         0 for my $recipient (sort @$recipients) {
177 0 0       0 if ($recipient =~ /(?:@|\.)(?:perl|cpan)\.org$/) {
178 0         0 push @tmprecipients, $recipient;
179             } else {
180 0         0 push @bad, $recipient;
181             }
182             }
183              
184 0 0       0 if (@bad) {
185 0         0 warn __PACKAGE__, ": Will not attempt to cc the following recipients since perl.org MX's will not relay for them. Either use Test::Reporter::Transport::Mail::Send, use other MX's, or only cc address ending in cpan.org or perl.org: ${\(join ', ', @bad)}.\n";
  0         0  
186             }
187              
188 0         0 $recipients = \@tmprecipients;
189             }
190              
191 0         0 $cc_str = join ', ', @$recipients;
192 0         0 chomp $cc_str;
193 0         0 chomp $cc_str;
194             }
195              
196 4 50       14 $via = ', via ' . $via if $via;
197              
198 4         6 my $envelope_sender = $from;
199 4         30 $envelope_sender =~ s/\s\([^)]+\)$//; # email only; no name
200              
201             # wrap as quoted-printable if we have lines longer than 100 characters
202 4         22 my $body = $report->report;
203 4         291 my $needs_qp = $body =~ /^.{100}/m;
204 4 50       21 $body = _encode_qp($body) if $needs_qp;
205 4         209 my @body = split /\n/, $body;
206              
207             # Net::SMTP returns 1 or undef for pass/fail
208             # Net::SMTP::TLS croaks on fail but may not return 1 on pass
209             # so this closure lets us die on an undef return only for Net::SMTP
210 4 50   2   32 my $die = sub { die $smtp->message if ref $smtp eq 'Net::SMTP' };
  2         34  
211            
212 4 100       9 eval {
213 4 50       38 $smtp->mail($envelope_sender) or $die->();
214 4 50       160 $smtp->to($report->address) or $die->();
215 4 0       108 if ( @$recipients ) { $smtp->cc(@$recipients) or $die->() };
  0 50       0  
216 4 50       16 $smtp->data() or $die->();
217 4 50       52 $smtp->datasend("Date: ", $self->_format_date, "\n") or $die->();
218 4 50       55 $smtp->datasend("Subject: ", $report->subject, "\n") or $die->();
219 4 50       123 $smtp->datasend("From: $from\n") or $die->();
220 4 50       138 $smtp->datasend("To: ", $report->address, "\n") or $die->();
221 4 0       78 if ( @$recipients ) { $smtp->datasend("Cc: $cc_str\n") or $die->() };
  0 50       0  
222 4 50       17 $smtp->datasend("Message-ID: ", $report->message_id(), "\n") or $die->();
223 4 50       279 $smtp->datasend("X-Reported-Via: Test::Reporter $Test::Reporter::VERSION$via\n") or $die->();
224 4 50       56 $smtp->datasend("X-Test-Reporter-Perl: $perl_version\n") or $die->();
225 4 50       45 if ( $needs_qp ) {
226 4         16 $smtp->datasend("MIME-Version: 1.0\n");
227 4         41 $smtp->datasend("Content-Type: text/plain; charset=utf-8\n");
228 4         39 $smtp->datasend("Content-Transfer-Encoding: quoted-printable\n");
229             }
230 4 50       50 $smtp->datasend("\n") or $die->();
231 4         41 for my $b ( @body ) {
232 284 50       3275 $smtp->datasend("$b\n") or $die->();
233             }
234 4 50       46 $smtp->dataend() or $die->();
235 4 100       25 $smtp->quit or $die->();
236 2         14 1;
237             } or die "$transport: $@";
238              
239 2         27 return 1;
240             }
241              
242             1;
243              
244             # ABSTRACT: SMTP transport for Test::Reporter
245              
246              
247              
248             =pod
249              
250             =head1 NAME
251              
252             Test::Reporter::Transport::Net::SMTP - SMTP transport for Test::Reporter
253              
254             =head1 VERSION
255              
256             version 1.59
257              
258             =head1 SYNOPSIS
259              
260             my $report = Test::Reporter->new(
261             transport => 'Net::SMTP',
262             );
263              
264             =head1 DESCRIPTION
265              
266             This module transmits a Test::Reporter report using Net::SMTP.
267              
268             =head1 USAGE
269              
270             See L and L for general usage
271             information.
272              
273             =head2 Transport Arguments
274              
275             $report->transport_args( @args );
276              
277             Any transport arguments are passed through to the Net::SMTP constructer.
278              
279             =head1 METHODS
280              
281             These methods are only for internal use by Test::Reporter.
282              
283             =head2 new
284              
285             my $sender = Test::Reporter::Transport::Net::SMTP->new( @args );
286              
287             The C method is the object constructor.
288              
289             =head2 send
290              
291             $sender->send( $report );
292              
293             The C method transmits the report.
294              
295             =head1 AUTHORS
296              
297             =over 4
298              
299             =item *
300              
301             Adam J. Foxson
302              
303             =item *
304              
305             David Golden
306              
307             =item *
308              
309             Kirrily "Skud" Robert
310              
311             =item *
312              
313             Ricardo Signes
314              
315             =item *
316              
317             Richard Soderberg
318              
319             =item *
320              
321             Kurt Starsinic
322              
323             =back
324              
325             =head1 COPYRIGHT AND LICENSE
326              
327             This software is copyright (c) 2011 by Authors and Contributors.
328              
329             This is free software; you can redistribute it and/or modify it under
330             the same terms as the Perl 5 programming language system itself.
331              
332             =cut
333              
334              
335             __END__