File Coverage

blib/lib/Email/LocalDelivery/Mbox.pm
Criterion Covered Total %
statement 70 73 95.8
branch 12 26 46.1
condition 2 5 40.0
subroutine 15 15 100.0
pod 0 3 0.0
total 99 122 81.1


line stmt bran cond sub pod time code
1 1     1   6 use strict;
  1         2  
  1         41  
2 1     1   6 use warnings;
  1         1  
  1         52  
3             package Email::LocalDelivery::Mbox;
4             {
5             $Email::LocalDelivery::Mbox::VERSION = '1.200';
6             }
7             # ABSTRACT: deliver mail to an mbox
8              
9              
10 1     1   8 use File::Path;
  1         2  
  1         99  
11 1     1   6 use File::Basename;
  1         2  
  1         93  
12 1     1   1166 use Email::Simple 1.998; # needed for ->header_obj
  1         7647  
  1         73  
13 1     1   14 use Fcntl ':flock';
  1         3  
  1         222  
14 1     1   7 use Symbol qw(gensym);
  1         3  
  1         1038  
15              
16             sub deliver {
17             # The slightly convoluted method of unrolling the stack is intended to limit
18             # the scope of which a large string at $_[1] might be in memory before being
19             # constructed into an Email::Simple. -- rjbs, 2007-05-25
20 1     1 0 3 my $class = shift;
21              
22 1         24 my $email;
23 1 50       4 if (eval { $_[0]->isa('Email::Simple') }) {
  1         17  
24 0         0 $email = shift;
25             } else {
26 1         3 my $text = shift;
27 1         6 $email = Email::Simple->new(\$text); # requires Email::Simple 1.998 or so
28             }
29              
30 1         188 my @files = @_;
31              
32 1         2 my @rv;
33              
34 1         3 for my $file (@files) {
35 1 50       4 my $fh = $class->_open_fh($file) or next;
36 1 50       5 print $fh "\n" if tell($fh) > 0;
37 1         5 print $fh $class->_from_line($email);
38 1         7 print $fh $class->_escape_from_body($email);
39              
40             # This will make streaming a bit more annoying. -- rjbs, 2007-05-25
41 1 50       51 print $fh "\n" unless $email->as_string =~ /\n$/;
42              
43 1 50       47 $class->_close_fh($fh) || next;
44 1         6 push @rv, $file;
45             }
46 1         27 return @rv;
47             }
48              
49             sub _open_fh {
50 1     1   2 my ($class, $file) = @_;
51 1         137 my $dir = dirname($file);
52 1 0 33     30 return if !-d $dir and not mkpath($dir);
53              
54 1         5 my $fh = gensym;
55 1 50       146 open $fh, ">> $file" or return;
56 1 50       7 $class->getlock($fh) || return;
57 1         6 seek $fh, 0, 2;
58 1         8 return $fh;
59             }
60              
61             sub _close_fh {
62 1     1   2 my ($class, $fh) = @_;
63 1 50       4 $class->unlock($fh) || return;
64 1 50       16 close $fh or return;
65 1         6 return 1;
66             }
67              
68             sub _escape_from_body {
69 1     1   2 my ($class, $email) = @_;
70              
71 1         5 my $body = $email->body;
72 1         16 $body =~ s/^(From )/>$1/gm;
73              
74 1         5 return $email->header_obj->as_string . $email->crlf . $body;
75             }
76              
77             sub _from_line {
78 1     1   2 my ($class, $email) = @_;
79              
80             # The qmail way.
81 1 50       5 return $ENV{UFLINE} . $ENV{RPLINE} . $ENV{DTLINE} if exists $ENV{UFLINE};
82              
83             # The boring way.
84 1         2 return _from_line_boring($email);
85             }
86              
87             sub _from_line_boring {
88 1     1   2 my $mail = shift;
89 1   50     5 my $from = $mail->header("Return-path")
90             || $mail->header("Sender")
91             || $mail->header("Reply-To")
92             || $mail->header("From")
93             || 'root@localhost';
94 1 50       97 $from = $1 if $from =~ /<(.*?)>/; # comment -> email@address
95 1         2 $from =~ s/\s*\(.*\)\s*//; # email@address (comment) -> email@address
96 1         2 $from =~ s/\s+//g; # if any whitespace remains, get rid of it.
97              
98 1         210 my $fromtime = localtime;
99 1         12 $fromtime =~ s/(:\d\d) \S+ (\d{4})$/$1 $2/; # strip timezone.
100 1         10 return "From $from $fromtime\n";
101             }
102              
103             sub getlock {
104 1     1 0 3 my ($class, $fh) = @_;
105 1         4 for (1 .. 10) {
106 1 50       14 return 1 if flock($fh, LOCK_EX | LOCK_NB);
107 0         0 sleep $_;
108             }
109 0         0 return 0;
110             }
111              
112             sub unlock {
113 1     1 0 2 my ($class, $fh) = @_;
114 1         65 flock($fh, LOCK_UN);
115             }
116              
117             1;
118              
119             __END__