File Coverage

blib/lib/Email/Send/SMTP/Gmail.pm
Criterion Covered Total %
statement 27 271 9.9
branch 0 134 0.0
condition 0 15 0.0
subroutine 9 17 52.9
pod 4 4 100.0
total 40 441 9.0


line stmt bran cond sub pod time code
1             package Email::Send::SMTP::Gmail;
2              
3 1     1   70385 use strict;
  1         3  
  1         31  
4 1     1   5 use warnings;
  1         2  
  1         30  
5 1     1   6 use vars qw($VERSION);
  1         2  
  1         80  
6              
7             $VERSION='1.32';
8             require Net::SMTP;
9 1     1   494 use Authen::SASL;
  1         1136  
  1         5  
10 1     1   519 use MIME::Base64;
  1         710  
  1         60  
11 1     1   617 use Encode;
  1         10746  
  1         77  
12 1     1   8 use File::Spec;
  1         2  
  1         22  
13 1     1   565 use LWP::MediaTypes;
  1         20539  
  1         130  
14 1     1   572 use Email::Date::Format qw(email_date);
  1         3631  
  1         3387  
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->{subject}='';
238             #$mail->{subject}=$properties{'-subject'} if defined $properties{'-subject'};
239             # Encode Subject to accomplish RFC
240 0 0         $mail->{subject}=encode("MIME-Q",$properties{'-subject'}) if defined $properties{'-subject'};
241              
242 0           $mail->{body}='';
243 0 0         $mail->{body}=$properties{'-body'} if defined $properties{'-body'};
244              
245 0           $mail->{attachments}='';
246 0 0         $mail->{attachments}=$properties{'-attachments'} if defined $properties{'-attachments'};
247              
248 0 0         $mail->{attachmentlist}=$properties{'-attachmentlist'} if defined $properties{'-attachmentlist'};
249              
250 0 0         if($mail->{attachments} ne '')
251             {
252 0           $mail->{attachments}=_checkfiles($mail->{attachments},$verbose);
253 0 0         print "Attachments separated by comma successfully verified\n" if $verbose;
254             }
255 0 0         if(defined $mail->{attachmentlist}){
256 0           $mail->{attachmentlist}=_checkfilelist($mail->{attachmentlist},$verbose);
257 0 0         print "Attachments \@list successfully verified\n" if $verbose;
258             }
259              
260 0           my $boundary=_createboundary();
261              
262 0           $self->{sender}->mail($mail->{from} . "\n");
263              
264 0           my @recepients = split(/,/, $mail->{to});
265 0           foreach my $recp (@recepients) {
266 0           $self->{sender}->to($recp . "\n");
267             }
268 0           my @ccrecepients = split(/,/, $mail->{cc});
269 0           foreach my $recp (@ccrecepients) {
270 0           $self->{sender}->cc($recp . "\n");
271             }
272 0           my @bccrecepients = split(/,/, $mail->{bcc});
273 0           foreach my $recp (@bccrecepients) {
274 0           $self->{sender}->bcc($recp . "\n");
275             }
276              
277 0           $self->{sender}->data();
278              
279             #Send header
280 0           $self->{sender}->datasend("From: " . $mail->{from} . "\n");
281 0           $self->{sender}->datasend("To: " . $mail->{to} . "\n");
282 0 0         $self->{sender}->datasend("Cc: " . $mail->{cc} . "\n") if ($mail->{cc} ne '');
283 0           $self->{sender}->datasend("Reply-To: " . $mail->{replyto} . "\n");
284 0           $self->{sender}->datasend("Subject: " . $mail->{subject} . "\n");
285 0           $self->{sender}->datasend("Date: " . email_date(). "\n");
286              
287 0 0         if($mail->{attachments} ne '')
    0          
288             {
289 0 0         print "With Attachments\n" if $verbose;
290 0           $self->{sender}->datasend("MIME-Version: 1.0\n");
291 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
292 0           $self->{sender}->datasend("Content-Type: multipart/related; BOUNDARY=\"$boundary\"\n");
293             }
294             else {
295 0           $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");
296             }
297              
298             # Send text body
299 0           $self->{sender}->datasend("\n--$boundary\n");
300 0           $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");
301              
302 0           $self->{sender}->datasend("\n");
303              
304             #################################################
305             # Chunk body in sections (Gmail SMTP limitations)
306             #my @groups_body = split(/(.{76})/,$mail->{body});
307             #$self->{sender}->datasend($_) foreach @groups_body;
308              
309             # Or better. Encode and split
310             #my $str=encode_base64($mail->{body});
311             #my @groups_body = split(/(.{76})/,$str);
312             #$self->{sender}->datasend($_) foreach @groups_body;
313              
314             # Limitation removed
315 0           $self->{sender}->datasend($mail->{body});
316             ##################################################
317              
318 0           $self->{sender}->datasend("\n\n");
319              
320 0           my @attachments=split(/,/,$mail->{attachments});
321              
322 0           foreach my $attach(@attachments)
323             {
324             #my($bytesread, $buffer, $data, $total);
325              
326 0           $attach=~s/\A[\s,\0,\t,\n,\r]*//;
327 0           $attach=~s/[\s,\0,\t,\n,\r]*\Z//;
328              
329             # Get the file name without its directory
330 0           my ($volume, $dir, $fileName) = File::Spec->splitpath($attach);
331             # Get the MIME type
332 0           my $contentType = guess_media_type($attach);
333 0 0         print "Composing MIME with attach $attach\n" if $verbose;
334              
335 0           $self->{sender}->datasend("--$boundary\n");
336 0           $self->{sender}->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
337 0           $self->{sender}->datasend("Content-Transfer-Encoding: base64\n");
338 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
339 0           $self->{sender}->datasend("Content-ID: <$fileName>\n");
340 0           $self->{sender}->datasend("Content-Disposition: inline; filename=\"$fileName\"\n\n");
341             }
342             else {
343 0           $self->{sender}->datasend("Content-Disposition: attachment; filename=\"$fileName\"\n\n");
344             }
345              
346             # Google requires us to divide the attachment
347             # First read -> Encode -> Send in chunks of 76
348             # Read
349 0           my $opened=open(my $file,'<',$attach);
350 0           binmode($file);
351             # Encode
352 0           local $/ = undef;
353 0           my $d=<$file>;
354 0           my $str=encode_base64($d);
355             # Chunks by 76
356 0           my @groups = split(/(.{76})/,$str);
357 0           $self->{sender}->datasend($_) foreach @groups;
358 0           close $file;
359              
360             #$self->{sender}->datasend("--$boundary\n"); # avoid dummy attachment
361             }
362 0           $self->{sender}->datasend("\n--$boundary--\n"); # send endboundary end message
363             }
364             elsif(defined $mail->{attachmentlist})
365             {
366 0 0         print "With Attachments\n" if $verbose;
367 0           $self->{sender}->datasend("MIME-Version: 1.0\n");
368             # $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");
369 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
370 0           $self->{sender}->datasend("Content-Type: multipart/related; BOUNDARY=\"$boundary\"\n");
371             }
372             else {
373 0           $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");
374             }
375              
376             # Send text body
377 0           $self->{sender}->datasend("\n--$boundary\n");
378 0           $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");
379              
380 0           $self->{sender}->datasend("\n");
381              
382             # Chunk body in sections (Gmail SMTP limitations)
383             #$self->{sender}->datasend($mail->{body} . "\n\n");
384 0           my @groups_body = split(/(.{76})/,$mail->{body});
385 0           $self->{sender}->datasend($_) foreach @groups_body;
386 0           $self->{sender}->datasend("\n\n");
387              
388 0           my $attachments=$mail->{attachmentlist};
389 0           foreach my $attach(@$attachments)
390             {
391             #my($bytesread, $buffer, $data, $total);
392              
393 0           $attach->{file}=~s/\A[\s,\0,\t,\n,\r]*//;
394 0           $attach->{file}=~s/[\s,\0,\t,\n,\r]*\Z//;
395              
396 0           my ($volume, $dir, $fileName) = File::Spec->splitpath($attach->{file});
397             # Get the MIME type
398 0           my $contentType = guess_media_type($attach->{file});
399 0 0         print "Composing MIME with attach $attach->{file}\n" if $verbose;
400              
401 0           $self->{sender}->datasend("--$boundary\n");
402 0           $self->{sender}->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
403 0           $self->{sender}->datasend("Content-Transfer-Encoding: base64\n");
404 0 0 0       if ((defined $properties{'-disposition'}) and ('inline' eq lc($properties{'-disposition'}))) {
405 0           $self->{sender}->datasend("Content-ID: <$fileName>\n");
406 0           $self->{sender}->datasend("Content-Disposition: inline; filename=\"$fileName\"\n\n");
407             }
408             else {
409 0           $self->{sender}->datasend("Content-Disposition: attachment; filename=\"$fileName\"\n\n");
410             }
411              
412             # Google requires us to divide the attachment
413             # First read -> Encode -> Send in chunks of 76
414             # Read
415 0           my $opened=open(my $file,'<',$attach->{file});
416 0           binmode($file);
417             # Encode
418 0           local $/ = undef;
419 0           my $d=<$file>;
420 0           my $str=encode_base64($d);
421             # Chunks by 76
422 0           my @groups = split(/(.{76})/,$str);
423 0           $self->{sender}->datasend($_) foreach @groups;
424 0           close $file;
425              
426             #$self->{sender}->datasend("--$boundary\n"); # to avoid noname.txt dummy attachment
427             }
428 0           $self->{sender}->datasend("\n--$boundary--\n"); # send endboundary end message
429             }
430             else { # No attachment
431 0 0         print "With No attachments\n" if $verbose;
432             # Send text body
433 0           $self->{sender}->datasend("MIME-Version: 1.0\n");
434 0           $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");
435              
436 0           $self->{sender}->datasend("\n");
437             # Chunk body in sections (Gmail SMTP limitations)
438             #$self->{sender}->datasend($mail->{body} . "\n\n");
439 0           my @groups_body = split(/(.{76})/,$mail->{body});
440 0           $self->{sender}->datasend($_) foreach @groups_body;
441             }
442              
443 0           $self->{sender}->datasend("\n");
444              
445 0 0         if($self->{sender}->dataend()) {
446 0 0         print "Email sent\n" if $verbose;
447 0           return 1;
448             }
449             else{
450 0           my $error_string=$self->{sender}->message();
451 0           chomp $error_string;
452 0           $self->{error}=$error_string;
453              
454 0 0         print "Sorry, there was an error during sending. Please, retry or use Debug\n" if $verbose;
455 0           return -1,$self->{error};
456             }
457              
458             }
459              
460             1;
461             __END__