File Coverage

blib/lib/Email/Folder/Mbox.pm
Criterion Covered Total %
statement 132 146 90.4
branch 44 66 66.6
condition 21 27 77.7
subroutine 16 18 88.8
pod 5 6 83.3
total 218 263 82.8


line stmt bran cond sub pod time code
1 1     1   4 use strict;
  1         1  
  1         26  
2 1     1   3 use warnings;
  1         1  
  1         42  
3             package Email::Folder::Mbox;
4             {
5             $Email::Folder::Mbox::VERSION = '0.860';
6             }
7             # ABSTRACT: reads raw RFC822 mails from an mbox file
8              
9 1     1   4 use Carp;
  1         1  
  1         47  
10 1     1   462 use IO::File;
  1         7566  
  1         125  
11 1     1   401 use Email::Folder::Reader;
  1         2  
  1         25  
12 1     1   429 use parent 'Email::Folder::Reader';
  1         251  
  1         5  
13              
14              
15             sub defaults {
16 9     9 0 49 ( eol => "\n")
17             }
18              
19             sub _open_it {
20 9     9   12 my $self = shift;
21 9         15 my $file = $self->{_file};
22 9         15 my $fh = $self->{fh};
23              
24 9 50 33     28 unless ($file eq "FH" and $fh) {
25             # sanity checking
26 9 50       162 croak "$file does not exist" unless (-e $file);
27 9 50       130 croak "$file is a directory" if (-d $file);
28              
29 9         53 local $/ = $self->{eol};
30 9         21 $fh = $self->_get_fh($file);
31             }
32              
33 9 50       49 if (seek $fh, tell($fh), 0) {
34             # Enable using seek only if $fh is seekable
35 9         22 $self->{seekable} = 1;
36             } else {
37             # Otherwise use cache for simulating backward seeks
38 0         0 $self->{cache} = [];
39             }
40              
41 9 100       21 if ($self->{seek_to}) {
42 1 50       5 unless ($self->{seekable}) {
43 0         0 croak "$file is not seekable but seek_to was set";
44             }
45             # we were told to seek. hope it all goes well
46 1         4 seek $fh, $self->{seek_to}, 0;
47             }
48             else {
49 8         33 local $/ = $self->{eol};
50 8         174 my $firstline = <$fh>;
51 8 100       25 if ($firstline) {
52 7 50       38 croak "$file is not an mbox file" unless $firstline =~ /^From /;
53             }
54 8         35 $self->{from} = $firstline;
55             }
56              
57 9         42 $self->{_fh} = $fh;
58             }
59              
60             sub _get_fh {
61 9     9   13 my $self = shift;
62 9         11 my $file = shift;
63 9 50       55 my $fh = IO::File->new($file) or croak "Cannot open $file";
64 9         678 binmode($fh);
65 9         37 return $fh;
66             }
67              
68             sub _read_nextline {
69 2884     2884   2162 my $self = shift;
70 2884 50 33     4511 if (not $self->{seekable} and @{$self->{cache}}) {
  0         0  
71 0         0 return shift @{$self->{cache}};
  0         0  
72             }
73 2884         2082 my $fh = $self->{_fh};
74 2884         6307 return <$fh>;
75             }
76              
77 1     1   375 use constant debug => 0;
  1         1  
  1         953  
78             my $count;
79              
80             sub next_from {
81 0     0 1 0 my $self = shift;
82 0 0       0 $self->_open_it unless $self->{_fh};
83 0         0 return $self->{from};
84             }
85              
86             sub next_messageref {
87 47     47 1 39 my $self = shift;
88              
89 47   66     112 my $fh = $self->{_fh} || $self->_open_it;
90 47         116 local $/ = $self->{eol};
91              
92 47         47 $self->{messageid} = undef;
93              
94 47         40 my $mail = '';
95 47         39 my $prev = '';
96 47         39 my $last;
97 47         39 my $inheaders = 1;
98 47         36 ++$count;
99 47         39 print "$count starting scanning at line $.\n" if debug;
100              
101 47         66 while (my $line = _read_nextline($self)) {
102 2310 100 100     4561 if ($line eq $/ && $inheaders) { # end of headers
103 40         27 print "$count end of headers at line $.\n" if debug;
104 40         37 $inheaders = 0; # stop looking for the end of headers
105 40         29 my $pos; # where to go back to if it goes wrong
106 40 50       77 $pos = tell $fh if $self->{seekable};
107              
108             # look for a content length header, and try to use that
109 40 100       309 if ($mail =~ m/^Content-Length:\s*(\d+)$/mi) {
110 18         18 my @cache;
111 18         49 $mail .= $prev;
112 18         19 $prev = '';
113 18         24 my $length = $1;
114 18         13 print " Content-Length: $length\n" if debug;
115 18         17 my $read = '';
116 18         27 while (my $bodyline = _read_nextline($self)) {
117 506 50       681 push @cache, $bodyline unless $self->{seekable};
118 506 100       675 last if length $read >= $length;
119             # unescape From_
120 488 50       638 $bodyline =~ s/^>(>*From )/$1/ if $self->{unescape};
121 488         636 $read .= $bodyline;
122             }
123             # grab the next line (should be /^From / or undef)
124 18         23 my $next = _read_nextline($self);
125 18 100 100     114 if (!defined $next || $next =~ /^From /) {
126 17         20 $self->{from} = $next;
127 17         60 $mail .= "$/$read";
128 17         57 return \$mail;
129             }
130 1 50       3 push @cache, $next unless $self->{seekable};
131             # seek back and scan line-by-line like the header
132             # wasn't here
133 1         2 print " Content-Length assertion failed '$next'\n" if debug;
134 1 50       2 if ($self->{seekable}) {
135 1         5 seek $fh, $pos, 0;
136             }
137             else {
138 0         0 unshift @{$self->{cache}}, @cache;
  0         0  
139             }
140             }
141              
142             # much the same, but with Lines:
143 23 100       122 if ($mail =~ m/^Lines:\s*(\d+)$/mi) {
144 2         3 my @cache;
145 2         5 $mail .= $prev;
146 2         3 $prev = '';
147 2         4 my $lines = $1;
148 2         1 print " Lines: $lines\n" if debug;
149 2         3 my $read = '';
150 2         10 for (1 .. $lines) {
151 37         37 my $bodyline = _read_nextline($self);
152 37 50       50 last unless defined $bodyline;
153 37 50       83 push @cache, $bodyline unless $self->{seekable};
154             # unescape From_
155 37 50       47 $bodyline =~ s/^>(>*From )/$1/ if $self->{unescape};
156 37         40 $read .= $bodyline;
157             }
158 2         3 my $ign = _read_nextline($self); # trailing newline
159 2         4 my $next = _read_nextline($self);
160 2 100 66     9 if (!defined $next || $next =~ /^From /) {
161 1         2 $self->{from} = $next;
162 1         4 $mail .= "$/$read";
163 1         4 return \$mail;
164             }
165 1 50       3 push @cache, $ign, $next unless $self->{seekable};
166             # seek back and scan line-by-line like the header
167             # wasn't here
168 1         1 print " Lines assertion failed '$next'\n" if debug;
169 1 50       4 if ($self->{seekable}) {
170 1         4 seek $fh, $pos, 0;
171             }
172             else {
173 0         0 unshift @{$self->{cache}}, @cache;
  0         0  
174             }
175             }
176             }
177              
178 2292 100 100     3746 if ($prev eq $/ && ($line =~ $self->_from_line_re)) {
179 20         21 $last = $line;
180 20         27 last;
181             }
182              
183 2272 100 100     7196 if ($inheaders && !defined $self->{messageid} && ($line =~ /^Message-Id:\s*(.+)/i)) {
      100        
184 40         76 $self->{messageid} = $1;
185             }
186              
187 2272         2197 $mail .= $prev;
188 2272         1799 $prev = $line;
189              
190             # unescape From_
191 2272 50       4281 $prev =~ s/^>(>*From )/$1/ if $self->{unescape};
192             }
193 29         35 $mail .= $prev;
194 29         19 print "$count end of message line $.\n" if debug;
195 29         30 $self->{from} = $last;
196 29 100       56 return unless $mail;
197 22         52 return \$mail;
198             }
199              
200             sub next_message {
201 47     47 1 46 my $self = shift;
202 47         70 my $ref = $self->next_messageref;
203 47 100       89 return unless $ref;
204 40         29 return ${$ref};
  40         156  
205             }
206              
207             my @FROM_RE;
208             BEGIN {
209 1     1   91 @FROM_RE = (
210             # according to mutt:
211             # A valid message separator looks like:
212             # From [ ]
213             qr/^From (?:\S+\s+)?(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)/,
214              
215             # though, as jwz rants, only this is reliable and portable
216             qr/^From /,
217             );
218             }
219              
220             sub _from_line_re {
221 236 50   236   1039 return $FROM_RE[ $_[0]->{jwz_From_} ? 1 : 0 ];
222             }
223              
224             sub tell {
225 1     1 1 3 my $self = shift;
226 1         4 return tell $self->{_fh};
227             }
228              
229             sub messageid {
230 0     0 1   my $self = shift;
231 0           return $self->{messageid};
232             }
233              
234             1;
235              
236             __END__