File Coverage

blib/lib/Mail/Server/IMAP4/Fetch.pm
Criterion Covered Total %
statement 112 143 78.3
branch 50 78 64.1
condition 17 37 45.9
subroutine 10 15 66.6
pod 9 9 100.0
total 198 282 70.2


line stmt bran cond sub pod time code
1             # Copyrights 2001-2019 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-Box-IMAP4. 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::Server::IMAP4::Fetch;
10 1     1   178420 use vars '$VERSION';
  1         7  
  1         56  
11             $VERSION = '3.006';
12              
13              
14 1     1   6 use strict;
  1         2  
  1         17  
15 1     1   4 use warnings;
  1         1  
  1         20  
16              
17 1     1   372 use Date::Parse;
  1         2304  
  1         114  
18 1     1   6 use Digest::MD5 qw/md5_base64/;
  1         2  
  1         1521  
19              
20              
21             sub new($)
22 7     7 1 95092 { my ($class, $part, %args) = @_;
23              
24 7         19 my $head = $part->head;
25 7         40 my $body = $part->body;
26 7         38 my $type = $body->type->study;
27              
28 7         1834 my $self = bless
29             { type => $type->body
30             , typeattr => [ $type->attrPairs ]
31             , charset => $body->charset
32             , bodylines => $body->nrLines
33             , bodysize => $body->size
34             }, $class;
35              
36 7         1239 $self->{headbegin} = ($head->fileLocation)[0];
37 7         52 @{$self}{qw/bodybegin bodyend/} = $body->fileLocation;
  7         61  
38              
39             # The fields use the defined() check, to avoid accidental expensive
40             # stringification by the field objects.
41              
42 7         11 my ($field, $value);
43 7 100       16 $self->{date} = $field->unfoldedBody
44             if defined($field = $head->get('Date'));
45              
46 7 100       182 $self->{subject} = $field->unfoldedBody
47             if defined($field = $head->get('Subject'));
48              
49 7 100       119 $self->{description} = $field->unfoldedBody
50             if defined($field = $head->get('Content-Description'));
51              
52 7 100       78 $self->{language} = $field->unfoldedBody
53             if defined($field = $head->get('Content-Language'));
54              
55 7 100       84 $self->{filename} = $value
56             if defined($value = $body->dispositionFilename);
57              
58             $self->{bodyMD5} = md5_base64($body->string)
59 7 50       2864 if $args{md5checksums};
60              
61 7 50       17 if(defined($field = $body->transferEncoding))
62 7         56 { my $tf = $field->unfoldedBody;
63 7 100       134 $self->{transferenc} = $tf unless $tf eq 'none';
64             }
65              
66             # Should become:
67             # $self->{disposition} = [ $field->body, $field->study->attributes ]
68 7 50       16 if(defined($field = $body->disposition))
69 7         55 { my $how = $field->body;
70 7 100       237 $how = $body->isText ? 'inline' : 'attachment' if $how eq 'none';
    100          
71 7         371 $self->{disposition} = [ $how, $field->attributes ];
72             }
73             else
74 0 0       0 { $self->{disposition} = [ ($body->isText ? 'inline' : 'attachment') ];
75             }
76              
77 7   66     182 my $id = $head->get('Content-Message-ID') || $head->get("Message-ID");
78 7 100       134 if(defined $id)
79 5         9 { my $msgid = $id->unfoldedBody;
80 5         81 $msgid =~ s/^\<*/
81 5         20 $msgid =~ s/\>*$/>/;
82 5 50       16 $self->{messageid} = $msgid if length $msgid;
83             }
84              
85 7         16 foreach my $addr ( qw/to from sender reply-to cc bcc/ )
86 42 100       452 { my $addrs = $head->study($addr) or next;
87 12         5398 foreach my $group ($addrs->groups)
88 11         58 { my @addrs = map { [ $_->phrase, $_->username, $_->domain ] }
  11         128  
89             $group->addresses;
90              
91 11         179 push @{$self->{$addr}}, [ $group->name, @addrs ];
  11         75  
92             }
93             }
94              
95 7 100       110 if($body->isMultipart)
    100          
96 1         4 { $self->{parts} = [ map { $class->new($_) } $body->parts ];
  2         22  
97             }
98             elsif($body->isNested)
99 1         4 { $self->{nest} = $class->new($body->nested);
100             }
101              
102 7         29 $self;
103             }
104              
105             #------------------------------------------
106              
107 0     0 1 0 sub headLocation() { @{ (shift) }{ qw/headbegin bodybegin/ } }
  0         0  
108 0     0 1 0 sub bodyLocation() { @{ (shift) }{ qw/bodybegin bodyend/ } }
  0         0  
109 0     0 1 0 sub partLocation() { @{ (shift) }{ qw/headbegin bodyend/ } }
  0         0  
110              
111             #------------------------------------------
112              
113             sub fetchBody($)
114 18     18 1 1644 { my ($self, $extended) = @_;
115              
116 18         39 my $type = uc $self->{type};
117 18         47 my ($mediatype, $subtype) = split m[/], $type;
118              
119 18 100       42 if($self->{parts})
120             { # Multipart message
121             # WARNING: no blanks between part descriptions
122 2         4 my $parts = join '', map $_->fetchBody($extended), @{$self->{parts}};
  2         10  
123 2   50     7 my @fields = (\$parts, $subtype || 'MIXED');
124              
125 2 100       6 if($extended) # only included when any valid info
126 1         3 { my @attr; # don't know what to include here
127             my @disp; # don't know about this either
128              
129             push @fields, \@attr, \@disp, $self->{language}
130 1 50 33     39 if @attr || @disp || defined $self->{language};
      33        
131             }
132              
133 2         6 return $self->_imapList(@fields);
134             }
135              
136             #
137             # Simple message
138             #
139              
140             my @fields =
141             ( ($mediatype || 'TEXT')
142             , ($subtype || 'PLAIN')
143             , $self->{typeattr}
144             , $self->{messageid}
145             , $self->{description}
146             , uc($self->{transferenc} || '8BIT')
147             , \($self->{bodysize})
148 16   50     77 );
      50        
      100        
149              
150 16 100       32 if(my $nest = $self->{nest})
151             { # type MESSAGE (message/rfc822 encapsulated)
152 2         5 push @fields
153             , \$nest->fetchEnvelope,
154             , \$nest->fetchBody($extended);
155             }
156 16         27 push @fields, \$self->{bodylines};
157              
158 8         14 push @fields, @{$self}{ qw/bodyMD5 disposition language/ }
159             if $extended
160 16 50 33     55 && ($self->{bodyMD5} || $self->{disposition} || $self->{language});
      66        
161              
162 16         33 $self->_imapList(@fields);
163             }
164              
165              
166             sub fetchEnvelope()
167 8     8 1 11 { my $self = shift;
168 8         22 my @fields = ($self->{date}, $self->{subject});
169              
170 8         17 foreach my $addr ( qw/from sender reply-to to cc bcc/ )
171 48 100       75 { unless($self->{$addr})
172 33         39 { push @fields, undef; # NIL
173 33         37 next;
174             }
175              
176             # For now, group information is ignored... RFC2060 is very
177             # unclear about it... and seems incompatible with RFC2822
178 15         20 my $addresses = '';
179 15         19 foreach my $group (@{$self->{$addr}})
  15         27  
180 15         25 { my ($name, @addr) = @$group;
181              
182             # addr_adl is obsoleted by rfc2822
183             $addresses .= $self->_imapList($_->[0], undef, $_->[1], $_->[2])
184 15         36 foreach @addr;
185             }
186              
187 15         26 push @fields, \$addresses;
188             }
189              
190 8         15 push @fields, $self->{'in-reply-to'}, $self->{messageid};
191              
192 8         16 $self->_imapList(@fields);
193             }
194              
195              
196 0     0 1 0 sub fetchSize() { shift->{bodysize} }
197              
198              
199             sub part(;$)
200 9     9 1 737 { my $self = shift;
201 9 100       25 my $nr = shift or return $self;
202              
203 7         19 my @nrs = split /\./, $nr;
204 7         17 while(@nrs)
205 8         15 { my $take = shift @nrs;
206 8 100 66     31 if(exists $self->{nest} && $take==1)
    100 100        
207 1         3 { $self = $self->{nest} }
208 5         17 elsif(exists $self->{parts} && @{$self->{parts}} >= $take)
209 4         14 { $self = $self->{parts}[$take-1] }
210 3         12 else { return undef }
211             }
212              
213 4         13 $self;
214             }
215              
216              
217             sub printStructure(;$$)
218 0     0 1 0 { my $self = shift;
219              
220 0 0       0 my $fh = @_ ? shift : select;
221 0 0       0 my $number = @_ ? shift : '';
222              
223 0         0 my $buffer; # only filled if filehandle==undef
224 0 0       0 open $fh, '>:raw', \$buffer unless defined $fh;
225              
226 0         0 my $type = $self->{type};
227 0   0     0 my $subject = $self->{subject} || '';
228 0         0 my $text = "$number $type: $subject\n";
229              
230 0   0     0 my $hbegin = $self->{headbegin} || 0;
231 0   0     0 my $bbegin = $self->{bodybegin} || '?';
232 0   0     0 my $bodyend = $self->{bodyend} || '?';
233 0 0       0 my $size = defined $self->{bodysize} ? $self->{bodysize} : '?';
234 0 0       0 my $lines = defined $self->{bodylines} ? $self->{bodylines} : '?';
235              
236 0         0 $text .= ' ' x (length($number) + 1);
237 0         0 $text .= "@ $hbegin-$bbegin-$bodyend, $size bytes, $lines lines\n";
238              
239 0 0       0 ref $fh eq 'GLOB' ? (print $fh $text) : $fh->print($text);
240              
241 0 0       0 if($self->{nest})
    0          
242 0 0       0 { $self->{nest}->printStructure($fh, length($number) ? $number.'.1' :'1');
243             }
244             elsif($self->{parts})
245 0         0 { my $count = 1;
246 0 0       0 $number .= '.' if length $number;
247             $_->printStructure($fh, $number.$count++)
248 0         0 foreach @{$self->{parts}};
  0         0  
249             }
250              
251 0         0 $buffer;
252             }
253              
254             #------------------------------------------
255              
256              
257             # Concatenate the elements of a list, as the IMAP protocol does.
258             # ARRAYS are included a sublist, and normal strings get quoted.
259             # Pass a ref-scalar if something needs to be included without
260             # quoting.
261              
262             sub _imapList(@)
263 65     65   82 { my $self = shift;
264 65         67 my @f;
265              
266 65         83 foreach (@_)
267 328 100       568 { if(ref $_ eq 'ARRAY') { push @f, $self->_imapList(@$_) }
  24 100       47  
    100          
268 53         57 elsif(ref $_ eq 'SCALAR') { push @f, ${$_} }
  53         76  
269 100         127 elsif(!defined $_) { push @f, 'NIL' }
270             else
271 151         167 { my $copy = $_;
272 151         186 $copy =~ s/\\/\\\\/g;
273 151         155 $copy =~ s/\"/\\"/g;
274 151         268 push @f, qq#"$_"#;
275             }
276             }
277              
278 65         76 local $" = ' ';
279 65         306 "(@f)";
280             }
281              
282             #------------------------------------------
283              
284              
285             1;