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-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-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   234480 use vars '$VERSION';
  1         10  
  1         50  
11             $VERSION = '3.008';
12              
13              
14 1     1   7 use strict;
  1         1  
  1         20  
15 1     1   5 use warnings;
  1         2  
  1         24  
16              
17 1     1   465 use Date::Parse;
  1         2877  
  1         134  
18 1     1   7 use Digest::MD5 qw/md5_base64/;
  1         1  
  1         1908  
19              
20              
21             sub new($)
22 7     7 1 121822 { my ($class, $part, %args) = @_;
23              
24 7         24 my $head = $part->head;
25 7         50 my $body = $part->body;
26 7         62 my $type = $body->type->study;
27              
28 7         1771 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         1614 $self->{headbegin} = ($head->fileLocation)[0];
37 7         63 @{$self}{qw/bodybegin bodyend/} = $body->fileLocation;
  7         119  
38              
39             # The fields use the defined() check, to avoid accidental expensive
40             # stringification by the field objects.
41              
42 7         16 my ($field, $value);
43 7 100       20 $self->{date} = $field->unfoldedBody
44             if defined($field = $head->get('Date'));
45              
46 7 100       214 $self->{subject} = $field->unfoldedBody
47             if defined($field = $head->get('Subject'));
48              
49 7 100       165 $self->{description} = $field->unfoldedBody
50             if defined($field = $head->get('Content-Description'));
51              
52 7 100       98 $self->{language} = $field->unfoldedBody
53             if defined($field = $head->get('Content-Language'));
54              
55 7 100       114 $self->{filename} = $value
56             if defined($value = $body->dispositionFilename);
57              
58             $self->{bodyMD5} = md5_base64($body->string)
59 7 50       3293 if $args{md5checksums};
60              
61 7 50       34 if(defined($field = $body->transferEncoding))
62 7         77 { my $tf = $field->unfoldedBody;
63 7 100       180 $self->{transferenc} = $tf unless $tf eq 'none';
64             }
65              
66             # Should become:
67             # $self->{disposition} = [ $field->body, $field->study->attributes ]
68 7 50       23 if(defined($field = $body->disposition))
69 7         73 { my $how = $field->body;
70 7 100       317 $how = $body->isText ? 'inline' : 'attachment' if $how eq 'none';
    100          
71 7         500 $self->{disposition} = [ $how, $field->attributes ];
72             }
73             else
74 0 0       0 { $self->{disposition} = [ ($body->isText ? 'inline' : 'attachment') ];
75             }
76              
77 7   66     243 my $id = $head->get('Content-Message-ID') || $head->get("Message-ID");
78 7 100       170 if(defined $id)
79 5         12 { my $msgid = $id->unfoldedBody;
80 5         117 $msgid =~ s/^\<*/
81 5         24 $msgid =~ s/\>*$/>/;
82 5 50       21 $self->{messageid} = $msgid if length $msgid;
83             }
84              
85 7         18 foreach my $addr ( qw/to from sender reply-to cc bcc/ )
86 42 100       588 { my $addrs = $head->study($addr) or next;
87 12         7253 foreach my $group ($addrs->groups)
88 11         82 { my @addrs = map { [ $_->phrase, $_->username, $_->domain ] }
  11         419  
89             $group->addresses;
90              
91 11         225 push @{$self->{$addr}}, [ $group->name, @addrs ];
  11         46  
92             }
93             }
94              
95 7 100       134 if($body->isMultipart)
    100          
96 1         4 { $self->{parts} = [ map { $class->new($_) } $body->parts ];
  2         28  
97             }
98             elsif($body->isNested)
99 1         5 { $self->{nest} = $class->new($body->nested);
100             }
101              
102 7         39 $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 2129 { my ($self, $extended) = @_;
115              
116 18         46 my $type = uc $self->{type};
117 18         60 my ($mediatype, $subtype) = split m[/], $type;
118              
119 18 100       53 if($self->{parts})
120             { # Multipart message
121             # WARNING: no blanks between part descriptions
122 2         6 my $parts = join '', map $_->fetchBody($extended), @{$self->{parts}};
  2         12  
123 2   50     9 my @fields = (\$parts, $subtype || 'MIXED');
124              
125 2 100       8 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     10 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     106 );
      50        
      100        
149              
150 16 100       44 if(my $nest = $self->{nest})
151             { # type MESSAGE (message/rfc822 encapsulated)
152 2         6 push @fields
153             , \$nest->fetchEnvelope,
154             , \$nest->fetchBody($extended);
155             }
156 16         34 push @fields, \$self->{bodylines};
157              
158 8         21 push @fields, @{$self}{ qw/bodyMD5 disposition language/ }
159             if $extended
160 16 50 33     72 && ($self->{bodyMD5} || $self->{disposition} || $self->{language});
      66        
161              
162 16         46 $self->_imapList(@fields);
163             }
164              
165              
166             sub fetchEnvelope()
167 8     8 1 21 { my $self = shift;
168 8         23 my @fields = ($self->{date}, $self->{subject});
169              
170 8         22 foreach my $addr ( qw/from sender reply-to to cc bcc/ )
171 48 100       103 { unless($self->{$addr})
172 33         46 { push @fields, undef; # NIL
173 33         51 next;
174             }
175              
176             # For now, group information is ignored... RFC2060 is very
177             # unclear about it... and seems incompatible with RFC2822
178 15         23 my $addresses = '';
179 15         25 foreach my $group (@{$self->{$addr}})
  15         30  
180 15         32 { my ($name, @addr) = @$group;
181              
182             # addr_adl is obsoleted by rfc2822
183             $addresses .= $self->_imapList($_->[0], undef, $_->[1], $_->[2])
184 15         43 foreach @addr;
185             }
186              
187 15         47 push @fields, \$addresses;
188             }
189              
190 8         18 push @fields, $self->{'in-reply-to'}, $self->{messageid};
191              
192 8         20 $self->_imapList(@fields);
193             }
194              
195              
196 0     0 1 0 sub fetchSize() { shift->{bodysize} }
197              
198              
199             sub part(;$)
200 9     9 1 862 { my $self = shift;
201 9 100       33 my $nr = shift or return $self;
202              
203 7         24 my @nrs = split /\./, $nr;
204 7         19 while(@nrs)
205 8         17 { my $take = shift @nrs;
206 8 100 66     40 if(exists $self->{nest} && $take==1)
    100 100        
207 1         4 { $self = $self->{nest} }
208 5         20 elsif(exists $self->{parts} && @{$self->{parts}} >= $take)
209 4         15 { $self = $self->{parts}[$take-1] }
210 3         15 else { return undef }
211             }
212              
213 4         18 $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   101 { my $self = shift;
264 65         84 my @f;
265              
266 65         107 foreach (@_)
267 328 100       737 { if(ref $_ eq 'ARRAY') { push @f, $self->_imapList(@$_) }
  24 100       77  
    100          
268 53         69 elsif(ref $_ eq 'SCALAR') { push @f, ${$_} }
  53         91  
269 100         184 elsif(!defined $_) { push @f, 'NIL' }
270             else
271 151         238 { my $copy = $_;
272 151         239 $copy =~ s/\\/\\\\/g;
273 151         188 $copy =~ s/\"/\\"/g;
274 151         328 push @f, qq#"$_"#;
275             }
276             }
277              
278 65         102 local $" = ' ';
279 65         382 "(@f)";
280             }
281              
282             #------------------------------------------
283              
284              
285             1;