File Coverage

blib/lib/Mail/DWIM.pm
Criterion Covered Total %
statement 125 206 60.6
branch 32 72 44.4
condition 7 12 58.3
subroutine 24 30 80.0
pod 0 20 0.0
total 188 340 55.2


line stmt bran cond sub pod time code
1             ###########################################
2             package Mail::DWIM;
3             ###########################################
4              
5 2     2   55425 use strict;
  2         6  
  2         92  
6 2     2   14 use warnings;
  2         4  
  2         220  
7             our @ISA = qw(Exporter);
8             our @EXPORT_OK = qw(mail);
9             our $VERSION = "0.07";
10             our @HTML_MODULES = qw(HTML::FormatText HTML::TreeBuilder MIME::Lite);
11             our @ATTACH_MODULES = qw(File::MMagic MIME::Lite);
12              
13 2     2   1886 use YAML qw(LoadFile);
  2         20879  
  2         125  
14 2     2   4185 use Log::Log4perl qw(:easy);
  2         143420  
  2         16  
15 2     2   1073 use Config;
  2         3  
  2         94  
16 2     2   1918 use Mail::Mailer;
  2         48181  
  2         13  
17 2     2   76 use Sys::Hostname;
  2         5  
  2         110  
18 2     2   13 use File::Basename;
  2         3  
  2         121  
19 2     2   9 use POSIX qw(strftime);
  2         5  
  2         8  
20 2     2   103 use File::Spec;
  2         3  
  2         4696  
21              
22             my $error;
23              
24             ###########################################
25             sub mail {
26             ###########################################
27 2     2 0 1255 my(@params) = @_;
28              
29 2         13 my $mailer = Mail::DWIM->new(@params);
30 2         8 $mailer->send();
31             }
32              
33             ###########################################
34             sub new {
35             ###########################################
36 7     7 0 1138 my($class, %options) = @_;
37              
38 7         241 my($homedir) = glob "~";
39              
40 7         15 my %defaults;
41              
42 7         58 my $self = {
43             global_cfg_file => "/etc/maildwim",
44             user_cfg_file => "$homedir/.maildwim",
45             transport => "sendmail",
46             raise_error => 1,
47             %options,
48             };
49              
50             # Guess the 'from' address
51 7 100       30 if(! exists $self->{from}) {
52 4         1782 my $user = scalar getpwuid($<);
53 4         20 my $domain = domain();
54 4         17 $self->{from} = "$user\@$domain";
55             }
56              
57             # Guess the 'date'
58 7 50       23 if (!exists $self->{date}) {
59 7         774 $self->{date} = strftime("%a, %e %b %Y %H:%M:%S %Z", localtime(time));
60             }
61              
62 7         20 for my $cfg (qw(global_cfg_file user_cfg_file)) {
63 14 100       282 if(-f $self->{$cfg}) {
64 8         68 my $yml = LoadFile( $self->{$cfg} );
65 8 50 66     32385 if(defined $yml and ref $yml ne 'HASH') {
66             # Needs to be a hash, but YAML file can be empty (undef)
67 0         0 LOGDIE "YAML file $self->{$cfg} format not a hash";
68             }
69             # merge with existing hash
70 8 100       53 %defaults = (%defaults, %$yml) if defined $yml;
71             }
72             }
73              
74 7         77 %$self = (%$self, %defaults, %options);
75              
76 7         46 bless $self, $class;
77             }
78              
79             ###########################################
80             sub cmd_line_mail {
81             ###########################################
82 0     0 0 0 my($self) = @_;
83              
84 0 0       0 $self->{subject} = 'no subject' unless defined $self->{subject};
85              
86 0         0 my $mailer;
87 0 0       0 $mailer = $self->{program} if defined $self->{program};
88 0 0       0 $mailer = bin_find("mail") unless defined $mailer;
89              
90 0 0       0 open(PIPE, "|-", $mailer,
91             "-s", $self->{subject}, $self->{to},
92             ) or LOGDIE "Opening $mailer failed: $!";
93              
94 0         0 print PIPE $self->{text};
95 0         0 return close PIPE;
96             }
97              
98             ###########################################
99             sub send {
100             ###########################################
101 4     4 0 17 my($self, $evaled) = @_;
102              
103 4 100 66     25 if(!$self->{raise_error} && ! $evaled) {
104 1         6 return $self->send_evaled();
105             }
106              
107 3         23 my $msg =
108             "Sending from=$self->{from} to=$self->{to} " .
109             "subj=" . snip($self->{subject}, 20) . " " .
110             "text=" . snip($self->{text}, 20) .
111             "";
112              
113 3         7 my @options = ();
114              
115 3 100       16 if(0) {
    50          
    0          
116 0         0 } elsif($self->{transport} eq "sendmail") {
117 2         5 @options = ();
118             } elsif($self->{transport} eq "smtp") {
119             # Mail::SMTP likes it that way
120 1         7 $ENV{ MAILADDRESS } = $self->{ from };
121              
122 1 50       12 LOGDIE "No smtp_server set" unless defined $self->{smtp_server};
123 0         0 @options = ("smtp", Server => $self->{smtp_server});
124 0 0       0 push @options, (Port => $self->{smtp_port})
125             if exists $self->{smtp_port};
126 0         0 $self->{to} = [split /\s*,\s*/, $self->{to}];
127             } elsif($self->{transport} eq "mail") {
128 0         0 return $self->cmd_line_mail();
129             } else {
130 0         0 LOGDIE "Unknown transport '$self->{transport}'";
131             }
132              
133 2         20 my $mailer = Mail::Mailer->new(@options);
134 2         9505 my %headers;
135 2         7 for (qw(from to cc bcc subject date)) {
136 12 100       49 $headers{ucfirst($_)} = $self->{$_} if exists $self->{$_};
137             }
138              
139 2         7 my $text = $self->{text};
140 2 50       9 if($self->{html_compat}) {
141 0         0 my $h;
142 0         0 ($h, $text) = html_msg($text);
143 0         0 %headers = (%headers, %$h);
144             }
145              
146 2 50       9 if($self->{attach}) {
147 0         0 my $h;
148 0         0 ($h, $text) = attach_msg($text, @{$self->{attach}});
  0         0  
149 0         0 %headers = (%headers, %$h);
150             }
151              
152 2 50       10 if($ENV{MAIL_DWIM_TEST}) {
153 2         17 DEBUG "Appending to test file $ENV{MAIL_DWIM_TEST}";
154 2         27 my $txt;
155 2         10 for (keys %headers) {
156 8 50       30 $txt .= "$_: $headers{$_}\n" if defined $headers{$_};
157             }
158 2         6 $txt .= "\n";
159              
160 2         11 test_file_append($txt . $text);
161 2         23 return 1;
162             } else {
163 0         0 DEBUG $msg;
164             }
165              
166 0         0 $mailer->open(\%headers);
167 0         0 print $mailer $text;
168 0         0 $mailer->close();
169             }
170              
171             ###########################################
172             sub send_evaled {
173             ###########################################
174 1     1 0 2 my($self) = @_;
175              
176 1         2 eval {
177 1         9 return $self->send(1);
178             };
179              
180 1 50       72 if($@) {
181 1         4 error($@);
182 1         15 return undef;
183             }
184             }
185              
186             ###########################################
187             sub error {
188             ###########################################
189 2     2 0 563 my($text) = @_;
190              
191 2 100       7 if(defined $text) {
192 1         1 $error = $text;
193             }
194              
195 2         10 return $error;
196             }
197              
198             ###########################################
199             sub test_file_append {
200             ###########################################
201 2     2 0 4 my($msg) = @_;
202              
203 2 50       92 open FILE, ">>$ENV{MAIL_DWIM_TEST}" or
204             LOGDIE "Cannot open $ENV{MAIL_DWIM_TEST} ($!)";
205 2         22 print FILE $msg, "\n\n";
206 2         142 close FILE;
207             }
208              
209             ###########################################
210             sub html_requirements {
211             ###########################################
212              
213 1     1 0 8 for (@HTML_MODULES) {
214 1         56 eval "require $_";
215 1 50       6 if($@) {
216 1         2 return undef;
217             }
218             }
219              
220 0         0 1;
221             }
222              
223             ###########################################
224             sub attach_requirements {
225             ###########################################
226              
227 1     1 0 7 for (@ATTACH_MODULES) {
228 1         54 eval "require $_";
229 1 50       11 if($@) {
230 1         3 return undef;
231             }
232             }
233              
234 0         0 1;
235             }
236              
237             ###########################################
238             sub html_msg {
239             ###########################################
240 0     0 0 0 my($htmltext) = @_;
241              
242 0 0       0 if(! html_requirements()) {
243 0         0 LOGDIE "Please install ",
244             join(" ", @HTML_MODULES), " from CPAN";
245             }
246              
247 0         0 my $tree = HTML::TreeBuilder->new();
248 0         0 $tree->parse($htmltext);
249 0         0 $tree->eof();
250 0         0 my $formatter = HTML::FormatText->new();
251 0         0 my $plaintext = $formatter->format($tree);
252              
253 0         0 my $msg = MIME::Lite->new(
254             Type => 'multipart/alternative',
255             );
256              
257 0         0 $msg->attach(
258             Type => 'text/plain',
259             Data => $plaintext
260             );
261              
262 0         0 $msg->attach(
263             Type => 'text/html',
264             Data => $htmltext,
265             );
266              
267 0         0 my %headers;
268              
269 0         0 for (qw(Content-Transfer-Encoding Content-Type
270             MIME-version)) {
271 0         0 $headers{$_} = $msg->attr($_);
272             }
273              
274 0         0 return \%headers, $msg->body_as_string;
275             }
276              
277             ###########################################
278             sub attach_msg {
279             ###########################################
280 0     0 0 0 my($text, @files) = @_;
281              
282 0 0       0 if(! attach_requirements()) {
283 0         0 LOGDIE "Please install ",
284             join(" ", @ATTACH_MODULES), " from CPAN";
285             }
286              
287 0         0 my $msg = MIME::Lite->new(
288             Type => "multipart/mixed"
289             );
290              
291 0         0 $msg->attach(Type => "TEXT",
292             Data => $text,
293             );
294              
295 0         0 for my $file (@files) {
296 0         0 my $mm = File::MMagic->new();
297 0         0 my $type = $mm->checktype_filename($file);
298 0 0       0 LOGDIE "Cannot determine mime type of $file" unless defined $type;
299              
300 0         0 $msg->attach(Type => $type,
301             Path => $file,
302             Filename => basename($file),
303             Disposition => "attachment",
304             );
305             }
306              
307 0         0 my $headers = mime_lite_headers($msg);
308              
309 0         0 return $headers, $msg->body_as_string;
310             }
311              
312             ###########################################
313             sub mime_lite_headers {
314             ###########################################
315 0     0 0 0 my($mlite) = @_;
316              
317 0         0 my %wanted = map { lc($_) => $_ }
  0         0  
318             qw(Content-Transfer-Encoding Content-Type
319             MIME-version);
320 0         0 my %headers = ();
321              
322 0         0 for my $field (@{$mlite->fields}) {
  0         0  
323 0 0       0 if(exists $wanted{$field->[0]}) {
324 0         0 my($name, $value) = split /:\s*/,
325             $mlite->fields_as_string([$field]), 2;
326 0         0 $headers{$name} = $value;
327             }
328             }
329              
330 0         0 return(\%headers);
331             }
332              
333             ###########################################
334             sub header_ucfirst {
335             ###########################################
336 0     0 0 0 my($name) = @_;
337              
338 0         0 $name =~ s/^(\w)/uc($1)/g;
339 0         0 $name =~ s/-(\w)/uc($1)/g;
340              
341 0         0 return $name;
342             }
343              
344             ###########################################
345             sub snip {
346             ###########################################
347 6     6 0 33 my($data, $maxlen) = @_;
348              
349 6 100       15 if(length $data <= $maxlen) {
350 4         9 return lenformat($data);
351             }
352              
353 2 50       5 $maxlen = 12 if $maxlen < 12;
354 2         5 my $sniplen = int(($maxlen - 8) / 2);
355              
356 2         4 my $start = substr($data, 0, $sniplen);
357 2         26 my $end = substr($data, -$sniplen);
358 2         7 my $snipped = length($data) - 2*$sniplen;
359              
360 2         8 return lenformat("$start\[...]$end", length $data);
361             }
362              
363             ###########################################
364             sub lenformat {
365             ###########################################
366 6     6 0 8 my($data, $orglen) = @_;
367              
368 6   66     31 return "(" . ($orglen || length($data)) . ")[" .
369             printable($data) . "]";
370             }
371              
372             ###########################################
373             sub printable {
374             ###########################################
375 6     6 0 7 my($data) = @_;
376              
377 6         170 $data =~ s/[^ \w.;!?@#$%^&*()+\\|~`'-,><[\]{}="]/./g;
378 6         35 return $data;
379             }
380              
381             ###########################################
382             sub blurt {
383             ###########################################
384 8     8 0 5517 my($data, $file) = @_;
385              
386 8 50       601 open FILE, ">$file" or die "Cannot open $file";
387 8         44 print FILE $data;
388 8         179 close FILE;
389             }
390              
391             ###########################################
392             sub slurp {
393             ###########################################
394 2     2 0 27 my($file) = @_;
395              
396 2         8 local($/);
397 2         4 $/ = undef;
398              
399 2 50       59 open FILE, "<$file" or die "Cannot open $file";
400 2         34 my $data = ;
401 2         27 close FILE;
402 2         11 return $data;
403             }
404              
405             ###########################################
406             sub domain {
407             ###########################################
408              
409 4     4 0 2338 my $domain = $Config{mydomain};
410              
411 4 50 33     14397 if(defined $domain and length($domain)) {
412 4         18 $domain =~ s/^\.//;
413 4         18 return $domain;
414             }
415              
416 0           eval { require Sys::Hostname; };
  0            
417 0 0         if(! $@) {
418 0           $domain = hostname();
419 0           return $domain;
420             }
421              
422 0           $domain = "localhost";
423              
424 0           return $domain;
425             }
426              
427             ######################################
428             sub bin_find {
429             ######################################
430 0     0 0   my($exe) = @_;
431              
432 0           for my $path (split /:/, $ENV{PATH}) {
433 0           my $full = File::Spec->catfile($path, $exe);
434              
435 0 0         return $full if -x $full;
436             }
437              
438 0           return undef;
439             }
440              
441             1;
442              
443             __END__