File Coverage

blib/lib/Mail/Internet.pm
Criterion Covered Total %
statement 56 300 18.6
branch 17 150 11.3
condition 6 69 8.7
subroutine 14 38 36.8
pod 28 32 87.5
total 121 589 20.5


line stmt bran cond sub pod time code
1             # Copyrights 1995-2017 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             package Mail::Internet;
6 2     2   571 use vars '$VERSION';
  2         3  
  2         98  
7             $VERSION = '2.19';
8              
9 2     2   9 use strict;
  2         3  
  2         38  
10             # use warnings? probably breaking too much code
11              
12 2     2   7 use Carp;
  2         4  
  2         108  
13 2     2   315 use Mail::Header;
  2         4  
  2         56  
14 2     2   583 use Mail::Util qw/mailaddress/;
  2         5  
  2         117  
15 2     2   273 use Mail::Address;
  2         5  
  2         5869  
16              
17              
18             sub new(@)
19 3     3 1 239 { my $call = shift;
20 3 100       8 my $arg = @_ % 2 ? shift : undef;
21 3         8 my %opt = @_;
22              
23 3   33     13 my $class = ref($call) || $call;
24 3         6 my $self = bless {}, $class;
25              
26 3 50       8 $self->{mail_inet_head} = $opt{Header} if exists $opt{Header};
27 3 50       6 $self->{mail_inet_body} = $opt{Body} if exists $opt{Body};
28              
29 3         11 my $head = $self->head;
30 3   50     13 $head->fold_length(delete $opt{FoldLength} || 79);
31 3 50       6 $head->mail_from($opt{MailFrom}) if exists $opt{MailFrom};
32 3 100       11 $head->modify(exists $opt{Modify} ? $opt{Modify} : 1);
33              
34 3 100       8 if(!defined $arg) { }
    50          
    0          
35             elsif(ref($arg) eq 'ARRAY')
36 1 50       4 { $self->header($arg) unless exists $opt{Header};
37 1 50       5 $self->body($arg) unless exists $opt{Body};
38             }
39             elsif(defined fileno($arg))
40 0 0       0 { $self->read_header($arg) unless exists $opt{Header};
41 0 0       0 $self->read_body($arg) unless exists $opt{Body};
42             }
43             else
44 0         0 { croak "couldn't understand $arg to Mail::Internet constructor";
45             }
46              
47 3         7 $self;
48             }
49              
50              
51             sub read(@)
52 0     0 1 0 { my $self = shift;
53 0         0 $self->read_header(@_);
54 0         0 $self->read_body(@_);
55             }
56              
57             sub read_body($)
58 0     0 0 0 { my ($self, $fd) = @_;
59 0         0 $self->body( [ <$fd> ] );
60             }
61              
62             sub read_header(@)
63 0     0 0 0 { my $head = shift->head;
64 0         0 $head->read(@_);
65 0         0 $head->header;
66             }
67              
68              
69             sub extract($)
70 0     0 1 0 { my ($self, $lines) = @_;
71 0         0 $self->head->extract($lines);
72 0         0 $self->body($lines);
73             }
74              
75              
76             sub dup()
77 2     2 1 4 { my $self = shift;
78 2         6 my $dup = ref($self)->new;
79              
80 2   50     12 my $body = $self->{mail_inet_body} || [];
81 2         3 my $head = $self->{mail_inet_head};;
82              
83 2         10 $dup->{mail_inet_body} = [ @$body ];
84 2 50       8 $dup->{mail_inet_head} = $head->dup if $head;
85 2         6 $dup;
86             }
87              
88             #---------------
89              
90             sub body(;$@)
91 7     7 1 9 { my $self = shift;
92              
93 7 100 50     42 return $self->{mail_inet_body} ||= []
94             unless @_;
95              
96 1 50       4 $self->{mail_inet_body} = ref $_[0] eq 'ARRAY' ? $_[0] : [ @_ ];
97             }
98              
99              
100 10   66 10 1 47 sub head { shift->{mail_inet_head} ||= Mail::Header->new }
101              
102             #---------------
103              
104             sub print($)
105 0     0 1 0 { my $self = shift;
106 0   0     0 my $fd = shift || \*STDOUT;
107              
108 0 0 0     0 $self->print_header($fd)
109             and print $fd "\n"
110             and $self->print_body($fd);
111             }
112              
113              
114 0     0 1 0 sub print_header($) { shift->head->print(@_) }
115              
116             sub print_body($)
117 0     0 1 0 { my $self = shift;
118 0   0     0 my $fd = shift || \*STDOUT;
119              
120 0         0 foreach my $ln (@{$self->body})
  0         0  
121 0 0       0 { print $fd $ln or return 0;
122             }
123              
124 0         0 1;
125             }
126              
127              
128             sub as_string()
129 4     4 1 6 { my $self = shift;
130 4         8 $self->head->as_string . "\n" . join '', @{$self->body};
  4         7  
131             }
132              
133              
134             sub as_mbox_string($)
135 2     2 1 4 { my $self = shift->dup;
136 2         2 my $escaped = shift;
137              
138 2         4 $self->head->delete('Content-Length');
139 2 50       10 $self->escape_from unless $escaped;
140 2         3 $self->as_string . "\n";
141             }
142              
143             #---------------
144              
145 1     1 1 2 sub header { shift->head->header(@_) }
146 0     0 1 0 sub fold { shift->head->fold(@_) }
147 0     0 1 0 sub fold_length { shift->head->fold_length(@_) }
148 0     0 1 0 sub combine { shift->head->combine(@_) }
149              
150              
151             sub add(@)
152 0     0 1 0 { my $head = shift->head;
153 0         0 my $ret;
154 0         0 while(@_)
155 0         0 { my ($tag, $line) = splice @_, 0, 2;
156 0 0       0 $ret = $head->add($tag, $line, -1)
157             or return undef;
158             }
159              
160 0         0 $ret;
161             }
162              
163              
164             sub replace(@)
165 0     0 1 0 { my $head = shift->head;
166 0         0 my $ret;
167              
168 0         0 while(@_)
169 0         0 { my ($tag, $line) = splice @_, 0, 2;
170 0 0       0 $ret = $head->replace($tag, $line, 0)
171             or return undef;
172             }
173              
174 0         0 $ret;
175             }
176              
177              
178             sub get(@)
179 0     0 1 0 { my $head = shift->head;
180              
181 0 0       0 return map { $head->get($_) } @_
  0         0  
182             if wantarray;
183              
184 0         0 foreach my $tag (@_)
185 0         0 { my $r = $head->get($tag);
186 0 0       0 return $r if defined $r;
187             }
188              
189 0         0 undef;
190             }
191              
192              
193             sub delete(@)
194 0     0 1 0 { my $head = shift->head;
195 0         0 map { $head->delete($_) } @_;
  0         0  
196             }
197              
198             # Undocumented; unused???
199             sub empty()
200 0     0 0 0 { my $self = shift;
201 0         0 %$self = ();
202 0         0 1;
203             }
204              
205             #---------------
206              
207             sub remove_sig($)
208 0     0 1 0 { my $body = shift->body;
209 0   0     0 my $nlines = shift || 10;
210 0         0 my $start = @$body;
211              
212 0         0 my $i = 0;
213 0   0     0 while($i++ < $nlines && $start--)
214 0 0       0 { next if $body->[$start] !~ /^--[ ]?[\r\n]/;
215              
216 0         0 splice @$body, $start, $i;
217 0         0 last;
218             }
219             }
220              
221              
222             sub sign(@)
223 0     0 1 0 { my ($self, %arg) = @_;
224 0         0 my ($sig, @sig);
225              
226 0 0       0 if($sig = delete $arg{File})
    0          
227 0         0 { local *SIG;
228              
229 0 0       0 if(open(SIG, $sig))
230 0         0 { local $_;
231 0 0       0 while() { last unless /^(--)?\s*$/ }
  0         0  
232 0         0 @sig = ($_, , "\n");
233 0         0 close SIG;
234             }
235             }
236             elsif($sig = delete $arg{Signature})
237 0 0       0 { @sig = ref($sig) ? @$sig : split(/\n/, $sig);
238             }
239              
240 0 0       0 if(@sig)
241 0         0 { $self->remove_sig;
242 0         0 s/[\r\n]*$/\n/ for @sig;
243 0         0 push @{$self->body}, "-- \n", @sig;
  0         0  
244             }
245              
246 0         0 $self;
247             }
248              
249              
250             sub tidy_body()
251 0     0 1 0 { my $body = shift->body;
252              
253 0   0     0 shift @$body while @$body && $body->[0] =~ /^\s*$/;
254 0   0     0 pop @$body while @$body && $body->[-1] =~ /^\s*$/;
255 0         0 $body;
256             }
257              
258             #---------------
259              
260             sub reply(@)
261 0     0 1 0 { my ($self, %arg) = @_;
262 0         0 my $class = ref $self;
263 0         0 my @reply;
264              
265 0         0 local *MAILHDR;
266 0 0       0 if(open(MAILHDR, "$ENV{HOME}/.mailhdr"))
267             { # User has defined a mail header template
268 0         0 @reply = ;
269 0         0 close MAILHDR;
270             }
271              
272 0         0 my $reply = $class->new(\@reply);
273              
274             # The Subject line
275 0   0     0 my $subject = $self->get('Subject') || "";
276 0 0 0     0 $subject = "Re: " . $subject
277             if $subject =~ /\S+/ && $subject !~ /Re:/i;
278              
279 0         0 $reply->replace(Subject => $subject);
280              
281             # Locate who we are sending to
282 0   0     0 my $to = $self->get('Reply-To')
283             || $self->get('From')
284             || $self->get('Return-Path')
285             || "";
286              
287 0         0 my $sender = (Mail::Address->parse($to))[0];
288              
289 0         0 my $name = $sender->name;
290 0 0       0 unless(defined $name)
291 0         0 { my $fr = $self->get('From');
292 0 0       0 $fr = (Mail::Address->parse($fr))[0] if defined $fr;
293 0 0       0 $name = $fr->name if defined $fr;
294             }
295              
296 0   0     0 my $indent = $arg{Indent} || ">";
297 0 0       0 if($indent =~ /\%/)
298 0         0 { my %hash = ( '%' => '%');
299 0 0       0 my @name = $name ? grep( {length $_} split /[\n\s]+/, $name) : '';
  0         0  
300              
301 0         0 $hash{f} = $name[0];
302 0 0       0 $hash{F} = $#name ? substr($hash{f},0,1) : $hash{f};
303              
304 0 0       0 $hash{l} = $#name ? $name[$#name] : "";
305 0   0     0 $hash{L} = substr($hash{l},0,1) || "";
306              
307 0   0     0 $hash{n} = $name || "";
308 0         0 $hash{I} = join "", map {substr($_,0,1)} @name;
  0         0  
309              
310 0 0       0 $indent =~ s/\%(.)/defined $hash{$1} ? $hash{$1} : $1/eg;
  0         0  
311             }
312              
313 0         0 my $id = $sender->address;
314 0         0 $reply->replace(To => $id);
315              
316             # Find addresses not to include
317 0   0     0 my $mailaddresses = $ENV{MAILADDRESSES} || "";
318              
319 0         0 my %nocc = (lc($id) => 1);
320             $nocc{lc $_->address} = 1
321 0         0 for Mail::Address->parse($reply->get('Bcc'), $mailaddresses);
322              
323 0 0       0 if($arg{ReplyAll}) # Who shall we copy this to
324 0         0 { my %cc;
325 0         0 foreach my $addr (Mail::Address->parse($self->get('To'), $self->get('Cc')))
326 0         0 { my $lc = lc $addr->address;
327             $cc{$lc} = $addr->format
328 0 0       0 unless $nocc{$lc};
329             }
330 0         0 my $cc = join ', ', values %cc;
331 0         0 $reply->replace(Cc => $cc);
332             }
333              
334             # References
335 0   0     0 my $refs = $self->get('References') || "";
336 0         0 my $mid = $self->get('Message-Id');
337              
338 0 0       0 $refs .= " " . $mid if defined $mid;
339 0         0 $reply->replace(References => $refs);
340              
341             # In-Reply-To
342 0         0 my $date = $self->get('Date');
343 0         0 my $inreply = "";
344              
345 0 0       0 if(defined $mid)
    0          
346 0         0 { $inreply = $mid;
347 0         0 my @comment;
348 0 0       0 push @comment, "from $name" if defined $name;
349 0 0       0 push @comment, "on $date" if defined $date;
350 0         0 local $" = ' ';
351 0 0       0 $inreply .= " (@comment)" if @comment;
352             }
353             elsif(defined $name)
354 0         0 { $inreply = $name . "'s message";
355 0 0       0 $inreply .= "of " . $date if defined $date;
356             }
357 0         0 $reply->replace('In-Reply-To' => $inreply);
358              
359             # Quote the body
360 0         0 my $body = $reply->body;
361 0         0 @$body = @{$self->body}; # copy body
  0         0  
362 0         0 $reply->remove_sig;
363 0         0 $reply->tidy_body;
364 0         0 s/\A/$indent/ for @$body;
365              
366             # Add references
367 0 0       0 unshift @{$body}, (defined $name ? $name . " " : "") . "<$id> writes:\n";
  0         0  
368              
369 0 0 0     0 if(defined $arg{Keep} && ref $arg{Keep} eq 'ARRAY') # Include lines
370 0         0 { foreach my $keep (@{$arg{Keep}})
  0         0  
371 0         0 { my $ln = $self->get($keep);
372 0 0       0 $reply->replace($keep => $ln) if defined $ln;
373             }
374             }
375              
376 0 0 0     0 if(defined $arg{Exclude} && ref $arg{Exclude} eq 'ARRAY') # Exclude lines
377 0         0 { $reply->delete(@{$arg{Exclude}});
  0         0  
378             }
379              
380 0         0 $reply->head->cleanup; # remove empty header lines
381 0         0 $reply;
382             }
383              
384              
385             sub smtpsend($@)
386 0     0 1 0 { my ($self, %opt) = @_;
387              
388 0         0 require Net::SMTP;
389 0         0 require Net::Domain;
390              
391 0         0 my $host = $opt{Host};
392 0   0     0 my $envelope = $opt{MailFrom} || mailaddress();
393 0         0 my $quit = 1;
394              
395 0         0 my ($smtp, @hello);
396              
397             push @hello, Hello => $opt{Hello}
398 0 0       0 if defined $opt{Hello};
399              
400             push @hello, Port => $opt{Port}
401 0 0       0 if exists $opt{Port};
402              
403             push @hello, Debug => $opt{Debug}
404 0 0       0 if exists $opt{Debug};
405              
406 0 0 0     0 if(!defined $host)
    0          
407 0         0 { local $SIG{__DIE__};
408 0         0 my @hosts = qw(mailhost localhost);
409             unshift @hosts, split /\:/, $ENV{SMTPHOSTS}
410 0 0       0 if defined $ENV{SMTPHOSTS};
411              
412 0         0 foreach $host (@hosts)
413 0         0 { $smtp = eval { Net::SMTP->new($host, @hello) };
  0         0  
414 0 0       0 last if defined $smtp;
415             }
416             }
417             elsif(UNIVERSAL::isa($host,'Net::SMTP')
418             || UNIVERSAL::isa($host,'Net::SMTP::SSL'))
419 0         0 { $smtp = $host;
420 0         0 $quit = 0;
421             }
422             else
423 0         0 { local $SIG{__DIE__};
424 0         0 $smtp = eval { Net::SMTP->new($host, @hello) };
  0         0  
425             }
426              
427 0 0       0 defined $smtp or return ();
428              
429 0         0 my $head = $self->cleaned_header_dup;
430              
431             # Who is it to
432              
433 0 0       0 my @rcpt = map { ref $_ ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'};
  0         0  
  0         0  
434 0 0       0 @rcpt = map { $head->get($_) } qw(To Cc Bcc)
  0         0  
435             unless @rcpt;
436              
437 0         0 my @addr = map {$_->address} Mail::Address->parse(@rcpt);
  0         0  
438 0 0       0 @addr or return ();
439              
440 0         0 $head->delete('Bcc');
441              
442             # Send it
443              
444             my $ok = $smtp->mail($envelope)
445             && $smtp->to(@addr)
446 0   0     0 && $smtp->data(join("", @{$head->header}, "\n", @{$self->body}));
447              
448 0 0       0 $quit && $smtp->quit;
449 0 0       0 $ok ? @addr : ();
450             }
451              
452              
453             sub send($@)
454 0     0 1 0 { my ($self, $type, @args) = @_;
455              
456 0         0 require Mail::Mailer;
457              
458 0         0 my $head = $self->cleaned_header_dup;
459 0         0 my $mailer = Mail::Mailer->new($type, @args);
460              
461 0         0 $mailer->open($head->header_hashref);
462 0         0 $self->print_body($mailer);
463 0         0 $mailer->close;
464             }
465              
466              
467             sub nntppost
468 0     0 1 0 { my ($self, %opt) = @_;
469              
470 0         0 require Net::NNTP;
471              
472 0   0     0 my $groups = $self->get('Newsgroups') || "";
473 0         0 my @groups = split /[\s,]+/, $groups;
474 0 0       0 @groups or return ();
475              
476 0         0 my $head = $self->cleaned_header_dup;
477              
478             # Remove these incase the NNTP host decides to mail as well as me
479 0         0 $head->delete(qw(To Cc Bcc));
480              
481 0         0 my $news;
482 0         0 my $quit = 1;
483              
484 0         0 my $host = $opt{Host};
485 0 0 0     0 if(ref($host) && UNIVERSAL::isa($host,'Net::NNTP'))
486 0         0 { $news = $host;
487 0         0 $quit = 0;
488             }
489             else
490 0         0 { my @opt = $opt{Host};
491              
492             push @opt, Port => $opt{Port}
493 0 0       0 if exists $opt{Port};
494              
495             push @opt, Debug => $opt{Debug}
496 0 0       0 if exists $opt{Debug};
497              
498 0 0       0 $news = Net::NNTP->new(@opt)
499             or return ();
500             }
501              
502 0         0 $news->post(@{$head->header}, "\n", @{$self->body});
  0         0  
  0         0  
503 0         0 my $rc = $news->code;
504              
505 0 0       0 $news->quit if $quit;
506              
507 0 0       0 $rc == 240 ? @groups : ();
508             }
509              
510              
511             sub escape_from
512 2     2 1 9 { my $body = shift->body;
513 2         4 scalar grep { s/\A(>*From) />$1 /o } @$body;
  24         52  
514             }
515              
516              
517              
518             sub unescape_from
519 0     0 1   { my $body = shift->body;
520 0           scalar grep { s/\A>(>*From) /$1 /o } @$body;
  0            
521             }
522              
523             # Don't tell people it exists
524             sub cleaned_header_dup()
525 0     0 0   { my $head = shift->head->dup;
526              
527 0           $head->delete('From '); # Just in case :-)
528              
529             # An original message should not have any Received lines
530 0           $head->delete('Received');
531              
532 0 0         $head->replace('X-Mailer', "Perl5 Mail::Internet v".$Mail::Internet::VERSION)
533             unless $head->count('X-Mailer');
534              
535 0   0       my $name = eval {local $SIG{__DIE__}; (getpwuid($>))[6]} || $ENV{NAME} ||"";
536              
537 0           while($name =~ s/\([^\(\)]*\)//) { 1; }
  0            
538              
539 0 0         if($name =~ /[^\w\s]/)
540 0           { $name =~ s/"/\"/g;
541 0           $name = '"' . $name . '"';
542             }
543              
544 0           my $from = sprintf "%s <%s>", $name, mailaddress();
545 0           $from =~ s/\s{2,}/ /g;
546              
547 0           foreach my $tag (qw(From Sender))
548 0 0         { $head->get($tag) or $head->add($tag, $from);
549             }
550              
551 0           $head;
552             }
553              
554             1;