File Coverage

blib/lib/MIME/Explode.pm
Criterion Covered Total %
statement 15 254 5.9
branch 0 194 0.0
condition 0 101 0.0
subroutine 5 15 33.3
pod 4 9 44.4
total 24 573 4.1


line stmt bran cond sub pod time code
1             #
2             # Explode.pm
3             # Last Modification: Sun Jun 26 21:19:40 WEST 2011
4             #
5             # Copyright (c) 2011 Henrique Dias .
6             # All rights reserved.
7             # This module is free software; you can redistribute it and/or modify
8             # it under the same terms as Perl itself.
9             #
10             package MIME::Explode;
11              
12 1     1   901 use strict;
  1         2  
  1         33  
13 1     1   5 use Carp;
  1         1  
  1         92  
14              
15             require Exporter;
16             require DynaLoader;
17             require AutoLoader;
18 1     1   864 use SelfLoader;
  1         18013  
  1         62  
19              
20 1     1   9 use vars qw($VERSION @ISA @EXPORT);
  1         1  
  1         80  
21              
22             @ISA = qw(Exporter DynaLoader);
23             @EXPORT = qw(&rfc822_base64 &rfc822_qprint);
24             $VERSION = '0.39';
25              
26 1     1   21 use constant BUFFSIZE => 64;
  1         2  
  1         4462  
27              
28             my %h_hash = (
29             'content-type' => "",
30             'content-disposition' => "",
31             'content-transfer-encoding' => "",
32             );
33              
34             my @patterns = (
35             '^([^= ]+) *=[ \"]*([^\"]+)',
36             '^(\w[\w\-\.]*):[\x20\x09]*([^\x0d\x0a\f]*)[\x0d\x0a\f]+',
37             '^[\x0a\x0d]+$',
38             '^begin\s*(\d\d\d)\s*(\S+)',
39             '^From +[^ ]+ +[a-zA-Z]{3} [a-zA-Z]{3} [ \d]\d \d\d:\d\d:\d\d \d{4}( [\+\-]\d\d\d\d)?[\x0a\x0d]+',
40             '^[\x20\x09]+(?=.*[^\x0a\x0d]+)',
41             '^[\x20\x09]+\w+\=[^\=]+'
42             );
43              
44             my %content_type = (
45             "text/html" => ".html",
46             "text/plain" => ".txt",
47             "message/rfc822" => ".rfc822",
48             "text/richtext" => ".richtext",
49             );
50              
51             SelfLoader->load_stubs();
52              
53             sub new {
54 0     0 1   my $proto = shift;
55 0   0       my $class = ref($proto) || $proto;
56 0           my $self = {
57             output_dir => "/tmp",
58             mkdir => 0755,
59             decode_subject => 0,
60             check_content_type => 0,
61             content_types => [],
62             types_action => "include",
63             @_,
64             };
65 0           bless($self, $class);
66 0           $self->init();
67 0           return($self);
68             }
69              
70             sub init {
71 0     0 0   my $self = shift;
72 0 0 0       return() if((-d $self->{'output_dir'}) || !$self->{'mkdir'});
73 0 0         mkdir($self->{'output_dir'}, $self->{'mkdir'}) or
74             die(join("", "MIME::Explode: Failed to create directory \"", $self->{'output_dir'}, "\": $!"));
75 0           return();
76             }
77              
78             sub clean_all {
79 0     0 1   my $self = shift;
80              
81 0           my $dir = $self->{'output_dir'};
82 0 0         opendir(DIRECTORY, $dir) or return("Can't opendir \"$dir\": $!\n");
83 0           while(defined(my $file = readdir(DIRECTORY))) {
84 0 0         next if($file =~ /^\.\.?$/);
85 0           my $path = "$dir/$file";
86 0 0         if(my ($f) = ($path =~ /^(.+)$/)) {
87 0 0         unlink($f) or return("Couldn't unlink \"$f\" file: $!");
88             }
89             }
90 0           closedir(DIRECTORY);
91 0 0         rmdir($dir) or return("Couldn't rmdir \"$dir\" directory: $!");
92 0           return();
93             }
94              
95             sub parse {
96 0     0 1   my $self = shift;
97              
98 0           local $/ = "\n";
99 0           my %headers = ();
100 0 0 0       my %args = (
101             'output_dir' => $self->{'output_dir'},
102             'check_ctype' => $self->{'check_content_type'} || 0,
103             'decode_subject' => $self->{'decode_subject'},
104             'ctypes' => {},
105             'types_action' => $self->{'types_action'} eq "include" ? 1 : 0,
106             );
107 0 0 0       $self->{'content_types'} = $self->{'exclude_types'} if(exists($self->{'exclude_types'}) && scalar(@{$self->{'exclude_types'}}));
  0            
108 0 0         if(scalar(@{$self->{'content_types'}})) {
  0            
109 0           my %ctypes = ();
110 0           @ctypes{@{$self->{'content_types'}}} = (0 .. $#{$self->{'content_types'}});
  0            
  0            
111 0           $args{'ctypes'} = \%ctypes;
112             }
113 0           my $last = &_parse(\@_, 1, 0, "0", "", \%args, {}, \%headers);
114 0 0         $self->{nmsgs} = ($last->[0]) ? (split(/\./, $last->[0]))[0] + 1 : 0;
115 0           my ($fh_mail, $fh_tmp) = @_;
116 0 0         if(defined($fh_tmp)) { while(<$fh_mail>) { print $fh_tmp $_; } }
  0            
  0            
117 0           return(\%headers);
118             }
119              
120 0     0 1   sub nmsgs { $_[0]->{'nmsgs'} }
121              
122             sub _parse {
123 0     0     my $fhs = shift;
124 0           my $header = shift;
125 0   0       my $mbox = shift || 0;
126 0   0       my $base = shift || "0";
127 0   0       my $origin = shift || "";
128 0           my $args = shift;
129 0           my $files = shift;
130              
131 0           my ($fh_mail, $fh_tmp) = @{$fhs};
  0            
132 0           my ($tree, $key, $tmpbuff, $boundary, $ftmp) = (join("\.", $base, "0"), "", "", "", "");
133 0           my ($check_ctype, $ctlength) = (1, 0);
134 0           my ($ph, $tmp, $exclude, $attcount, $checkhdr) = (0, 0, 0, 0, 0);
135 0           my $fh;
136 0           while(local $_ = <$fh_mail>) {
137 0 0         defined($fh_tmp) and print $fh_tmp $_;
138 0 0         if($header) {
139 0           ($ph, $attcount, $exclude, $tmpbuff, $check_ctype, $ctlength, $ftmp) = (1, 0, 0, "", 1, 0, "");
140 0 0 0       if(!$mbox && $base eq "0" && /$patterns[4]/o) { $mbox = 1; next; }
  0   0        
  0            
141 0 0         if(exists($_[0]->{$tree}->{$key})) {
    0          
142 0           s/\x0d//og;
143 0 0         if(s/$patterns[5]/ /o) {
144 0           s/\s+$//o;
145 0 0         if(ref($_[0]->{$tree}->{$key}) eq "ARRAY") {
146 0           $_[0]->{$tree}->{$key}->[$#{$_[0]->{$tree}->{$key}}] .= $_;
  0            
147 0           next;
148             }
149 0 0         if(ref($_[0]->{$tree}->{$key}) eq "HASH") { $_[0]->{$tree}->{$key}->{value} .= $_; }
  0            
150             else {
151 0 0 0       $key eq "subject" and $_[0]->{$tree}->{$key} =~ /\?\=$/o and s/^ (?=\=\?)//o;
152 0           $_[0]->{$tree}->{$key} .= $_;
153             }
154 0           next;
155             }
156 0 0 0       if(exists($h_hash{$key}) && exists($_[0]->{$tree}->{$key}->{value})) {
    0 0        
157 0           &header2hash($_[0]->{$tree}->{$key}, $_[0]->{$tree}->{$key}->{value});
158             } elsif($key eq "subject" && $args->{decode_subject}) {
159 0           my @parts = &decode_mimewords($_[0]->{$tree}->{subject});
160 0           delete($_[0]->{$tree}->{subject});
161 0 0         $_[0]->{$tree}->{subject}->{value} = [map {$_->[0] || ""} @parts];
  0            
162 0 0         $_[0]->{$tree}->{subject}->{charset} = [map {$_->[1] || "us-ascii"} @parts];
  0            
163             }
164 0           } elsif(/$patterns[6]/o) { next; }
165              
166 0 0         if(/$patterns[1]/o) {
167 0 0         defined($fh) and &file_close($fh);
168 0           ($header, $checkhdr) = (1, 1);
169 0           $key = lc($1);
170 0 0 0       if($key eq "received" || $key eq "x-received") {
171 0           push(@{$_[0]->{$tree}->{$key}}, $2);
  0            
172 0           next;
173             }
174 0 0         unless(exists($_[0]->{$tree}->{$key})) {
175 0 0         $_[0]->{$tree}->{$key} = (exists($h_hash{$key})) ? {value => $2} : $2;
176             }
177 0           next;
178             }
179 0 0 0       next if(!$checkhdr && (length() <= 2) && /$patterns[2]/o);
      0        
180 0           $header = 0;
181 0 0 0       if(exists($_[0]->{$tree}->{'content-type'}) && exists($_[0]->{$tree}->{'content-type'}->{value})) {
182 0           $_[0]->{$tree}->{'content-type'}->{value} = lc($_[0]->{$tree}->{'content-type'}->{value});
183 0 0 0       if(exists($_[0]->{$tree}->{'content-type'}->{boundary}) && $_[0]->{$tree}->{'content-type'}->{value} =~ /multipart\/\w+/o) {
    0          
184 0           my $res = &_parse($fhs, $header, $mbox, $tree, $_[0]->{$tree}->{'content-type'}->{boundary}, $args, $files, $_[0]);
185 0 0         if($res->[1]) {
186 0 0         $mbox ? ($tmp = 1) : return([$tree, $res->[1]]);
187 0           $_ = $res->[1];
188 0           } else { next; }
189             } elsif($_[0]->{$tree}->{'content-type'}->{value} eq "message/rfc822") {
190 0           my $res = &_parse($fhs, 1, $mbox, $tree, $origin, $args, $files, $_[0]);
191 0 0         if($res->[1]) {
192 0 0         $mbox ? ($tmp = 1) : return([$tree, $res->[1]]);
193 0           $_ = $res->[1];
194 0           } else { next; }
195             }
196             }
197             }
198 0           $checkhdr = 0;
199 0           $key = "";
200 0 0         defined($_) or next;
201 0 0         if(/$patterns[3]/o) {
202 0           my $file = &check_filename($files, $2);
203 0 0         my $filepath = ($args->{output_dir}) ? join("/", $args->{output_dir}, $file) : $file;
204 0   0       my $res = uu_file($fhs, $filepath, $1 || "644",
205             {
206             action => $args->{'types_action'},
207             mimetypes => $args->{'ctypes'}
208             }
209             );
210 0           $_[0]->{"$tree.$attcount"}->{'content-type'}->{value} = $res->[0];
211 0 0         $_[0]->{"$tree.$attcount"}->{'content-disposition'}->{filepath} = $filepath unless($res->[1]);
212 0           $attcount++;
213 0           next;
214             }
215 0           my $breakmsg = "";
216 0 0         unless(defined($fh)) {
217 0           $boundary = $origin;
218 0 0 0       if(exists($_[0]->{$tree}->{'content-type'}) && exists($_[0]->{$tree}->{'content-type'}->{value})) {
219 0 0 0       $exclude = 1 if(($_[0]->{$tree}->{'content-type'}->{value} =~ /^multipart\/\w+$/o) || ($_[0]->{$tree}->{'content-type'}->{value} eq "message/rfc822"));
220 0           } else { $check_ctype = 1; }
221 0 0         unless($exclude) {
222 0 0 0       if(exists($_[0]->{$tree}->{'content-transfer-encoding'}) &&
223             exists($_[0]->{$tree}->{'content-transfer-encoding'}->{value})) {
224 0           $_[0]->{$tree}->{'content-transfer-encoding'}->{value} = lc($_[0]->{$tree}->{'content-transfer-encoding'}->{value});
225 0 0 0       if($_[0]->{$tree}->{'content-transfer-encoding'}->{value} eq "base64" ||
      0        
226             ($_[0]->{$tree}->{'content-transfer-encoding'}->{value} eq "quoted-printable" && $boundary)) {
227 0           &set_filename($files, $_[0]->{$tree});
228 0 0         my $filepath = ($args->{output_dir}) ? join("/", $args->{output_dir}, $_[0]->{$tree}->{'content-disposition'}->{filename}) : $_[0]->{$tree}->{'content-disposition'}->{filename};
229 0 0 0       my $res = &decode_content($fhs,
230             $_[0]->{$tree}->{'content-transfer-encoding'}->{value},
231             $filepath,
232             $boundary ? "--$boundary" : "",
233             {
234             mimetype => $_[0]->{$tree}->{'content-type'}->{value} || "",
235             checktype => $args->{'check_ctype'},
236             action => $args->{'types_action'},
237             mimetypes => $args->{'ctypes'},
238             mailbox => $mbox
239             });
240 0 0         $_[0]->{$tree}->{'content-type'}->{value} = $res->[1] if($res->[1]);
241 0 0         $_[0]->{$tree}->{'content-disposition'}->{filepath} = $filepath unless($res->[2]);
242 0           $tmp = 1;
243 0 0         unless($_ = $res->[0]) {
244 0           $exclude = 1;
245 0           next;
246             }
247 0 0 0       if($mbox && /$patterns[4]/o && scalar(@{[split(/\./o, $tree)]}) > 2) {
  0   0        
248 0           $breakmsg = $_;
249 0           $_ = "--$boundary--\r\n";
250             }
251             }
252             }
253             }
254             }
255 0 0 0       if($mbox && /$patterns[4]/o) {
256 0 0         if(scalar(@{[split(/\./o, $tree)]}) > 2) {
  0            
257 0           $breakmsg = $_;
258 0 0         $boundary ? ($_ = "--$boundary--\r\n") : return([$tree, $breakmsg]);
259             } else {
260 0 0         defined($fh) and &file_close($fh);
261 0           $header = 1;
262 0           my @ps = split(/\./o, $tree);
263 0           $tree = join(".", ++$ps[0], "0");
264 0           next;
265             }
266             }
267 0 0 0       $tmp = ((length() <= 2) && /$patterns[2]/o) ? 1 : 0;
268 0 0 0       (defined($fh) || !$tmp) or next;
269 0 0         if($boundary) {
270 0 0         if(index($_, "--$boundary--") >= 0) {
271 0 0         defined($fh) and &file_close($fh);
272 0 0 0       if($mbox && scalar(@{[split(/\./o, $tree)]}) == 2) {
  0            
273 0           ($tmp, $exclude) = (1, 1);
274 0           $boundary = "";
275 0           next;
276 0           } else { return([$tree, $breakmsg]); }
277             }
278 0 0         if(index($_, "--$boundary") >= 0) {
279 0 0         defined($fh) and &file_close($fh);
280 0           ($tmp, $header) = (1, 1);
281 0           $boundary = "";
282 0 0         if($ph) {
283 0 0         return([$tree]) if($_[0]->{$base}->{'content-type'}->{value} eq "message/rfc822");
284 0           my @ps = split(/\./o, $tree);
285 0           $ps[$#ps]++;
286 0           $tree = join("\.", @ps);
287             }
288 0           next;
289             }
290             }
291 0 0 0       (!$exclude && $ph) or next;
292 0 0 0       if($check_ctype && $args->{check_ctype}) {
293 0           ($tmpbuff .= $_) =~ s/^[\n\r\t]+//o;
294 0 0         if(length($tmpbuff) > BUFFSIZE) {
295 0   0       $_[0]->{$tree}->{'content-type'}->{value} ||= "";
296 0 0         if(my $ct = set_content_type($tmpbuff, $_[0]->{$tree}->{'content-type'}->{value})) {
297 0           $_[0]->{$tree}->{'content-type'}->{value} = $ct;
298 0           $tmpbuff = "";
299 0           $check_ctype = 0;
300             }
301 0 0         if($exclude = exists($args->{'ctypes'}->{$_[0]->{$tree}->{'content-type'}->{value}}) ? ($args->{'types_action'} ? 0 : 1) :
  0 0          
    0          
    0          
    0          
    0          
302             scalar(keys(%{$args->{'ctypes'}})) ? ($args->{'types_action'} ? 1 : 0) : ($args->{'types_action'} ? 0 : 1)) {
303 0 0         if(defined($fh)) {
304 0           &file_close($fh);
305 0           unlink($_[0]->{$tree}->{'content-disposition'}->{filepath});
306 0           delete($_[0]->{$tree}->{'content-disposition'}->{filepath});
307             }
308 0           next;
309             }
310             }
311             }
312 0 0         unless(defined($fh)) {
313 0           &set_filename($files, $_[0]->{$tree});
314 0 0         $_[0]->{$tree}->{'content-disposition'}->{filepath} = ($args->{output_dir}) ?
315             join("/", $args->{output_dir}, $_[0]->{$tree}->{'content-disposition'}->{filename}) :
316             $_[0]->{$tree}->{'content-disposition'}->{filename};
317 0 0         defined($fh) and &file_close($fh);
318 0           $fh = &file_open($_[0]->{$tree}->{'content-disposition'}->{filepath});
319             }
320 0 0         if(defined($fh)) {
321 0 0 0       if(!$ftmp && (length() <= 2) && /$patterns[2]/o) {
      0        
322 0           $ftmp .= $_;
323 0           next;
324             }
325 0 0         if($ftmp) {
326 0           $_ = join("", $ftmp, $_);
327 0           $ftmp = "";
328             }
329 0 0         print $fh ($_[0]->{$tree}->{'content-transfer-encoding'}->{value} eq "quoted-printable") ? rfc822_qprint($_) : $_;
330 0 0         exists($_[0]->{$tree}->{'content-length'}) or next;
331 0 0         if(($ctlength += length()) >= $_[0]->{$tree}->{'content-length'}) {
332 0 0         defined($fh) and &file_close($fh);
333 0           $exclude = 1;
334 0           next;
335             }
336             }
337             }
338 0 0         defined($fh) and &file_close($fh);
339 0           return([$tree, ""]);
340             }
341              
342             sub file_close {
343 0     0 0   close($_[0]);
344 0           undef($_[0]);
345             }
346              
347             sub file_open {
348 0     0 0   my $path = shift;
349 0           local *FILE;
350              
351 0 0         if($path =~ /^(.+)$/) { $path = $1; }
  0            
352 0 0         open(FILE, ">$path") or die("MIME::Explode: Couldn't open $path for writing: $!\n");
353 0           binmode(FILE);
354 0           return *FILE;
355             }
356              
357             sub header2hash {
358 0     0 0   my $header = pop;
359              
360 0           my $params = semicolon_split($header);
361 0   0       $_[0]->{value} = shift(@{$params}) || "";
362 0 0         map {/$patterns[0]/o and $_[0]->{lc($1)} = $2; } @{$params};
  0            
  0            
363 0           return();
364             }
365              
366             sub set_filename {
367 0     0 0   my $files = shift;
368 0           my $h = shift;
369              
370 0           my $file = "file";
371 0 0         if(exists($h->{'content-disposition'}->{filename})) {
    0          
    0          
372 0           $file = $h->{'content-disposition'}->{filename};
373             } elsif(exists($h->{'content-type'}->{name})) {
374 0           $file = $h->{'content-type'}->{name};
375             } elsif(exists($h->{'content-type'}->{value})) {
376 0           my $ctype = lc($h->{'content-type'}->{value});
377 0   0       $file .= $content_type{$ctype} || "";
378             }
379 0           $file =~ s/^[ \.]+$/file/o;
380 0           $h->{'content-disposition'}->{filename} = &check_filename($files, $file);
381 0 0         $h->{'content-transfer-encoding'}->{value} = "" unless(exists($h->{'content-transfer-encoding'}->{value}));
382              
383 0           return();
384             }
385              
386             bootstrap MIME::Explode $VERSION;
387              
388             1;
389              
390             __DATA__