File Coverage

blib/lib/Mail/Box/Parser/Perl.pm
Criterion Covered Total %
statement 107 169 63.3
branch 31 72 43.0
condition 6 27 22.2
subroutine 17 23 73.9
pod 12 13 92.3
total 173 304 56.9


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