File Coverage

blib/lib/Email/Sender/Transport/Maildir.pm
Criterion Covered Total %
statement 83 83 100.0
branch 14 26 53.8
condition 2 6 33.3
subroutine 19 19 100.0
pod 0 1 0.0
total 118 135 87.4


line stmt bran cond sub pod time code
1             package Email::Sender::Transport::Maildir 1.500;
2             # ABSTRACT: deliver mail to a maildir on disk
3              
4 2     2   81917 use Moo;
  2         10149  
  2         10  
5             with 'Email::Sender::Transport';
6              
7 2     2   1645 use Errno ();
  2         5  
  2         30  
8 2     2   8 use Fcntl;
  2         2  
  2         499  
9 2     2   13 use File::Path 2.06;
  2         51  
  2         103  
10 2     2   20 use File::Spec;
  2         3  
  2         39  
11              
12 2     2   896 use Sys::Hostname;
  2         1908  
  2         98  
13              
14 2     2   448 use MooX::Types::MooseLike::Base qw(Bool);
  2         5724  
  2         136  
15              
16             #pod =head1 DESCRIPTION
17             #pod
18             #pod This transport delivers into a maildir. The maildir's location may be given as
19             #pod the F argument to the constructor, and defaults to F in the
20             #pod current directory (at the time of transport initialization).
21             #pod
22             #pod If the directory does not exist, it will be created.
23             #pod
24             #pod By default, three headers will be added:
25             #pod
26             #pod * X-Email-Sender-From - the envelope sender
27             #pod * X-Email-Sender-To - the envelope recipients (one header per rcpt)
28             #pod * Lines - the number of lines in the body
29             #pod
30             #pod These can be controlled with the C and
31             #pod C constructor arguments.
32             #pod
33             #pod The L object returned on success has a C
34             #pod method that returns the filename to which the message was delivered.
35             #pod
36             #pod =cut
37              
38             {
39             package
40             Email::Sender::Success::MaildirSuccess;
41 2     2   11 use Moo;
  2         4  
  2         12  
42 2     2   691 use MooX::Types::MooseLike::Base qw(Str);
  2         5  
  2         140  
43             extends 'Email::Sender::Success';
44             has filename => (
45             is => 'ro',
46             isa => Str,
47             required => 1,
48             );
49 2     2   10 no Moo;
  2         4  
  2         9  
50             }
51              
52              
53             my $HOSTNAME;
54 2     2   518 BEGIN { ($HOSTNAME = hostname) =~ s/\..*//; }
55 2     2   5 sub _hostname { $HOSTNAME }
56              
57             my $MAILDIR_TIME = 0;
58             my $MAILDIR_COUNTER = 0;
59              
60             has [ qw(add_lines_header add_envelope_headers) ] => (
61             is => 'ro',
62             isa => Bool,
63             default => sub { 1 },
64             );
65              
66             has dir => (
67             is => 'ro',
68             required => 1,
69             default => sub { File::Spec->catdir(File::Spec->curdir, 'Maildir') },
70             );
71              
72             sub send_email {
73 2     2 0 7 my ($self, $email, $env) = @_;
74              
75 2         3 my $dupe = Email::Abstract->new(\do { $email->as_string });
  2         10  
76              
77 2 50       493 if ($self->add_envelope_headers) {
78             $dupe->set_header('X-Email-Sender-From' =>
79 2 50       22 (defined $env->{from} ? $env->{from} : '-'),
80             );
81              
82 2         167 my @to = grep {; defined } @{ $env->{to} };
  2         6  
  2         4  
83 2 50       9 $dupe->set_header('X-Email-Sender-To' => (@to ? @to : '-'));
84             }
85              
86 2         117 $self->_ensure_maildir_exists;
87              
88 2 50       18 $self->_add_lines_header($dupe) if $self->add_lines_header;
89 2         384 $self->_update_time;
90              
91 2         8 my $fn = $self->_deliver_email($dupe);
92              
93 2         45 return Email::Sender::Success::MaildirSuccess->new({
94             filename => $fn,
95             });
96             }
97              
98             sub _ensure_maildir_exists {
99 2     2   5 my ($self) = @_;
100              
101 2         6 for my $dir (qw(cur tmp new)) {
102 6         55 my $subdir = File::Spec->catdir($self->dir, $dir);
103 6 100       110 next if -d $subdir;
104              
105 3 50 33     472 Email::Sender::Failure->throw("couldn't create $subdir: $!")
106             unless File::Path::make_path($subdir) || -d $subdir;
107             }
108             }
109              
110             sub _add_lines_header {
111 2     2   7 my ($class, $email) = @_;
112 2 50       41 return if $email->get_header("Lines");
113 2         139 my $lines = $email->get_body =~ tr/\n/\n/;
114 2         64 $email->set_header("Lines", $lines);
115             }
116              
117             sub _update_time {
118 2     2   25 my $time = time;
119 2 100       8 if ($MAILDIR_TIME != $time) {
120 1         2 $MAILDIR_TIME = $time;
121 1         2 $MAILDIR_COUNTER = 0;
122             } else {
123 1         3 $MAILDIR_COUNTER++;
124             }
125             }
126              
127             sub _deliver_email {
128 2     2   6 my ($self, $email) = @_;
129              
130 2         6 my ($tmp_filename, $tmp_fh) = $self->_delivery_fh;
131              
132             # if (eval { $email->can('stream_to') }) {
133             # eval { $mail->stream_to($fh); 1 } or return;
134             #} else {
135 2         8 my $string = $email->as_string;
136 2 50       364 $string =~ s/\x0D\x0A/\x0A/g unless $^O eq 'MSWin32';
137 2 50       45 print $tmp_fh $string
138             or Email::Sender::Failure->throw("could not write to $tmp_filename: $!");
139              
140 2 50       96 close $tmp_fh
141             or Email::Sender::Failure->throw("error closing $tmp_filename: $!");
142              
143 2         35 my $target_name = File::Spec->catfile($self->dir, 'new', $tmp_filename);
144              
145 2         360 my $ok = rename(
146             File::Spec->catfile($self->dir, 'tmp', $tmp_filename),
147             $target_name,
148             );
149              
150 2 50       14 Email::Sender::Failure->throw("could not move $tmp_filename from tmp to new")
151             unless $ok;
152              
153 2         12 return $target_name;
154             }
155              
156             sub _delivery_fh {
157 2     2   4 my ($self) = @_;
158              
159 2         8 my $hostname = $self->_hostname;
160              
161 2         3 my ($filename, $fh);
162 2         6 until ($fh) {
163 2         11 $filename = join q{.}, $MAILDIR_TIME, $$, ++$MAILDIR_COUNTER, $hostname;
164 2         26 my $filespec = File::Spec->catfile($self->dir, 'tmp', $filename);
165 2         175 sysopen $fh, $filespec, O_CREAT|O_EXCL|O_WRONLY;
166 2         12 binmode $fh;
167             Email::Sender::Failure->throw("cannot create $filespec for delivery: $!")
168 2 0 33     10 unless $fh or $!{EEXIST};
169             }
170              
171 2         8 return ($filename, $fh);
172             }
173              
174 2     2   1620 no Moo;
  2         4  
  2         7  
175             1;
176              
177             __END__