File Coverage

blib/lib/Mail/Box/Parser/Perl.pm
Criterion Covered Total %
statement 107 168 63.6
branch 32 72 44.4
condition 8 30 26.6
subroutine 17 23 73.9
pod 12 13 92.3
total 176 306 57.5


line stmt bran cond sub pod time code
1             # Copyrights 2001-2023 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.03.
5             # This code is part of distribution Mail-Message. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Box::Parser::Perl;
10 7     7   1481 use vars '$VERSION';
  7         14  
  7         372  
11             $VERSION = '3.013';
12              
13 7     7   43 use base 'Mail::Box::Parser';
  7         13  
  7         1143  
14              
15 7     7   49 use strict;
  7         32  
  7         137  
16 7     7   43 use warnings;
  7         13  
  7         181  
17              
18 7     7   538 use Mail::Message::Field;
  7         22  
  7         234  
19 7     7   40 use List::Util 'sum';
  7         38  
  7         587  
20 7     7   544 use IO::File;
  7         1978  
  7         15809  
21              
22              
23             sub init(@)
24 12     12 0 29 { my ($self, $args) = @_;
25              
26 12 50       52 $self->SUPER::init($args) or return;
27              
28 12         30 $self->{MBPP_trusted} = $args->{trusted};
29 12         25 $self->{MBPP_fix} = $args->{fix_header_errors};
30 12         46 $self;
31             }
32              
33             sub pushSeparator($)
34 7     7 1 18 { my ($self, $sep) = @_;
35 7         22 unshift @{$self->{MBPP_separators}}, $sep;
  7         25  
36 7 50       18 $self->{MBPP_strip_gt}++ if $sep eq 'From ';
37 7         18 $self;
38             }
39              
40             sub popSeparator()
41 7     7 1 14 { my $self = shift;
42 7         21 my $sep = shift @{$self->{MBPP_separators}};
  7         17  
43 7 50       19 $self->{MBPP_strip_gt}-- if $sep eq 'From ';
44 7         16 $sep;
45             }
46            
47             sub filePosition(;$)
48 7     7 1 13 { my $self = shift;
49 7 50       42 @_ ? $self->{MBPP_file}->seek(shift, 0) : $self->{MBPP_file}->tell;
50             }
51              
52             my $empty = qr/^\015?\012?$/;
53              
54              
55             sub readHeader()
56 26     26 1 44 { my $self = shift;
57 26         48 my $file = $self->{MBPP_file};
58              
59 26         100 my @ret = ($file->tell, undef);
60 26         88 my $line = $file->getline;
61              
62             LINE:
63 26         79 while(defined $line)
64 92 100       515 { last LINE if $line =~ $empty;
65 66         330 my ($name, $body) = split /\s*\:\s*/, $line, 2;
66              
67 66 50       158 unless(defined $body)
68 0         0 { $self->log(WARNING =>
69             "Unexpected end of header in ".$self->filename.":\n $line");
70              
71 0 0 0     0 if(@ret && $self->fixHeaderErrors)
72 0         0 { $ret[-1][1] .= ' '.$line; # glue err line to previous field
73 0         0 $line = $file->getline;
74 0         0 next LINE;
75             }
76             else
77 0         0 { $file->seek(-length $line, 1);
78 0         0 last LINE;
79             }
80             }
81              
82 66 50       132 $body = "\n" unless length $body;
83              
84             # Collect folded lines
85 66         168 while($line = $file->getline)
86 69 100       209 { $line =~ m!^[ \t]! ? ($body .= $line) : last;
87             }
88              
89 66         121 $body =~ s/\015//g;
90 66         224 push @ret, [ $name, $body ];
91             }
92              
93 26         91 $ret[1] = $file->tell;
94 26         98 @ret;
95             }
96              
97             sub _is_good_end($)
98 0     0   0 { my ($self, $where) = @_;
99              
100             # No seps, then when have to trust it.
101 0         0 my $sep = $self->{MBPP_separators}[0];
102 0 0       0 return 1 unless defined $sep;
103              
104 0         0 my $file = $self->{MBPP_file};
105 0         0 my $here = $file->tell;
106 0 0       0 $file->seek($where, 0) or return 0;
107              
108             # Find first non-empty line on specified location.
109 0         0 my $line = $file->getline;
110 0   0     0 $line = $file->getline while defined $line && $line =~ $empty;
111              
112             # Check completed, return to old spot.
113 0         0 $file->seek($here, 0);
114 0 0       0 return 1 unless defined $line;
115              
116 0 0 0     0 substr($line, 0, length $sep) eq $sep
117             && ($sep ne 'From ' || $line =~ m/ (?:19[6-9]|20[0-3])[0-9]\b/ );
118             }
119              
120             sub readSeparator()
121 20     20 1 34 { my $self = shift;
122              
123 20         43 my $sep = $self->{MBPP_separators}[0];
124 20 50       58 return () unless defined $sep;
125              
126 20         28 my $file = $self->{MBPP_file};
127 20         54 my $start = $file->tell;
128              
129 20         46 my $line = $file->getline;
130 20   33     218 while(defined $line && $line =~ $empty)
131 0         0 { $start = $file->tell;
132 0         0 $line = $file->getline;
133             }
134              
135 20 50       52 return () unless defined $line;
136              
137 20         92 $line =~ s/[\012\015]+$/\n/;
138 20 50       130 return ($start, $line)
139             if substr($line, 0, length $sep) eq $sep;
140              
141 0         0 $file->seek($start, 0);
142 0         0 ();
143             }
144              
145             sub _read_stripped_lines(;$$)
146 32     32   66 { my ($self, $exp_chars, $exp_lines) = @_;
147 32         53 my @seps = @{$self->{MBPP_separators}};
  32         73  
148              
149 32         54 my $file = $self->{MBPP_file};
150 32         52 my $lines = [];
151 32         52 my $msgend;
152              
153 32 100       82 if(@seps)
154             {
155             LINE:
156 20         31 while(1)
157 85         201 { my $where = $file->getpos;
158 85 50       202 my $line = $file->getline
159             or last LINE;
160              
161 85         158 foreach my $sep (@seps)
162 100 100       227 { next if substr($line, 0, length $sep) ne $sep;
163              
164             # Some apps fail to escape lines starting with From
165 20 50 33     64 next if $sep eq 'From ' && $line !~ m/ 19[789][0-9]| 20[0-9][0-9]/;
166              
167 20         75 $file->setpos($where);
168 20         52 $msgend = $file->tell;
169 20         49 last LINE;
170             }
171              
172 65         155 push @$lines, $line;
173             }
174              
175 20 100 66     133 if(@$lines && $lines->[-1] =~ s/(\r?\n)\z//)
176 14 100 66     63 { pop @$lines if @seps==1 && length($lines->[-1])==0;
177             }
178             }
179             else # File without separators.
180 12 50       65 { $lines = ref $file eq 'Mail::Box::FastScalar' ? $file->getlines : [ $file->getlines ];
181             }
182              
183 32         86 my $bodyend = $file->tell;
184 32 50       87 if($lines)
185 32 50       69 { if($self->{MBPP_strip_gt})
186 0         0 { s/^\>(\>*From\s)/$1/ for @$lines;
187             }
188 32 50       70 unless($self->{MBPP_trusted})
189 0         0 { s/\015$// for @$lines;
190             # input is read as binary stream (i.e. preserving CRLF on Windows).
191             # Code is based on this assumption. Removal of CR if not trusted
192             # conflicts with this assumption. [Markus Spann]
193             }
194             }
195              
196 32         114 ($bodyend, $lines, $msgend);
197             }
198              
199             sub _take_scalar($$)
200 0     0   0 { my ($self, $begin, $end) = @_;
201 0         0 my $file = $self->{MBPP_file};
202 0         0 $file->seek($begin, 0);
203              
204 0         0 my $return;
205 0         0 $file->read($return, $end-$begin);
206 0         0 $return =~ s/\015//g;
207 0         0 $return;
208             }
209              
210             sub bodyAsString(;$$)
211 0     0 1 0 { my ($self, $exp_chars, $exp_lines) = @_;
212 0         0 my $file = $self->{MBPP_file};
213 0         0 my $begin = $file->tell;
214              
215 0 0 0     0 if(defined $exp_chars && $exp_chars>=0)
216             { # Get at once may be successful
217 0         0 my $end = $begin + $exp_chars;
218              
219 0 0       0 if($self->_is_good_end($end))
220 0         0 { my $body = $self->_take_scalar($begin, $end);
221 0 0       0 $body =~ s/^\>(\>*From\s)/$1/gm if $self->{MBPP_strip_gt};
222 0         0 return ($begin, $file->tell, $body);
223             }
224             }
225              
226 0         0 my ($end, $lines) = $self->_read_stripped_lines($exp_chars, $exp_lines);
227 0         0 return ($begin, $end, join('', @$lines));
228             }
229              
230             sub bodyAsList(;$$)
231 32     32 1 66 { my ($self, $exp_chars, $exp_lines) = @_;
232 32         57 my $file = $self->{MBPP_file};
233 32         87 my $begin = $file->tell;
234              
235 32         90 my ($end, $lines) = $self->_read_stripped_lines($exp_chars, $exp_lines);
236 32         105 ($begin, $end, $lines);
237             }
238              
239             sub bodyAsFile($;$$)
240 0     0 1 0 { my ($self, $out, $exp_chars, $exp_lines) = @_;
241 0         0 my $file = $self->{MBPP_file};
242 0         0 my $begin = $file->tell;
243              
244 0         0 my ($end, $lines) = $self->_read_stripped_lines($exp_chars, $exp_lines);
245              
246 0         0 $out->print($_) for @$lines;
247 0         0 ($begin, $end, scalar @$lines);
248             }
249              
250             sub bodyDelayed(;$$)
251 0     0 1 0 { my ($self, $exp_chars, $exp_lines) = @_;
252 0         0 my $file = $self->{MBPP_file};
253 0         0 my $begin = $file->tell;
254              
255 0 0       0 if(defined $exp_chars)
256 0         0 { my $end = $begin + $exp_chars;
257              
258 0 0       0 if($self->_is_good_end($end))
259 0         0 { $file->seek($end, 0);
260 0         0 return ($begin, $end, $exp_chars, $exp_lines);
261             }
262             }
263              
264 0         0 my ($end, $lines) = $self->_read_stripped_lines($exp_chars, $exp_lines);
265 0         0 my $chars = sum(map length, @$lines);
266 0         0 ($begin, $end, $chars, scalar @$lines);
267             }
268              
269             sub openFile($)
270 12     12 1 25 { my ($self, $args) = @_;
271 12 50       32 my $mode = $args->{mode} or die "mode required";
272 12   33     44 my $fh = $args->{file} || IO::File->new($args->{filename}, $mode);
273              
274 12 50       30 return unless $fh;
275 12         20 $self->{MBPP_file} = $fh;
276              
277 12 50 33     98 $fh->binmode(':raw')
278             if $fh->can('binmode') || $fh->can('BINMODE');
279              
280 12         29 $self->{MBPP_separators} = [];
281              
282             # binmode $fh, ':crlf' if $] < 5.007; # problem with perlIO
283 12         34 $self;
284             }
285              
286             sub closeFile()
287 24     24 1 39 { my $self = shift;
288              
289 24         50 delete $self->{MBPP_separators};
290 24         42 delete $self->{MBPP_strip_gt};
291              
292 24 100       78 my $file = delete $self->{MBPP_file} or return;
293 12         52 $file->close;
294 12         24 $self;
295             }
296              
297             #------------------------------------------
298              
299              
300             sub fixHeaderErrors(;$)
301 0     0 1   { my $self = shift;
302 0 0         @_ ? ($self->{MBPP_fix} = shift) : $self->{MBPP_fix};
303             }
304              
305             #------------------------------------------
306              
307             1;