File Coverage

blib/lib/Email/Sender/Transport/SMTPS.pm
Criterion Covered Total %
statement 18 79 22.7
branch 0 48 0.0
condition 0 12 0.0
subroutine 6 16 37.5
pod 0 3 0.0
total 24 158 15.1


line stmt bran cond sub pod time code
1             package Email::Sender::Transport::SMTPS;
2              
3 1     1   31606 use Moo;
  1         15372  
  1         7  
4 1     1   2535 use MooX::Types::MooseLike::Base qw(Bool Int Str);
  1         8828  
  1         141  
5             # ABSTRACT: Email::Sender joins Net::SMTPS
6              
7 1     1   881 use Email::Sender::Failure::Multi;
  1         58789  
  1         42  
8 1     1   786 use Email::Sender::Success::Partial;
  1         2466  
  1         35  
9 1     1   542 use Email::Sender::Util;
  1         35946  
  1         1320  
10             our $VERSION = '0.04';
11              
12             has host => (is => 'ro', isa => Str, default => sub { 'localhost' });
13             has ssl => (is => 'ro', isa => Str);
14             has port => (
15             is => 'ro',
16             isa => Int,
17             lazy => 1,
18             default => sub { return ($_[0]->ssl and $_[0]->ssl eq 'starttls') ? 587 : $_[0]->ssl ? 465 : 25; },
19             );
20              
21             has timeout => (is => 'ro', isa => Int, default => sub { 120 });
22              
23             has sasl_username => (is => 'ro', isa => Str);
24             has sasl_password => (is => 'ro', isa => Str);
25              
26             has allow_partial_success => (is => 'ro', isa => Bool, default => sub { 0 });
27              
28             has helo => (is => 'ro', isa => Str);
29             has localaddr => (is => 'ro');
30             has localport => (is => 'ro', isa => Int);
31             has debug => (is => 'ro', isa => Bool);
32              
33             # I am basically -sure- that this is wrong, but sending hundreds of millions of
34             # messages has shown that it is right enough. I will try to make it textbook
35             # later. -- rjbs, 2008-12-05
36             sub _quoteaddr {
37 0     0     my $addr = shift;
38 0           my @localparts = split /\@/, $addr;
39 0           my $domain = pop @localparts;
40 0           my $localpart = join q{@}, @localparts;
41              
42             # this is probably a little too paranoid
43 0 0 0       return $addr unless $localpart =~ /[^\w.+-]/ or $localpart =~ /^\./;
44 0           return join q{@}, qq("$localpart"), $domain;
45             }
46              
47             sub _smtp_client {
48 0     0     my ($self) = @_;
49              
50 0           my $class = "Net::SMTP";
51 0 0         if ($self->ssl) {
52 0           require Net::SMTPS;
53 0           $class = "Net::SMTPS";
54             } else {
55 0           require Net::SMTP;
56             }
57              
58 0           my $smtp = $class->new( $self->_net_smtp_args );
59              
60 0 0         $self->_throw("unable to establish SMTP connection") unless $smtp;
61              
62 0 0         if ($self->sasl_username) {
63 0 0         $self->_throw("sasl_username but no sasl_password")
64             unless defined $self->sasl_password;
65              
66 0 0         unless ($smtp->auth($self->sasl_username, $self->sasl_password)) {
67 0 0         if ($smtp->message =~ /MIME::Base64|Authen::SASL/) {
68 0           Carp::confess("SMTP auth requires MIME::Base64 and Authen::SASL");
69             }
70              
71 0           $self->_throw('failed AUTH', $smtp);
72             }
73             }
74              
75 0           return $smtp;
76             }
77              
78             sub _net_smtp_args {
79 0     0     my ($self) = @_;
80              
81             # compatible
82 0           my $ssl = $self->ssl;
83 0 0 0       $ssl = 'ssl' if $self->ssl and $self->ssl ne 'starttls';
84             return (
85 0 0         $self->host,
    0          
    0          
    0          
    0          
86             Port => $self->port,
87             Timeout => $self->timeout,
88             defined $ssl ? (doSSL => $ssl) : (),
89             defined $self->helo ? (Hello => $self->helo) : (),
90             defined $self->localaddr ? (LocalAddr => $self->localaddr) : (),
91             defined $self->localport ? (LocalPort => $self->localport) : (),
92             defined $self->debug ? (Debug => $self->debug) : (),
93             );
94             }
95              
96             sub _throw {
97 0     0     my ($self, @rest) = @_;
98 0           Email::Sender::Util->_failure(@rest)->throw;
99             }
100              
101             sub send_email {
102 0     0 0   my ($self, $email, $env) = @_;
103              
104             Email::Sender::Failure->throw("no valid addresses in recipient list")
105 0 0         unless my @to = grep { defined and length } @{ $env->{to} };
  0 0          
  0            
106              
107 0           my $smtp = $self->_smtp_client;
108              
109 0     0     my $FAULT = sub { $self->_throw($_[0], $smtp); };
  0            
110              
111 0 0         $smtp->mail(_quoteaddr($env->{from}))
112             or $FAULT->("$env->{from} failed after MAIL FROM:");
113              
114 0           my @failures;
115             my @ok_rcpts;
116              
117 0           for my $addr (@to) {
118 0 0         if ($smtp->to(_quoteaddr($addr))) {
119 0           push @ok_rcpts, $addr;
120             } else {
121             # my ($self, $error, $smtp, $error_class, @rest) = @_;
122 0           push @failures, Email::Sender::Util->_failure(
123             undef,
124             $smtp,
125             recipients => [ $addr ],
126             );
127             }
128             }
129              
130             # This logic used to include: or (@ok_rcpts == 1 and $ok_rcpts[0] eq '0')
131             # because if called without SkipBad, $smtp->to can return 1 or 0. This
132             # should not happen because we now always pass SkipBad and do the counting
133             # ourselves. Still, I've put this comment here (a) in memory of the
134             # suffering it caused to have to find that problem and (b) in case the
135             # original problem is more insidious than I thought! -- rjbs, 2008-12-05
136              
137 0 0 0       if (
      0        
138             @failures
139             and ((@ok_rcpts == 0) or (! $self->allow_partial_success))
140             ) {
141 0 0         $failures[0]->throw if @failures == 1;
142              
143 0 0         my $message = sprintf '%s recipients were rejected during RCPT',
144             @ok_rcpts ? 'some' : 'all';
145              
146 0           Email::Sender::Failure::Multi->throw(
147             message => $message,
148             failures => \@failures,
149             );
150             }
151              
152             # restore Pobox's support for streaming, code-based messages, and arrays here
153             # -- rjbs, 2008-12-04
154              
155 0 0         $smtp->data or $FAULT->("error at DATA start");
156              
157 0           my $msg_string = $email->as_string;
158 0           my $hunk_size = $self->_hunk_size;
159              
160 0           while (length $msg_string) {
161 0           my $next_hunk = substr $msg_string, 0, $hunk_size, '';
162 0 0         $smtp->datasend($next_hunk) or $FAULT->("error at during DATA");
163             }
164              
165 0 0         $smtp->dataend or $FAULT->("error at after DATA");
166              
167 0           my $message = $smtp->message;
168              
169 0           $self->_message_complete($smtp);
170              
171             # We must report partial success (failures) if applicable.
172 0 0         return $self->success({ message => $message }) unless @failures;
173 0           return $self->partial_success({
174             message => $message,
175             failure => Email::Sender::Failure::Multi->new({
176             message => 'some recipients were rejected during RCPT',
177             failures => \@failures
178             }),
179             });
180             }
181              
182 0     0     sub _hunk_size { 2**20 } # send messages to DATA in hunks of 1 mebibyte
183              
184             sub success {
185 0     0 0   my $self = shift;
186 0           my $success = Moo::Role->create_class_with_roles('Email::Sender::Success', 'Email::Sender::Role::HasMessage')->new(@_);
187             }
188              
189             sub partial_success {
190 0     0 0   my $self = shift;
191 0           my $partial_success = Moo::Role->create_class_with_roles('Email::Sender::Success::Partial', 'Email::Sender::Role::HasMessage')->new(@_);
192             }
193              
194 0     0     sub _message_complete { $_[1]->quit; }
195              
196             with 'Email::Sender::Transport';
197 1     1   11 no Moo;
  1         2  
  1         5  
198             1;
199             __END__