File Coverage

blib/lib/Mail/Box/Parser/Perl.pm
Criterion Covered Total %
statement 21 174 12.0
branch 0 64 0.0
condition 0 49 0.0
subroutine 7 29 24.1
pod 17 18 94.4
total 45 334 13.4


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Message version 4.04.
2             # The POD got stripped from this file by OODoc version 3.06.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2026 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Box::Parser::Perl;{
13             our $VERSION = '4.04';
14             }
15              
16 2     2   24152 use parent 'Mail::Box::Parser';
  2         5  
  2         20  
17              
18 2     2   144 use strict;
  2         4  
  2         52  
19 2     2   10 use warnings;
  2         4  
  2         142  
20              
21 2     2   12 use Log::Report 'mail-message', import => [ qw/__x error fault info warning trace/ ];
  2         4  
  2         14  
22              
23 2     2   542 use List::Util qw/sum/;
  2         3  
  2         185  
24 2     2   577 use IO::File ();
  2         1282  
  2         70  
25              
26 2     2   15 use Mail::Message::Field ();
  2         5  
  2         6404  
27              
28             my $empty_line = qr/^\015?\012?$/;
29              
30             #--------------------
31              
32             sub init(@)
33 0     0 0   { my ($self, $args) = @_;
34 0           $self->SUPER::init($args);
35              
36 0   0       $self->{MBPP_mode} = $args->{mode} || 'r';
37             $self->{MBPP_filename} = $args->{filename} || ref $args->{file}
38 0 0 0       or error __x"filename or handle required to create a parser.";
39              
40 0           $self->start(file => $args->{file});
41 0           $self;
42             }
43              
44             #--------------------
45              
46 0     0 1   sub filename() { $_[0]->{MBPP_filename} }
47 0     0 1   sub openMode() { $_[0]->{MBPP_mode} }
48 0     0 1   sub file() { $_[0]->{MBPP_file} }
49              
50             #--------------------
51              
52             sub start(@)
53 0     0 1   { my ($self, %args) = @_;
54 0 0         $self->openFile(%args) or return;
55 0           $self->takeFileInfo;
56              
57 0           trace "opened folder ".$self->filename." to be parsed";
58 0           $self;
59             }
60              
61              
62             sub stop()
63 0     0 1   { my $self = shift;
64 0           trace "close parser for file " . $self->filename;
65 0           $self->closeFile;
66             }
67              
68              
69             sub restart()
70 0     0 1   { my $self = shift;
71 0           $self->closeFile;
72 0 0         $self->openFile(@_) or return;
73 0           $self->takeFileInfo;
74 0           trace "restarted parser for file " . $self->filename;
75 0           $self;
76             }
77              
78              
79             sub fileChanged()
80 0     0 1   { my $self = shift;
81 0           my ($size, $mtime) = (stat $self->filename)[7,9];
82 0 0 0       return 0 if !defined $size || !defined $mtime;
83 0 0         $size != $self->{MBPP_size} || $mtime != $self->{MBPP_mtime};
84             }
85              
86              
87             sub filePosition(;$)
88 0     0 1   { my $self = shift;
89 0 0         @_ ? $self->file->seek(shift, 0) : $self->file->tell;
90             }
91              
92              
93             sub readHeader()
94 0     0 1   { my $self = shift;
95 0 0         my $file = $self->file or return ();
96 0           my @ret = ($file->tell, undef);
97 0           my $line = $file->getline;
98              
99             LINE:
100 0           while(defined $line)
101 0 0         { last LINE if $line =~ $empty_line;
102 0           my ($name, $body) = split /\s*\:\s*/, $line, 2;
103              
104 0 0         unless(defined $body)
105 0           { warning __x"unexpected end of header in {file}:\n {line}", file => $self->filename, line => $line;
106              
107 0 0 0       if(@ret && $self->fixHeaderErrors)
108 0           { $ret[-1][1] .= ' '.$line; # glue err line to previous field
109 0           $line = $file->getline;
110 0           next LINE;
111             }
112              
113 0           $file->seek(-length $line, 1);
114 0           last LINE;
115             }
116              
117 0 0         length $body or $body = "\n";
118              
119             # Collect folded lines
120 0           while($line = $file->getline)
121 0 0         { $line =~ m!^[ \t]! ? ($body .= $line) : last;
122             }
123              
124 0           $body =~ s/\015//g;
125 0           push @ret, [ $name, $body ];
126             }
127              
128 0           $ret[1] = $file->tell;
129 0           @ret;
130             }
131              
132             sub _is_good_end($)
133 0     0     { my ($self, $where) = @_;
134              
135             # No seps, then when have to trust it.
136 0   0       my $sep = $self->activeSeparator // return 1;
137 0           my $file = $self->file;
138 0           my $here = $file->tell;
139 0 0         $file->seek($where, 0) or return 0;
140              
141             # Find first non-empty line on specified location.
142 0           my $line = $file->getline;
143 0   0       $line = $file->getline while defined $line && $line =~ $empty_line;
144              
145             # Check completed, return to old spot.
146 0           $file->seek($here, 0);
147 0   0       $line // return 1;
148              
149 0 0 0       substr($line, 0, length $sep) eq $sep
150             && ($sep ne 'From ' || $line =~ m/ (?:19[6-9]|20[0-3])[0-9]\b/ );
151             }
152              
153             sub readSeparator()
154 0     0 1   { my $self = shift;
155 0   0       my $sep = $self->activeSeparator // return ();
156 0           my $file = $self->file;
157 0           my $start = $file->tell;
158              
159 0           my $line = $file->getline;
160 0   0       while(defined $line && $line =~ $empty_line)
161 0           { $start = $file->tell;
162 0           $line = $file->getline;
163             }
164              
165 0   0       $line // return ();
166 0           $line =~ s/[\012\015]+$/\n/;
167              
168 0 0         substr($line, 0, length $sep) eq $sep
169             and return ($start, $line);
170              
171 0           $file->seek($start, 0);
172 0           ();
173             }
174              
175             sub _readStrippedLines(;$$)
176 0     0     { my ($self, $exp_chars, $exp_lines) = @_;
177 0           my $seps = $self->separators;
178 0           my $file = $self->file;
179 0           my $lines = [];
180 0           my $msgend;
181              
182 0 0         if(@$seps)
183             {
184             LINE:
185 0           while(1)
186 0           { my $where = $file->getpos;
187 0 0         my $line = $file->getline or last LINE;
188              
189 0           foreach my $sep (@$seps)
190 0 0         { substr($line, 0, length $sep) eq $sep or next;
191              
192             # Some apps fail to escape lines starting with From
193 0 0 0       next if $sep eq 'From ' && $line !~ m/ 19[789][0-9]| 20[0-9][0-9]/;
194              
195 0           $file->setpos($where);
196 0           $msgend = $file->tell;
197 0           last LINE;
198             }
199              
200 0           push @$lines, $line;
201             }
202              
203 0 0 0       if(@$lines && $lines->[-1] =~ s/\015?\012\z//)
204             { # Keep an empty line to signal the existence of a preamble, but
205             # remove a second.
206 0 0 0       pop @$lines if @$seps==1 && @$lines > 1 && length($lines->[-1])==0;
      0        
207             }
208             }
209             else # File without separators.
210 0 0         { $lines = ref $file eq 'Mail::Box::FastScalar' ? $file->getlines : [ $file->getlines ];
211             }
212              
213 0           my $bodyend = $file->tell;
214 0 0         if($self->stripGt)
215 0           { s/^\>(\>*From\s)/$1/ for @$lines;
216             }
217              
218 0 0         unless($self->trusted)
219 0           { s/\015$// for @$lines;
220             # input is read as binary stream (i.e. preserving CRLF on Windows).
221             # Code is based on this assumption. Removal of CR if not trusted
222             # conflicts with this assumption. [Markus Spann]
223             }
224              
225 0           ($bodyend, $lines, $msgend);
226             }
227              
228             sub _take_scalar($$)
229 0     0     { my ($self, $begin, $end) = @_;
230 0           my $file = $self->file;
231 0           $file->seek($begin, 0);
232              
233 0           my $buffer;
234 0           $file->read($buffer, $end-$begin);
235 0           $buffer =~ s/\015//gr;
236             }
237              
238             sub bodyAsString(;$$)
239 0     0 1   { my ($self, $exp_chars, $exp_lines) = @_;
240 0           my $file = $self->file;
241 0           my $begin = $file->tell;
242              
243 0 0 0       if(defined $exp_chars && $exp_chars>=0)
244             { # Get at once may be successful
245 0           my $end = $begin + $exp_chars;
246              
247 0 0         if($self->_is_good_end($end))
248 0           { my $body = $self->_take_scalar($begin, $end);
249 0 0         $body =~ s/^\>(\>*From\s)/$1/gm if $self->stripGt;
250 0           return ($begin, $file->tell, $body);
251             }
252             }
253              
254 0           my ($end, $lines) = $self->_readStrippedLines($exp_chars, $exp_lines);
255 0           ($begin, $end, join('', @$lines));
256             }
257              
258             sub bodyAsList(;$$)
259 0     0 1   { my ($self, $exp_chars, $exp_lines) = @_;
260 0           my $file = $self->file;
261 0           my $begin = $file->tell;
262              
263 0           my ($end, $lines) = $self->_readStrippedLines($exp_chars, $exp_lines);
264 0           ($begin, $end, $lines);
265             }
266              
267             sub bodyAsFile($;$$)
268 0     0 1   { my ($self, $out, $exp_chars, $exp_lines) = @_;
269 0           my $file = $self->file;
270 0           my $begin = $file->tell;
271              
272 0           my ($end, $lines) = $self->_readStrippedLines($exp_chars, $exp_lines);
273              
274 0           $out->print($_) for @$lines;
275 0           ($begin, $end, scalar @$lines);
276             }
277              
278             sub bodyDelayed(;$$)
279 0     0 1   { my ($self, $exp_chars, $exp_lines) = @_;
280 0           my $file = $self->file;
281 0           my $begin = $file->tell;
282              
283 0 0         if(defined $exp_chars)
284 0           { my $end = $begin + $exp_chars;
285              
286 0 0         if($self->_is_good_end($end))
287 0           { $file->seek($end, 0);
288 0           return ($begin, $end, $exp_chars, $exp_lines);
289             }
290             }
291              
292 0           my ($end, $lines) = $self->_readStrippedLines($exp_chars, $exp_lines);
293 0           my $chars = sum(map length, @$lines);
294 0           ($begin, $end, $chars, scalar @$lines);
295             }
296              
297              
298             sub openFile(%)
299 0     0 1   { my ($self, %args) = @_;
300              
301             #XXX IO::File is hard to remove because of the mode to be translated
302             my $fh = $self->{MBPP_file} = $args{file} ||
303 0 0 0       IO::File->new($self->filename, $args{mode} || $self->openMode)
304             or return;
305              
306 0 0 0       $fh->binmode(':raw')
307             if $fh->can('binmode') || $fh->can('BINMODE');
308              
309 0           $self->resetSeparators;
310 0           $self;
311             }
312              
313              
314             sub closeFile()
315 0     0 1   { my $self = shift;
316 0           $self->resetSeparators;
317              
318 0 0         my $file = delete $self->{MBPP_file} or return;
319 0           $file->close;
320 0           $self;
321             }
322              
323              
324             sub takeFileInfo()
325 0     0 1   { my $self = shift;
326 0           @$self{ qw/MBPP_size MBPP_mtime/ } = (stat $self->filename)[7,9];
327             }
328              
329             #--------------------
330              
331             #--------------------
332              
333             sub DESTROY
334 0     0     { my $self = shift;
335 0           $self->stop;
336 0           $self->SUPER::DESTROY;
337             }
338              
339             1;