File Coverage

blib/lib/Mail/SendEasy.pm
Criterion Covered Total %
statement 25 346 7.2
branch 0 154 0.0
condition 0 32 0.0
subroutine 9 23 39.1
pod 3 6 50.0
total 37 561 6.6


line stmt bran cond sub pod time code
1             #############################################################################
2             ## Name: SendEasy.pm
3             ## Purpose: Mail::SendEasy
4             ## Author: Graciliano M. P.
5             ## Modified by:
6             ## Created: 2004-01-23
7             ## RCS-ID:
8             ## Copyright: (c) 2004 Graciliano M. P.
9             ## Licence: This program is free software; you can redistribute it and/or
10             ## modify it under the same terms as Perl itself
11             #############################################################################
12            
13             package Mail::SendEasy ;
14 1     1   11242 use 5.006 ;
  1         2  
  1         40  
15            
16 1     1   6 use strict qw(vars);
  1         1  
  1         34  
17 1     1   6 no warnings ;
  1         5  
  1         40  
18            
19 1     1   5 use vars qw($VERSION @ISA) ;
  1         17  
  1         73  
20            
21             $VERSION = '1.2' ;
22            
23             ###########
24             # REQUIRE #
25             ###########
26            
27 1     1   761 use Time::Local ;
  1         1644  
  1         65  
28            
29 1     1   542 use Mail::SendEasy::SMTP ;
  1         3  
  1         58  
30 1     1   9 use Mail::SendEasy::Base64 ;
  1         2  
  1         65  
31 1     1   604 use Mail::SendEasy::IOScalar ;
  1         4  
  1         6813  
32            
33             my $ARCHZIP_PM ;
34            
35 1     1   443 eval("use Archive::Zip ()") ;
  0            
  0            
36             if ( defined &Archive::Zip::new ) { $ARCHZIP_PM = 1 ;}
37            
38             ########
39             # VARS #
40             ########
41            
42             my $RN = "\015\012" ;
43             my $ER ;
44            
45             #######
46             # NEW #
47             #######
48            
49             sub new {
50 0     0 1   my $this = shift ;
51 0 0         return( $this ) if ref($this) ;
52 0   0       my $class = $this || __PACKAGE__ ;
53 0           $this = bless({} , $class) ;
54            
55 0           my ( %args ) = @_ ;
56            
57 0 0         if ( !defined $args{smtp} ) { $args{smtp} = 'localhost' ;}
  0            
58 0 0         if ( $args{port} !~ /^\d+$/ ) { $args{port} = 25 ;}
  0            
59 0 0         if ( $args{timeout} !~ /^\d+$/ ) { $args{timeout} = 30 ;}
  0            
60            
61 0           $this->{SMTP} = Mail::SendEasy::SMTP->new( $args{smtp} , $args{port} , $args{timeout} , $args{user} , $args{pass} , 1 ) ;
62            
63 0           return $this ;
64             }
65            
66             ########
67             # SEND #
68             ########
69            
70             sub send {
71 0 0   0 1   my $this = UNIVERSAL::isa($_[0] , 'Mail::SendEasy') ? shift : undef ;
72            
73 0           my $SMTP = $this->{SMTP} ;
74            
75 0           $ER = undef ;
76            
77 0           my %mail ;
78            
79 0           while (@_) {
80 0           my $k = lc(shift @_) ;
81 0           $k =~ s/_//gs ;
82 0           $k =~ s/\W//gs ;
83 0 0         $k =~ s/s$// if $k !~ /^(?:pass)$/ ;
84 0           my $v = shift @_ ;
85 0 0 0       if ( !ref($v) && $k !~ /^(?:msg|message|html|msghtml)$/ ) {
86 0           $v =~ s/^\s+//gs ;
87 0           $v =~ s/\s+$//gs ;
88             }
89 0           $mail{$k} = $v ;
90             }
91            
92 0 0 0       if ( !defined $mail{msg} && defined $mail{message} ) { $mail{msg} = delete $mail{message} ;}
  0            
93 0 0 0       if ( !defined $mail{html} && defined $mail{msghtml} ) { $mail{html} = delete $mail{msghtml} ;}
  0            
94 0 0 0       if ( !defined $mail{anex} && defined $mail{attach} ) { $mail{anex} = delete $mail{attach} ;}
  0            
95            
96 0 0         if ( !defined $mail{from} ) { $ER = "Blank From adress!" ; return( undef ) ;}
  0            
  0            
97 0 0         if ( !defined $mail{to} ) { $ER = "Blank recipient (to)!" ; return( undef ) ;}
  0            
  0            
98            
99 0 0         if ( !$SMTP ) {
100 0 0         if ( !defined $mail{smtp} ) { $mail{smtp} = 'localhost' ;}
  0            
101 0 0         if ( $mail{port} !~ /^\d+$/ ) { $mail{port} = 25 ;}
  0            
102 0 0         if ( $mail{timeout} !~ /^\d+$/ ) { $mail{timeout} = 30 ;}
  0            
103            
104 0 0         $SMTP = Mail::SendEasy::SMTP->new($mail{smtp} , $mail{port} , $mail{timeout} , $mail{user} , $mail{pass} , 1) if !$SMTP ;
105             }
106            
107 0 0         if (!$SMTP) { return ;}
  0            
108            
109             ## Check mails ################
110             {
111 0 0         my @from = &_check_emails( $mail{from} ) ; return( undef ) if $ER ;
  0            
  0            
112 0 0         if ($#from > 0) { $ER = "More than one From: " . join(" ; ", @from) ; return( undef ) ;}
  0            
  0            
113 0           $mail{from} = @from[0] ;
114            
115 0 0         my @to = &_check_emails( $mail{to} ) ; return( undef ) if $ER ;
  0            
116 0           $mail{to} = \@to ;
117            
118 0 0         if ( defined $mail{cc} ) {
119 0 0         my @cc = &_check_emails( $mail{cc} ) ; return( undef ) if $ER ;
  0            
120 0           $mail{cc} = \@cc ;
121             }
122            
123 0 0         if ( defined $mail{reply} ) {
124 0 0         my @reply = &_check_emails( $mail{reply} ) ; return( undef ) if $ER ;
  0            
125 0 0         $mail{reply} = @reply[0] ; delete $mail{reply} if $mail{reply} eq '' ;
  0            
126             }
127            
128 0 0         if ( defined $mail{error} ) {
129 0 0         my @error = &_check_emails( $mail{error} ) ; return( undef ) if $ER ;
  0            
130 0 0         $mail{error} = @error[0] ; delete $mail{error} if $mail{error} eq '' ;
  0            
131             }
132             }
133            
134             ## ANEXS ######################
135            
136 0 0         if ( defined $mail{anex} ) {
137 0           my @anex = $mail{anex} ;
138 0 0         @anex = @{$mail{anex}} if ref($mail{anex}) eq 'ARRAY' ;
  0            
139            
140 0           foreach my $anex_i ( @anex ) {
141 0           &_to_one_line($anex_i) ;
142 0 0         if ($anex_i eq '') { next ;}
  0            
143 0           $anex_i =~ s/[\/\\]+/\//gs ;
144 0 0         if (!-e $anex_i) { $ER = "Invalid Anex: $anex_i" ; return( undef ) ;}
  0            
  0            
145 0 0         if (-d $anex_i) { $ER = "Anex is a directory: $anex_i" ; return( undef ) ;}
  0            
  0            
146 0           $anex_i =~ s/\/$// ;
147             }
148            
149 0           my @anex_part ;
150            
151 0 0 0       if ( $ARCHZIP_PM && $mail{zipanex} ) {
152 0           my ($filename , $zip_content) = &_zip_anexs($mail{zipanex},@anex) ;
153            
154 0           my %part = (
155             'Content-Type' => "application/octet-stream; name=\"$filename\"" ,
156             'Content-Transfer-Encoding' => 'base64' ,
157             'Content-Disposition' => "attachment; filename=\"$filename\"" ,
158             'content' => &encode_base64( $zip_content ) ,
159             );
160            
161 0           push(@anex_part , \%part) ;
162             }
163             else {
164 0           foreach my $anex_i ( @anex ) {
165 0           my ($filename) = ( $anex_i =~ /\/*([^\/]+)$/ );
166            
167 0           my %part = (
168             'Content-Type' => "application/octet-stream; name=\"$filename\"" ,
169             'Content-Transfer-Encoding' => 'base64' ,
170             'Content-Disposition' => "attachment; filename=\"$filename\"" ,
171             'content' => &encode_base64( &cat($anex_i) ) ,
172             );
173            
174 0           push(@anex_part , \%part) ;
175             }
176             }
177            
178 0           delete $mail{anex} ;
179 0 0         $mail{anex} = \@anex_part if @anex_part ;
180             }
181            
182             ## MIME #######################
183            
184 0           delete $mail{MIME} ;
185            
186 0           $mail{MIME}{Date} = &time_to_date() ;
187            
188 0           $mail{MIME}{From} = $mail{from} ;
189            
190 0 0         if ( $mail{fromtitle} =~ /\S/s ) {
191 0           my $title = delete $mail{fromtitle} ;
192 0           $title =~ s/[\r\n]+/ /gs ;
193 0           $title =~ s/<.*?>//gs ;
194 0           $title =~ s/^\s+//gs ;
195 0           $title =~ s/\s+$//gs ;
196 0           $title =~ s/"/'/gs ;
197 0 0         $mail{MIME}{From} = qq`"$title" <$mail{from}>` if $title ne '' ;
198             }
199            
200 0           $mail{MIME}{To} = join(" , ", @{$mail{to}} ) ;
  0            
201 0 0         $mail{MIME}{Cc} = join(" , ", @{$mail{cc}} ) if $mail{cc} ;
  0            
202            
203 0 0         $mail{MIME}{'Reply-To'} = $mail{reply} if $mail{reply} ;
204 0 0         $mail{MIME}{'Errors-To'} = $mail{error} if $mail{error} ;
205            
206 0 0         $mail{MIME}{'Subject'} = $mail{subject} if $mail{subject} ;
207            
208 0           $mail{MIME}{'Mime-version'} = '1.0' ;
209 0           $mail{MIME}{'X-Mailer'} = "Mail::SendEasy/$VERSION Perl/$]-$^O" ;
210 0           $mail{MIME}{'Msg-ID'} = $mail{msgid} ;
211            
212            
213 0 0         if ( defined $mail{msg} ) {
214 0           $mail{msg} =~ s/\r\n?/\n/gs ;
215 0 0         if ( $mail{msg} !~ /\n\n$/s) { $mail{msg} =~ s/\n?$/\n\n/s ;}
  0            
216            
217 0           my %part = (
218             'Content-Type' => 'text/plain; charset=ISO-8859-1' ,
219             'Content-Transfer-Encoding' => 'quoted-printable' ,
220             'content' => &_encode_qp( $mail{msg} ) ,
221             );
222            
223 0           push(@{$mail{MIME}{part}} , \%part ) ;
  0            
224             }
225            
226 0 0         if ( defined $mail{html} ) {
227 0           $mail{msg} =~ s/\r\n?/\n/gs ;
228            
229 0           my %part = (
230             'Content-Type' => 'text/html; charset=ISO-8859-1' ,
231             'Content-Transfer-Encoding' => 'quoted-printable' ,
232             'content' => &_encode_qp( $mail{html} ) ,
233             );
234            
235 0           push(@{$mail{MIME}{part}} , \%part ) ;
  0            
236             }
237            
238             ## Content
239             {
240 0           my $msg_part ;
  0            
241            
242             ## Alternative
243 0 0         if ( $#{ $mail{MIME}{part} } == 1 ) {
  0            
244 0           my $boudary = &_new_boundary() ;
245 0           $msg_part .= qq`Content-Type: multipart/alternative; boundary="$boudary"\n\n`;
246            
247 0           $msg_part .= "This is a multi-part message in MIME format.\n" ;
248 0           $msg_part .= "This message is in 2 versions: TXT and HTML\n" ;
249 0           $msg_part .= "You need a reader with MIME to read this message!\n\n" ;
250            
251 0           $msg_part .= &_new_part($boudary , @{$mail{MIME}{part}}[0]) ;
  0            
252 0           $msg_part .= &_new_part($boudary , @{$mail{MIME}{part}}[1]) ;
  0            
253 0           $msg_part .= qq`--$boudary--\n` ;
254 0           delete $mail{MIME}{part} ;
255             }
256 0           else { $msg_part .= &_new_part('' , @{$mail{MIME}{part}}[0]) ;}
  0            
257            
258             ## Mixed
259 0 0         if ( $mail{anex} ) {
260 0           my @anex = @{$mail{anex}} ;
  0            
261            
262 0           my $boudary = &_new_boundary() ;
263 0           $mail{MIME}{content} .= qq`Content-Type: multipart/mixed; boundary="$boudary"\n\n`;
264 0           $mail{MIME}{content} .= &_new_part($boudary , $msg_part) ;
265 0           foreach my $anex_i ( @anex ) {
266 0           $mail{MIME}{content} .= &_new_part($boudary , $anex_i) ;
267 0           $anex_i = undef ;
268             }
269 0           $mail{MIME}{content} .= qq`--$boudary--\n` ;
270            
271 0           delete $mail{anex} ;
272             }
273 0           else { $mail{MIME}{content} = $msg_part ;}
274             }
275            
276 0           $mail{MIME}{content} =~ s/\r\n?/\n/gs ;
277            
278             ## SEND #####################
279            
280 0 0 0       if ( ($SMTP->{USER} ne '' || $SMTP->{PASS} ne '') && $SMTP->auth_types ) {
      0        
281 0 0         if ( !$SMTP->auth ) { return ;}
  0            
282             }
283            
284 0 0         if ( $SMTP->MAIL("FROM:<$mail{from}>") !~ /^2/ ) { $ER = "MAIL FROM error (". $SMTP->last_response_line .")!" ; $SMTP->close ; return ;}
  0            
  0            
  0            
285            
286 0           foreach my $to ( @{$mail{to}} ) {
  0            
287 0 0         if ( $SMTP->RCPT("TO:<$to>") !~ /^2/ ) { $ER = "RCPT error (". $SMTP->last_response_line .")!" ; $SMTP->close ; return ;}
  0            
  0            
  0            
288             }
289            
290            
291 0           foreach my $to ( @{$mail{cc}} ) {
  0            
292 0 0         if ( $SMTP->RCPT("TO:<$to>") !~ /^2/ ) { $ER = "RCPT error (". $SMTP->last_response_line .")!" ; $SMTP->close ; return ;}
  0            
  0            
  0            
293             }
294            
295 0 0         if ( $SMTP->DATA =~ /^3/ ) {
296 0           &_send_MIME($SMTP , %mail) ;
297 0 0         if ( $SMTP->DATAEND !~ /^2/ ) { $ER = "Message transmission failed (". $SMTP->last_response_line .")!" ; $SMTP->close ; return ;}
  0            
  0            
  0            
298             }
299 0           else { $ER = "Can't send data (". $SMTP->last_response_line .")!" ; $SMTP->close ; return ;}
  0            
  0            
300            
301 0           $SMTP->close ;
302 0           return 1 ;
303             }
304            
305             ##############
306             # _SEND_MIME #
307             ##############
308            
309             sub _send_MIME {
310 0     0     my ( $SMTP , %mail ) = @_ ;
311            
312 0           my @order = qw(
313             Date
314             From
315             To
316             Cc
317             Reply-To
318             Errors-To
319             Subject
320             Msg-ID
321             X-Mailer
322             Mime-version
323             );
324            
325 0           foreach my $order_i ( @order ) {
326 0 0         if ( !defined $mail{MIME}{$order_i} ) { next ;}
  0            
327 0           $SMTP->print("$order_i: " . $mail{MIME}{$order_i} . $RN) ;
328             }
329            
330 0           $mail{MIME}{content} =~ s/\n/$RN/gs ;
331 0           $SMTP->print($mail{MIME}{content}) ;
332             }
333            
334             #############
335             # _NEW_PART #
336             #############
337            
338             sub _new_part {
339 0     0     my ( $boudary , $part ) = @_ ;
340 0           my $new_part ;
341            
342 0 0         if ( !ref($part) ) {
343 0 0         $new_part .= "--$boudary\n" if $boudary ;
344 0           $new_part .= $part ;
345 0 0         $new_part .= "\n" if $boudary ;
346 0           return( $new_part ) ;
347             }
348            
349 0           my @order = qw(
350             Content-Type
351             Content-Transfer-Encoding
352             Content-Disposition
353             );
354            
355 0 0         $new_part .= "--$boudary\n" if $boudary ;
356            
357 0           foreach my $order_i ( @order ) {
358 0 0         if ( !defined $$part{$order_i} ) { next ;}
  0            
359 0           my $val = $$part{$order_i} ;
360 0           $new_part .= "$order_i: $val\n" ;
361             }
362            
363 0           $new_part .= "\n" ;
364 0           $new_part .= $$part{content} ;
365 0 0         $new_part .= "\n" if $boudary ;
366            
367 0           return( $new_part ) ;
368             }
369            
370             #################
371             # _NEW_BOUNDARY #
372             #################
373            
374             sub _new_boundary {
375 0     0     push my @lyb1,(qw(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) ) ;
376 0           push my @lyb2,(qw(0 1 2 3 4 5 6 7 8 9) ) ;
377            
378 0           my $boudary = "--=_Mail_SendEasy_" ;
379 0           while( length($boudary) < 25 ) { $boudary .= @lyb1[rand(@lyb1)] ;}
  0            
380 0           $boudary .= '_' ;
381 0           while( length($boudary) < 31 ) { $boudary .= @lyb2[rand(@lyb2)] ;}
  0            
382 0           $boudary .= '_' ;
383 0           $boudary .= time() ;
384            
385 0           return( $boudary ) ;
386             }
387            
388             ##############
389             # _ENCODE_QP # From MIME::QuotedPrint
390             ##############
391            
392             sub _encode_qp {
393 0     0     my $res = shift;
394            
395 0           $res =~ s/^\./\.\./gom ;
396 0           $res =~ s/\r\n?/\n/gs ;
397            
398 0           $res =~ s/([^ \t\n!<>~-])/sprintf("=%02X", ord($1))/eg ;
  0            
399            
400 0           $res =~ s/([ \t]+)$/ join('', map { sprintf("=%02X", ord($_)) } split('', $1) )/egm ;
  0            
  0            
401            
402 0           my $brokenlines = "" ;
403 0           $brokenlines .= "$1=\n" while $res =~ s/(.*?^[^\n]{73} (?:
404             [^=\n]{2} (?! [^=\n]{0,1} $) # 75 not followed by .?\n
405             |[^=\n] (?! [^=\n]{0,2} $) # 74 not followed by .?.?\n
406             | (?! [^=\n]{0,3} $) # 73 not followed by .?.?.?\n
407             ))//xsm ;
408            
409 0           return "$brokenlines$res" ;
410             }
411            
412             ################
413             # _TO_ONE_LINE #
414             ################
415            
416             sub _to_one_line {
417 0     0     $_[0] =~ s/[\r\n]+/ /gs ;
418 0           $_[0] =~ s/^\s+//gs ;
419 0           $_[0] =~ s/\s+$//gs ;
420             }
421            
422             #################
423             # _CHECK_EMAILS #
424             #################
425            
426             sub _check_emails {
427 0     0     my @mails = split(/\s*(?:[;:,]+|\s+)\s*/s , $_[0]) ;
428 0 0         @mails = @{$_[0]} if ref($_[0]) eq 'ARRAY' ;
  0            
429            
430 0           foreach my $mails_i ( @mails ) {
431 0           &_to_one_line($mails_i) ;
432 0 0         if ($mails_i eq '') { next ;}
  0            
433 0 0         if (! &_format($mails_i) ) { $ER = "Invalid recipient: $mails_i" ; return( undef ) ;}
  0            
  0            
434             }
435 0           return( @mails ) ;
436             }
437            
438             ###########
439             # _FORMAT #
440             ###########
441            
442             sub _format {
443 0 0   0     if ( $_[0] eq '' ) { return( undef ) ;}
  0            
444            
445 0           my ( $mail ) = @_ ;
446            
447 0           my $stat = 1 ;
448            
449 0 0         if ($mail !~ /^[\w\.-]+\@localhost$/gsi) {
    0          
450 0 0         if ($mail !~ /^[\w\.-]+\@(?:[\w-]+\.)*?(?:\w+(?:-\w+)*)(?:\.\w+)+$/ ) { $stat = undef ;}
  0            
451             }
452 0           elsif ($mail !~ /^[\w\.-]+\@[\w-]+$/ ) { $stat = undef ;}
453            
454 0 0         return 1 if $stat ;
455 0           return undef ;
456             }
457            
458             ################
459             # TIME_TO_DATE #
460             ################
461            
462             sub time_to_date {
463             # convert a time() value to a date-time string according to RFC 822
464 0   0 0 0   my $time = $_[0] || time();
465            
466 0           my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
467 0           my @wdays = qw(Sun Mon Tue Wed Thu Fri Sat);
468            
469 0           my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time) ;
470            
471 0           my $TZ ;
472            
473 0 0         if ( $TZ eq "" ) {
474             # offset in hours
475 0           my $offset = sprintf "%.1f", (timegm(localtime) - time) / 3600;
476 0           my $minutes = sprintf "%02d", ( $offset - int($offset) ) * 60;
477 0           $TZ = sprintf("%+03d", int($offset)) . $minutes;
478             }
479            
480 0           return join(" ",
481             ($wdays[$wday] . ','),
482             $mday,
483             $months[$mon],
484             $year+1900,
485             sprintf("%02d", $hour) . ":" . sprintf("%02d", $min),
486             $TZ
487             );
488             }
489            
490             #######
491             # CAT #
492             #######
493            
494             sub cat {
495 0     0 0   my ( $file ) = @_ ;
496 0 0         if (ref($file) eq 'SCALAR') { $file = ${$file} ;}
  0            
  0            
497            
498 0           my $fh = $file ;
499 0 0         if (ref($fh) ne 'GLOB') { open($fh,$file) ; binmode($fh) ;}
  0            
  0            
500            
501 0 0 0       if ( *{$fh}->{DATA} && *{$fh}->{content} ne '' ) { return( *{$fh}->{content} ) ;}
  0            
  0            
  0            
  0            
502            
503 0           my $data ;
504 0 0         seek($fh,0,1) if ! *{$fh}->{DATA} ;
  0            
505 0           1 while( read($fh, $data , 1024*8*2 , length($data) ) ) ;
506 0           close($fh) ;
507            
508 0           return( $data ) ;
509             }
510            
511             #########
512             # ERROR #
513             #########
514            
515 0     0 1   sub error { return( $ER ) ;}
516            
517             ########
518             # WARN #
519             ########
520            
521             sub warn {
522 0 0   0 0   my $this = UNIVERSAL::isa($_[0] , 'Mail::SendEasy') ? shift : undef ;
523 0           $ER = $_[0] ;
524             }
525            
526             ##############
527             # _ZIP_ANEXS #
528             ##############
529            
530             sub _zip_anexs {
531 0     0     my $zip_name = shift ;
532 0           my $def_name ;
533 0 0         if ($zip_name !~ /\.zip$/i) { $zip_name = 'anex.zip' ; $def_name = 1 ;}
  0            
  0            
534            
535 0           my $zip_content ;
536 0           my $IO = Mail::SendEasy::IOScalar->new(\$zip_content) ;
537            
538 0           my $zip = Archive::Zip->new() ;
539            
540 0           my $anex1 ;
541 0           foreach my $anex_i ( @_ ) {
542 0           my ($filename) = ( $anex_i =~ /\/*([^\/]+)$/ ) ;
543 0           $anex1 = $filename ;
544 0           $zip->addFile($anex_i , $filename) ;
545             }
546            
547 0           my $status = $zip->writeToFileHandle($IO) ;
548            
549 0 0 0       if ($def_name && $#_ == 0) { $zip_name = $anex1 ;}
  0            
550            
551 0           $zip_name =~ s/\s+/_/gs ;
552 0           $zip_name =~ s/^\.+// ;
553 0           $zip_name =~ s/\.\.+/\./ ;
554 0           $zip_name =~ s/\.[^\.]+$// ;
555 0           $zip_name .= ".zip" ;
556            
557 0           return( $zip_name , $zip_content ) ;
558             }
559            
560             #######
561             # END #
562             #######
563            
564             1;
565            
566            
567             __END__