File Coverage

blib/lib/Email/Send/SMTP/Gmail.pm
Criterion Covered Total %
statement 27 274 9.8
branch 0 138 0.0
condition 0 15 0.0
subroutine 9 17 52.9
pod 4 4 100.0
total 40 448 8.9


line stmt bran cond sub pod time code
1             package Email::Send::SMTP::Gmail;
2              
3 1     1   69031 use strict;
  1         3  
  1         31  
4 1     1   5 use warnings;
  1         2  
  1         29  
5 1     1   6 use vars qw($VERSION);
  1         2  
  1         77  
6              
7             $VERSION='1.35';
8             require Net::SMTP;
9 1     1   469 use Authen::SASL;
  1         1123  
  1         6  
10 1     1   495 use MIME::Base64;
  1         735  
  1         59  
11 1     1   582 use Encode;
  1         10549  
  1         79  
12 1     1   8 use File::Spec;
  1         3  
  1         23  
13 1     1   545 use LWP::MediaTypes;
  1         20733  
  1         156  
14 1     1   582 use Email::Date::Format qw(email_date);
  1         3597  
  1         3315  
15              
16             sub new{
17 0     0 1   my $class=shift;
18 0           my $self={@_};
19 0           bless($self, $class);
20 0           my %properties=@_;
21 0           my $smtp='smtp.gmail.com'; # Default value
22 0           my $port='default'; # Default value
23 0           my $layer='tls'; # Default value
24 0           my $auth='AUTO'; # Default
25 0           my $ssl_verify_mode=''; #Default - Warning SSL_VERIFY_NONE
26 0           my $ssl_version='';
27 0           my $timeout=60;
28              
29 0 0         $smtp=$properties{'-smtp'} if defined $properties{'-smtp'};
30 0 0         $port=$properties{'-port'} if defined $properties{'-port'};
31 0 0         $layer=$properties{'-layer'} if defined $properties{'-layer'};
32 0 0         $auth=$properties{'-auth'} if defined $properties{'-auth'};
33 0 0         $ssl_verify_mode=$properties{'-ssl_verify_mode'} if defined $properties{'-ssl_verify_mode'};
34 0 0         $ssl_version=$properties{'-ssl_version'} if defined $properties{'-ssl_version'};
35 0 0         $timeout=$properties{'-timeout'} if defined $properties{'-timeout'};
36              
37 0 0         if(defined $properties{'-from'}){
38 0           $self->{from}=$properties{'-from'};
39             }
40             else{
41 0           $self->{from}=$properties{'-login'};
42             }
43              
44 0           my $connect=$self->_initsmtp($smtp,$port,$properties{'-login'},$properties{'-pass'},$layer,$auth,$properties{'-debug'},$ssl_verify_mode,$ssl_version,$properties{'-ssl_verify_path'},$properties{'-$ssl_verify_ca'},$timeout);
45              
46 0 0         return -1,$self->{error} if(defined $self->{error});
47 0           return $self;
48             }
49              
50             sub _initsmtp{
51 0     0     my $self=shift;
52 0           my $smtp=shift;
53 0           my $port=shift;
54 0           my $login=shift;
55 0           my $pass=shift;
56 0           my $layer=shift;
57 0           my $auth=shift;
58 0           my $debug=shift;
59 0           my $ssl_mode=shift;
60 0           my $ssl_version=shift;
61 0           my $ssl_path=shift;
62 0           my $ssl_ca=shift;
63 0           my $timeout=shift;
64              
65             # The module sets the SMTP google but could use another!
66             # Set port if default
67 0 0         if($port eq 'default'){
68 0 0         if($layer eq 'ssl'){
69 0           $port=465;
70             }
71             else{
72 0           $port=25;
73             }
74             }
75              
76 0 0         print "Connecting to $smtp using $layer with $auth on port $port and timeout of $timeout\n" if $debug;
77             # Set security layer from $layer
78 0 0         if($layer eq 'none')
79             {
80 0 0         if (not $self->{sender} = Net::SMTP->new($smtp, Port =>$port, Debug=>$debug, Timeout=>$timeout)){
81 0           my $error_string=$self->{sender}->message();
82 0           chomp $error_string;
83 0           $self->{error}=$error_string;
84 0 0         print "Could not connect to SMTP server ($smtp $port)\n" if $debug;
85 0           return $self;
86             }
87             }
88             else{
89 0           my $sec=undef;
90 0 0         my $ssl=($layer eq 'ssl')?1:0;
91 0 0         if (not $self->{sender} = Net::SMTP->new($smtp, Port=>$port, Debug=>$debug, SSL=>$ssl, SSL_verify_mode=>$ssl_mode, SSL_version=>$ssl_version,SSL_ca_file=>$ssl_ca,SSL_ca_path=>$ssl_path, Timeout=>$timeout)){
92 0           $self->{error}=$@;
93 0 0         print "Could not connect to SMTP server\n" if $debug;
94 0           return $self;
95             }
96             }
97 0 0         if($auth ne 'none'){
98 0 0         $self->{sender}->starttls if($layer eq 'tls');
99              
100 0 0         if($auth eq 'AUTO'){
101 0 0         unless($self->{sender}->auth($login,$pass)){
102 0           my $error_string=$self->{sender}->message();
103 0           chomp $error_string;
104 0           $self->{error}=$error_string;
105 0 0         print "Authentication -using server methods list- (SMTP) failed: $error_string\n" if $debug;
106             }
107             }
108             else{
109 0 0         unless($self->{sender}->auth(Authen::SASL->new(mechanism => $auth, callback => { user => $login, pass => $pass }))){
110 0           my $error_string=$self->{sender}->message();
111 0           chomp $error_string;
112 0           $self->{error}=$error_string;
113 0 0         print "Authentication -forcing $auth -(SMTP) failed: $error_string\n" if $debug;
114             }
115             }
116             }
117 0           return $self;
118             }
119              
120             sub bye{
121 0     0 1   my $self=shift;
122 0           $self->{sender}->quit();
123 0           return $self;
124             }
125              
126             sub banner{
127 0     0 1   my $self=shift;
128 0           my $banner=$self->{sender}->banner();
129 0           chomp $banner;
130 0           return $banner;
131             }
132              
133             sub _checkfiles
134             {
135             # Checks that all the attachments exist
136 0     0     my $attachs=shift;
137 0           my $verbose=shift;
138              
139 0           my $result=''; # list of valid attachments
140              
141 0           my @attachments=split(/,/,$attachs);
142 0           foreach my $attach(@attachments)
143             {
144 0           $attach=~s/\A[\s,\0,\t,\n,\r]*//;
145 0           $attach=~s/[\s,\0,\t,\n,\r]*\Z//;
146              
147 0 0         unless (-f $attach) {
148 0 0         print "Unable to find the attachment file: $attach (removed from list)\n" if $verbose;
149             }
150             else{
151 0           my $opened=open(my $file,'<',$attach);
152 0 0         if( not $opened){
153 0 0         print "Unable to open the attachment file: $attach (removed from list)\n" if $verbose;
154             }
155             else{
156 0           close $file;
157 0           $result.=','.$attach;
158 0 0         print "Attachment file: $attach added\n" if $verbose;
159             }
160             }
161             }
162 0           $result=~s/\A\,//;
163 0           return $result;
164             }
165              
166             sub _checkfilelist
167             {
168             # Checks that all the attachments exist
169 0     0     my $attachs=shift;
170 0           my $verbose=shift;
171              
172 0           my $result=undef; # list of valid attachments
173 0           my $i=0;
174              
175 0           foreach my $attach(@$attachs)
176             {
177 0           $attach->{file}=~s/\A[\s,\0,\t,\n,\r]*//;
178 0           $attach->{file}=~s/[\s,\0,\t,\n,\r]*\Z//;
179              
180 0 0         unless (-f $attach->{file}) {
181 0 0         print "Unable to find the attachment file: $attach->{file} (removed from list)\n" if $verbose;
182             }
183             else{
184 0           my $opened=open(my $file,'<',$attach->{file});
185 0 0         if( not $opened){
186 0 0         print "Unable to open the attachment file: $attach->{file} (removed from list)\n" if $verbose;
187             }
188             else{
189 0           close $file;
190 0           $result->[$i]->{file}=$attach->{file};
191 0           $i++;
192 0 0         print "Attachment file: $attach->{file} added\n" if $verbose;
193             }
194             }
195             }
196 0           return $result;
197             }
198              
199             sub _createboundary
200             {
201             # Create arbitrary frontier text used to separate different parts of the message
202 0     0     return "This-is-a-mail-boundary-8217539";
203             }
204              
205             sub send
206             {
207 0     0 1   my $self=shift;
208 0           my %properties=@_; # rest of params by hash
209              
210 0           my $verbose=0;
211 0 0         $verbose=$properties{'-verbose'} if defined $properties{'-verbose'};
212             # Load all the email param
213 0           my $mail;
214              
215 0 0         $mail->{to}=$properties{'-to'} if defined $properties{'-to'};
216              
217 0 0 0       $mail->{to}=' ' if((not defined $mail->{to}) or ($mail->{to} eq ''));
218              
219 0           $mail->{from}=$self->{from};
220 0 0         $mail->{from}=$properties{'-from'} if defined $properties{'-from'};
221              
222 0           $mail->{replyto}=$mail->{from};
223 0 0         $mail->{replyto}=$properties{'-replyto'} if defined $properties{'-replyto'};
224              
225 0           $mail->{cc}='';
226 0 0         $mail->{cc}=$properties{'-cc'} if defined $properties{'-cc'};
227              
228 0           $mail->{bcc}='';
229 0 0         $mail->{bcc}=$properties{'-bcc'} if defined $properties{'-bcc'};
230              
231 0           $mail->{charset}='UTF-8';
232 0 0         $mail->{charset}=$properties{'-charset'} if defined $properties{'-charset'};
233              
234 0           $mail->{contenttype}='text/plain';
235 0 0         $mail->{contenttype}=$properties{'-contenttype'} if defined $properties{'-contenttype'};
236              
237 0           $mail->{sns_topic}=undef;
238 0 0         $mail->{sns_topic}=$properties{'-sns_topic'} if defined $properties{'-sns_topic'};
239              
240 0           $mail->{subject}='';
241             #$mail->{subject}=$properties{'-subject'} if defined $properties{'-subject'};
242             # Encode Subject to accomplish RFC
243 0 0         $mail->{subject}=encode("MIME-Q",$properties{'-subject'}) if defined $properties{'-subject'};
244              
245 0           $mail->{body}='';
246 0 0         $mail->{body}=$properties{'-body'} if defined $properties{'-body'};
247              
248 0           $mail->{attachments}='';
249 0 0         $mail->{attachments}=$properties{'-attachments'} if defined $properties{'-attachments'};
250              
251 0 0         $mail->{attachmentlist}=$properties{'-attachmentlist'} if defined $properties{'-attachmentlist'};
252              
253 0 0         if($mail->{attachments} ne '')
254             {
255 0           $mail->{attachments}=_checkfiles($mail->{attachments},$verbose);
256 0 0         print "Attachments separated by comma successfully verified\n" if $verbose;
257             }
258 0 0         if(defined $mail->{attachmentlist}){
259 0           $mail->{attachmentlist}=_checkfilelist($mail->{attachmentlist},$verbose);
260 0 0         print "Attachments \@list successfully verified\n" if $verbose;
261             }
262              
263 0           my $boundary=_createboundary();
264              
265 0           $self->{sender}->mail($mail->{from} . "\n");
266              
267 0           my @recepients = split(/,/, $mail->{to});
268 0           foreach my $recp (@recepients) {
269 0           $self->{sender}->to($recp . "\n");
270             }
271 0           my @ccrecepients = split(/,/, $mail->{cc});
272 0           foreach my $recp (@ccrecepients) {
273 0           $self->{sender}->cc($recp . "\n");
274             }
275 0           my @bccrecepients = split(/,/, $mail->{bcc});
276 0           foreach my $recp (@bccrecepients) {
277 0           $self->{sender}->bcc($recp . "\n");
278             }
279              
280 0           $self->{sender}->data();
281              
282             #Send header
283 0           $self->{sender}->datasend("From: " . $mail->{from} . "\n");
284 0           $self->{sender}->datasend("To: " . $mail->{to} . "\n");
285 0 0         $self->{sender}->datasend("Cc: " . $mail->{cc} . "\n") if ($mail->{cc} ne '');
286 0           $self->{sender}->datasend("Reply-To: " . $mail->{replyto} . "\n");
287 0           $self->{sender}->datasend("Subject: " . $mail->{subject} . "\n");
288 0           $self->{sender}->datasend("Date: " . email_date(). "\n");
289 0 0         $self->{sender}->datasend("X-SES-CONFIGURATION-SET: " . $mail->{sns_topic} ."\n") if (defined $mail->{sns_topic});
290              
291 0 0         if($mail->{attachments} ne '')
    0          
292             {
293 0 0         print "With Attachments\n" if $verbose;
294 0           $self->{sender}->datasend("MIME-Version: 1.0\n");
295 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
296 0           $self->{sender}->datasend("Content-Type: multipart/related; BOUNDARY=\"$boundary\"\n");
297             }
298             else {
299 0           $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");
300             }
301              
302             # Send text body
303 0           $self->{sender}->datasend("\n--$boundary\n");
304 0           $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");
305              
306 0           $self->{sender}->datasend("\n");
307              
308             #################################################
309             # Chunk body in sections (Gmail SMTP limitations)
310             #my @groups_body = split(/(.{76})/,$mail->{body});
311             #$self->{sender}->datasend($_) foreach @groups_body;
312              
313             # Or better. Encode and split
314             #my $str=encode_base64($mail->{body});
315             #my @groups_body = split(/(.{76})/,$str);
316             #$self->{sender}->datasend($_) foreach @groups_body;
317              
318             # Limitation removed
319 0           $self->{sender}->datasend($mail->{body});
320             ##################################################
321              
322 0           $self->{sender}->datasend("\n\n");
323              
324 0           my @attachments=split(/,/,$mail->{attachments});
325              
326 0           foreach my $attach(@attachments)
327             {
328             #my($bytesread, $buffer, $data, $total);
329              
330 0           $attach=~s/\A[\s,\0,\t,\n,\r]*//;
331 0           $attach=~s/[\s,\0,\t,\n,\r]*\Z//;
332              
333             # Get the file name without its directory
334 0           my ($volume, $dir, $fileName) = File::Spec->splitpath($attach);
335             # Get the MIME type
336 0           my $contentType = guess_media_type($attach);
337 0 0         print "Composing MIME with attach $attach\n" if $verbose;
338              
339 0           $self->{sender}->datasend("--$boundary\n");
340 0           $self->{sender}->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
341 0           $self->{sender}->datasend("Content-Transfer-Encoding: base64\n");
342 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
343 0           $self->{sender}->datasend("Content-ID: <$fileName>\n");
344 0           $self->{sender}->datasend("Content-Disposition: inline; filename=\"$fileName\"\n\n");
345             }
346             else {
347 0           $self->{sender}->datasend("Content-Disposition: attachment; filename=\"$fileName\"\n\n");
348             }
349              
350             # Google requires us to divide the attachment
351             # First read -> Encode -> Send in chunks of 76
352             # Read
353 0           my $opened=open(my $file,'<',$attach);
354 0           binmode($file);
355             # Encode
356 0           local $/ = undef;
357 0           my $d=<$file>;
358 0           my $str=encode_base64($d);
359             # Chunks by 76
360 0           my @groups = split(/(.{76})/,$str);
361 0           $self->{sender}->datasend($_) foreach @groups;
362 0           close $file;
363              
364             #$self->{sender}->datasend("--$boundary\n"); # avoid dummy attachment
365             }
366 0           $self->{sender}->datasend("\n--$boundary--\n"); # send endboundary end message
367             }
368             elsif(defined $mail->{attachmentlist})
369             {
370 0 0         print "With Attachments\n" if $verbose;
371 0           $self->{sender}->datasend("MIME-Version: 1.0\n");
372             # $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");
373 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
374 0           $self->{sender}->datasend("Content-Type: multipart/related; BOUNDARY=\"$boundary\"\n");
375             }
376             else {
377 0           $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");
378             }
379              
380             # Send text body
381 0           $self->{sender}->datasend("\n--$boundary\n");
382 0           $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");
383              
384 0           $self->{sender}->datasend("\n");
385              
386             # Chunk body in sections (Gmail SMTP limitations)
387             #$self->{sender}->datasend($mail->{body} . "\n\n");
388 0           my @groups_body = split(/(.{76})/,$mail->{body});
389 0           $self->{sender}->datasend($_) foreach @groups_body;
390 0           $self->{sender}->datasend("\n\n");
391              
392 0           my $attachments=$mail->{attachmentlist};
393 0           foreach my $attach(@$attachments)
394             {
395             #my($bytesread, $buffer, $data, $total);
396              
397 0           $attach->{file}=~s/\A[\s,\0,\t,\n,\r]*//;
398 0           $attach->{file}=~s/[\s,\0,\t,\n,\r]*\Z//;
399              
400 0           my ($volume, $dir, $fileName) = File::Spec->splitpath($attach->{file});
401             # Get the MIME type
402 0           my $contentType = guess_media_type($attach->{file});
403 0 0         print "Composing MIME with attach $attach->{file}\n" if $verbose;
404              
405 0           $self->{sender}->datasend("--$boundary\n");
406 0           $self->{sender}->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
407 0           $self->{sender}->datasend("Content-Transfer-Encoding: base64\n");
408 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
409 0           $self->{sender}->datasend("Content-ID: <$fileName>\n");
410 0           $self->{sender}->datasend("Content-Disposition: inline; filename=\"$fileName\"\n\n");
411             }
412             else {
413 0           $self->{sender}->datasend("Content-Disposition: attachment; filename=\"$fileName\"\n\n");
414             }
415              
416             # Google requires us to divide the attachment
417             # First read -> Encode -> Send in chunks of 76
418             # Read
419 0           my $opened=open(my $file,'<',$attach->{file});
420 0           binmode($file);
421             # Encode
422 0           local $/ = undef;
423 0           my $d=<$file>;
424 0           my $str=encode_base64($d);
425             # Chunks by 76
426 0           my @groups = split(/(.{76})/,$str);
427 0           $self->{sender}->datasend($_) foreach @groups;
428 0           close $file;
429              
430             #$self->{sender}->datasend("--$boundary\n"); # to avoid noname.txt dummy attachment
431             }
432 0           $self->{sender}->datasend("\n--$boundary--\n"); # send endboundary end message
433             }
434             else { # No attachment
435 0 0         print "With No attachments\n" if $verbose;
436             # Send text body
437 0           $self->{sender}->datasend("MIME-Version: 1.0\n");
438 0           $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");
439              
440 0           $self->{sender}->datasend("\n");
441             # Chunk body in sections (Gmail SMTP limitations)
442             #$self->{sender}->datasend($mail->{body} . "\n\n");
443 0           my @groups_body = split(/(.{76})/,$mail->{body});
444 0           $self->{sender}->datasend($_) foreach @groups_body;
445             }
446              
447 0           $self->{sender}->datasend("\n");
448              
449 0 0         if($self->{sender}->dataend()) {
450 0 0         print "Email sent\n" if $verbose;
451 0           return 1;
452             }
453             else{
454 0           my $error_string=$self->{sender}->message();
455 0           chomp $error_string;
456 0           $self->{error}=$error_string;
457              
458 0 0         print "Sorry, there was an error during sending. Please, retry or use Debug\n" if $verbose;
459 0           return -1,$self->{error};
460             }
461              
462             }
463              
464             1;
465             __END__