File Coverage

blib/lib/Mail/Sendmail/Enhanced.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Mail::Sendmail::Enhanced;
2              
3 1     1   36831 use 5.008;
  1         5  
  1         60  
4              
5 1     1   7 use strict;
  1         2  
  1         46  
6 1     1   7 use warnings;
  1         7  
  1         53  
7              
8 1     1   1011 use utf8;
  1         12  
  1         5  
9 1     1   840 use Mail::Sendmail '0.79_16';
  1         21987  
  1         249  
10             use MIME::Base64;
11              
12             our $VERSION = '0.01';
13              
14             ################################################################################
15              
16             sub new
17             {
18             my ( $this ) = ( shift );
19              
20             my $mail = {};
21             bless ( $mail, $this );
22              
23             while ( my $key = shift ) {
24             if ( ref ( $key ) eq 'HASH' ) {
25             foreach my $k (sort keys %{$key} ) {
26             $mail->{$k} = $$key{$k};
27             }
28             } else {
29             my $value = shift;
30             $mail->{$key} = $value;
31             }
32             }
33              
34             $mail->{smtp} ||= '';
35             $mail->{from} ||= '';
36             $mail->{charset} ||= 'utf-8';
37             $mail->{type} ||= 'text/plain';
38              
39             $mail->{user} ||= '';
40             $mail->{pass} ||= '';
41             $mail->{method} ||= 'LOGIN';
42             $mail->{required} ||= 1;
43              
44             $mail->{to} ||= '';
45             $mail->{cc} ||= '';
46             $mail->{subject} ||= 'No subject defined';
47             $mail->{message} ||= 'No message defined!';
48              
49             $mail->{attachments}||= {};
50              
51             return $mail;
52             }
53              
54             ################################################################################
55              
56             sub send
57             {
58             my ( $self, $ARG ) = ( shift, shift );
59              
60             $ARG->{to} || $ARG->{cc} || die 'No to: or cc: email address given!';
61              
62             my $charset = $ARG->{'charset'} || $self-> {charset} || 'utf-8';
63             my $type = $ARG->{'type'} || $self-> {type} || 'text/plain';
64              
65             my $boundary = "====" . time() . "====";
66              
67             # Email subject is encoded using proper character encoding.
68             # original "encode_qp" function contains up to 2 arguments,
69             # but in a case of character set it is needed to start every
70             # new line with a statemant of the encoding, so - as a the
71             # third parameter - the charset is sent to the function.
72              
73             my $subject = $self->encode_qp( $ARG->{subject} || $self->{subject} || '' , "?=\n", "=?$charset?Q?" );
74              
75             my %mail = (
76             'X-Mailer' => "This is Perl Mail::Sendmail::Enhanced version $Mail::Sendmail::Enhanced::VERSION",
77             'Content-Type' => "multipart/mixed; charset=$charset; boundary=\"$boundary\"",
78             'Smtp' => ($ARG->{smpt}|| $self->{smtp} ),
79             'From' => ($ARG->{from}|| $self->{from} ),
80             'To' => ($ARG->{to} || $self->{to} || '' ),
81             'Cc' => ($ARG->{cc} || $self->{cc} || '' ),
82             'Subject' => $subject,
83             auth => {
84             user => ($ARG->{user} || $self->{user} ),
85             pass => ($ARG->{pass} || $self->{pass} ),
86             method => ($ARG->{method} || $self->{method} ),
87             required=> ($ARG->{required}|| $self->{required} ),
88             },
89             );
90              
91             $boundary = '--'.$boundary;
92             $mail{'Message'} = "$boundary\n"
93             ."Content-Type: $type; charset=$charset\n"
94             ."Content-Transfer-Encoding: quoted-printable\n\n"
95             .$self->encode_qp( $ARG->{'message'} , '', '' )."\n";
96              
97             $ARG->{'attachments'} ||= $self-> {attachments};
98              
99             # attachment files are packed one by one into the message part each divided by boundary
100              
101             foreach my $fileName ( sort keys %{$ARG->{'attachments'}} ) {
102             my $fileLocation = $ARG->{'attachments'}->{$fileName};
103             if (open (my $F, $fileLocation )) {
104             my $input_record_separator = $/;
105             binmode $F; undef $/;
106             my $attachment = encode_base64(<$F>);
107             close $F;
108             $/ = $input_record_separator;
109              
110             $mail{'Message'} .= "$boundary\n"
111             ."Content-Type: application/octet-stream; name=\"$fileName\"\n"
112             ."Content-ID: <$fileName>\n"
113             ."Content-Transfer-Encoding: base64\n"
114             ."Content-Disposition: attachment; filename=\"$fileName\"\n\n"
115             ."$attachment\n";
116             }
117             }
118              
119             $mail{'Message'} .= "$boundary--\n";
120              
121             return $Mail::Sendmail::error unless sendmail( %mail );
122              
123             return;
124             }
125              
126             ################################################################################
127              
128             sub encode_qp
129             {
130             ##########################################################################
131             # This function is an exact copy of the that of the same name
132             # from the module: MIME::QuotedPrint::Perl '1.00'" however with
133             # the following changes:
134             # 1. number of arguments increases to 3 insted of 2 previously.
135             # 2. The third argument represents the begining of each encoded lines
136             # which contains character set (requirement by mail subject field.
137             # 3. There are some changes in counting character in each line.
138             # Because of requirements of the specification the first line -
139             # because of the key word "Subject:" line may contain only 65 not
140             # 73 characters.
141             # The behaviour of the function is identical with the original one
142             # in case we send two arguments only (the third is undefined)
143             ##########################################################################
144              
145             my ( $self, $res, $eol, $bol ) = ( shift, shift, shift, shift );
146              
147             # $self= mail object
148             # $res = encoded text
149             # $eol = characters at the end of each encoded line
150             # $bol = characters at the begining of each encoded line
151              
152              
153             if ($] >= 5.006) {
154             require bytes;
155             if (bytes::length($res) > length($res) || ($] >= 5.008 && $res =~ /[^\0-\xFF]/))
156             {
157             require Carp;
158             Carp::croak("The Quoted-Printable encoding is only defined for bytes");
159             }
160             }
161              
162             $eol = "\n" unless defined $eol;
163             my $mid = '';
164             unless ( defined $bol ) { $mid = '='; $bol = '' }
165              
166             my $RE_Z = "\\z";
167             $RE_Z = "\$" if $] < 5.005;
168              
169             # Do not mention ranges such as $res =~ s/([^ \t\n!-<>-~])/sprintf("=%02X", ord($1))/eg;
170             # since that will not even compile on an EBCDIC machine (where ord('!') > ord('<')).
171             if (ord('A') == 193) { # EBCDIC style machine
172             if (ord('[') == 173) {
173             $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
174             $res =~ s/([ \t]+)$/
175             join('', map { sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('cp1047',$_)))) }
176             split('', $1)
177             )/egm; # rule #3 (encode whitespace at eol)
178             }
179             elsif (ord('[') == 187) {
180             $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
181             $res =~ s/([ \t]+)$/
182             join('', map { sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('posix-bc',$_)))) }
183             split('', $1)
184             )/egm; # rule #3 (encode whitespace at eol)
185             }
186             elsif (ord('[') == 186) {
187             $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
188             $res =~ s/([ \t]+)$/
189             join('', map { sprintf("=%02X", ord(Encode::encode('iso-8859-1',Encode::decode('cp37',$_)))) }
190             split('', $1)
191             )/egm; # rule #3 (encode whitespace at eol)
192             }
193             }
194             else { # ASCII style machine
195             $res =~ s/([^ \t\n!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~])/sprintf("=%02X", ord($1))/eg; # rule #2,#3
196             $res =~ s/\n/=0A/g unless length($eol);
197             $res =~ s/([ \t]+)$/
198             join('', map { sprintf("=%02X", ord($_)) }
199             split('', $1)
200             )/egm; # rule #3 (encode whitespace at eol)
201             }
202              
203             return $res unless length($eol);
204              
205             # rule #5 (lines must be shorter than 76 chars, but we are not allowed
206             # to break =XX escapes. This makes things complicated :-( )
207             my $brokenlines = "";
208             my $noc = 65; #number of characters in the first final line (becouse of the letters "Subject:"
209             $brokenlines .= " $bol$1$mid$eol"
210             while $res =~ s/(.*?^[^\n]{$noc} (?:
211             [^=\n]{2} (?! [^=\n]{0,1} $) # 75 not followed by .?\n
212             |[^=\n] (?! [^=\n]{0,2} $) # 74 not followed by .?.?\n
213             | (?! [^=\n]{0,3} $) # 73 not followed by .?.?.?\n
214             ))//xsm;
215             $res =~ s/\n$RE_Z/$eol/o;
216              
217             $brokenlines .= " $bol$res$eol" if $res;
218             $brokenlines =~ s/^ //;
219             $brokenlines;
220             }
221              
222             ################################################################################
223              
224             1;
225              
226             ################################################################################
227              
228             =pod
229              
230             =head1 NAME
231              
232             Mail::Sendmail::Enhanced - This is enhanced version of the L
233             module with encoding and attachments added.
234              
235             =head1 SYNOPSIS
236              
237             #!/usr/bin/perl -w
238              
239             use strict;
240             use warnings;
241              
242             use Mail::Sendmail::Enhanced;
243              
244             my $MAIL = Mail::Sendmail::Enhanced-> new(
245             charset => 'UTF-8',
246             smtp => 'Your SMTP server',
247             from => 'Your mail',
248             user => 'user',
249             pass => 'password',
250             method => 'LOGIN',
251             required => 1,
252             attachments => {
253             'name for email of the file1' => 'OS file1 location',
254             'name for email of the file2' => 'OS file2 location',
255             },
256             );
257              
258             for (1..2) {
259             print $MAIL-> send( {
260             to => 'author of the module: ',
261             subject => "Subject longer than 80 characters with Polish letters: lowercase: ąćęłńóśźż and uppercase: ĄĆĘŁŃÓŚŹŻ.",
262             message => "This is the message nr $_. in the character encoding UTF-8.
263              
264             This is an example of using UTF-8 Polish letters in an email subject field: encoded and longer than 80 characters.",
265              
266             __END__
267              
268             =head1 DESCRIPTION
269              
270             This module is enhanced version of the module L.
271             It preserved the nicest feature of the original modules:
272             the pure Perl solution method.
273              
274             From L:
275              
276             "Simple platform independent e-mail from your perl script. Only
277             requires Perl 5 and a network connection. Mail::Sendmail takes a hash
278             with the message to send and sends it to your mail server. It is
279             intended to be very easy to setup and use."
280              
281             In L two things were added:
282              
283             1. Encoding - which uses the refurbish function B from the
284             module L which is put into the current one.
285             This is pure Perl solution.
286              
287             2. Attachments - which allows to add attachments easily. It makes it
288             by using the technique connected with "multipart/mixed" and "boundary"
289             'Content-Type' attribute.
290              
291             List of files to send (attachments) is given as a simple hash:
292              
293             attachments => {
294             'name for email of the file1' => 'OS file1 location',
295             'name for email of the file2' => 'OS file2 location',
296             },
297              
298             where the keys of the hash are "public" (in email) names of files and
299             values of the hash are these files OS locations, respectively.
300              
301             =head1 AUTHOR
302              
303             Waldemar Biernacki, C<< >>
304              
305             =head1 BUGS
306              
307             Please report any bugs or feature requests to
308             C, or through the web interface at
309             L.
310             I will be notified, and then you'll automatically be notified of progress on your
311             bug as I make changes.
312              
313             =head1 LICENSE AND COPYRIGHT
314              
315             Copyright 2015 Waldemar Biernacki.
316              
317             This program is free software; you can redistribute it and/or modify it
318             under the terms of the the Artistic License (2.0). You may obtain a
319             copy of the full license at:
320              
321             L
322              
323             Any use, modification, and distribution of the Standard or Modified
324             Versions is governed by this Artistic License. By using, modifying or
325             distributing the Package, you accept this license. Do not use, modify,
326             or distribute the Package, if you do not accept this license.
327              
328             If your Modified Version has been derived from a Modified Version made
329             by someone other than you, you are nevertheless required to ensure that
330             your Modified Version complies with the requirements of this license.
331              
332             This license does not grant you the right to use any trademark, service
333             mark, tradename, or logo of the Copyright Holder.
334              
335             This license includes the non-exclusive, worldwide, free-of-charge
336             patent license to make, have made, use, offer to sell, sell, import and
337             otherwise transfer the Package with respect to any patent claims
338             licensable by the Copyright Holder that are necessarily infringed by the
339             Package. If you institute patent litigation (including a cross-claim or
340             counterclaim) against any party alleging that the Package constitutes
341             direct or contributory patent infringement, then this Artistic License
342             to you shall terminate on the date that such litigation is filed.
343              
344             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
345             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
346             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
347             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
348             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
349             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
350             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
351             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
352              
353             =head1 SEE ALSO
354              
355             L, L
356              
357             =cut