File Coverage

blib/lib/Convert/BulkDecoder.pm
Criterion Covered Total %
statement 198 298 66.4
branch 103 174 59.2
condition 27 57 47.3
subroutine 9 10 90.0
pod 0 6 0.0
total 337 545 61.8


line stmt bran cond sub pod time code
1             package Convert::BulkDecoder;
2              
3             # Convert::BulkDecoder - Extract binary data from mail and news messages
4             # RCS Info : $Id: BulkDecoder.pm,v 1.12 2005-06-19 17:35:38+02 jv Exp $
5             # Author : Johan Vromans
6             # Created On : Wed Jan 29 16:59:58 2003
7             # Last Modified By: Johan Vromans
8             # Last Modified On: Sun Jun 19 17:34:31 2005
9             # Update Count : 88
10             # Status : Unknown, Use with caution!
11              
12             $VERSION = "1.03";
13              
14 5     5   165665 use strict;
  5         12  
  5         207  
15 5     5   5934 use integer;
  5         58  
  5         29  
16              
17             sub new {
18 25     25 0 150994 my ($pkg, %atts) = @_;
19 25 50       262 $pkg = ref $pkg if ref $pkg;
20              
21 25         347 my $self = bless {
22             # Set explicit defaults.
23             tmpdir => "/var/tmp",
24             destdir => "",
25             force => 0,
26             verbose => 1,
27             crc => 1,
28             md5 => 1,
29             debug => 0,
30             neat => \&_neat,
31             }, $pkg;
32              
33             # Copy constructor attributes.
34 25         693 foreach ( keys(%$self) ) {
35 200 100       821 if ( defined($atts{$_}) ) {
36 70         330 $self->{$_} = delete($atts{$_});
37             }
38             }
39              
40             # Bail of if any remain.
41 25         68 my $err = "";
42 25         120 foreach my $k ( sort keys %atts ) {
43 0         0 $err .= $pkg . ": invalid constructor attribute: $k\n";
44             }
45 25 50       94 die($err) if $err;
46              
47             # Polish.
48 25         88 foreach ( $self->{destdir}, $self->{tmpdir} ) {
49 50 100       124 next unless $_;
50 35         80 $_ .= "/";
51 35         433 s;/+$;/;;
52             }
53              
54 25 100       184 if ( $self->{md5} ) {
55 10         116 require Digest::MD5;
56 10         115 $self->{_md5} = Digest::MD5->new;
57             }
58              
59 25         109 $self;
60             }
61              
62             sub decode {
63              
64 25     25 0 542 my ($self, $a) = @_;
65              
66             # Try uudecode, or find out better.
67 25         89 my $ret = $self->uudecode($a);
68              
69             # MIME.
70 25 100       116 $ret = $self->mimedecode($a) if $ret eq 'M';
71              
72             # yEnc.
73 25 100       125 $ret = $self->ydecode($a) if $ret =~ /^Y/;
74              
75             # UNSUPPORTED -- FOR TESTING ONLY!
76             # $ret = $self->ydecode_ydecode($a, $1) if $ret =~ /^Y(.*)/;
77              
78 25         85 $ret;
79             }
80              
81             sub uudecode {
82 25     25 0 46 my ($self, $a) = @_;
83              
84 25         50 my $doing = 0;
85 25         39 my $size = 0;
86 25         35 my $name;
87 25         70 $self->{result} = "EMPTY";
88              
89             # Process the message lines.
90 25         64 foreach ( @$a ) {
91 1083 100       3227 if ( $doing ) { # uudecoding...
92 528 100       1129 if ( /^end/ ) {
93 8         716 close(OUT);
94 8 100       155 $self->{md5} = $self->{_md5}->b64digest if $self->{md5};
95 8         23 $self->{size} = $size;
96 8         99 $doing = 2; # done
97 8         16 $self->{result} = "OK";
98 8         17 last;
99             }
100             # Select lines to process.
101 520 100       1260 next if /[a-z]/;
102 384 100       1220 next unless int((((ord() - 32) & 077) + 2) / 3)
103             == int(length() / 4);
104             # Decode.
105 376         1214 my $t = unpack("u",$_);
106 376 50       2051 print OUT $t or die("print(".$self->{file}."): $!\n");
107 376         483 $size += length($t);
108 376 100       1537 $self->{_md5}->add($t) if $self->{md5};
109 376         550 next;
110             }
111              
112             # Check for MIME.
113 555 100       1409 if ( m;^content-type:.*(image/|multipart);i ) {
114 5         22 return 'M'; # MIME
115             }
116              
117 550 100       1257 if ( m/^=ybegin\s+.*\s+name=(.+)/i ) {
118 10         59 return "Y$1"; # yEnc
119             }
120              
121             # Otherwise, search for the uudecode 'begin' line.
122 540 100       1455 if ( /^begin\s+\d+\s+(.+)$/ ) {
123 10         37 $name = $self->{neat}->($1);
124 10         32 $self->{type} = "U";
125 10         738 $self->{name} = $name;
126 10         30 $self->{file} = $self->{destdir} . $name;
127 10         18 $doing = 2; # Done
128 10 100       977 warn("Decoding(UU) to ", $self->{file}, "\n")
129             if $self->{verbose};
130             # Skip duplicates.
131             # Note that testing for -s fails if it is a
132             # notexisting symlink.
133 10 100 66     226 if ( (-l $self->{file} || -s _ ) && !$self->{force} ) {
      100        
134 2         7 $self->{size} = -s _;
135 2         6 $self->{result} = "DUP";
136 2         6 last;
137             }
138              
139 8 50       1059 open (OUT, ">".$self->{file})
140             or die("create(".$self->{file}."): $!\n");
141 8         128 binmode(OUT);
142 8         12 $doing = 1; # Doing
143 8         20 $self->{result} = "FAIL";
144 8         18 next;
145             }
146             }
147 10         15 push(@{$self->{parts}},
  10         97  
148             { type => $self->{type},
149             size => $self->{size},
150             md5 => $self->{md5},
151             result => $self->{result},
152             name => $self->{name},
153             file => $self->{file} });
154 10         35 return $self->{result};
155             }
156              
157             my @crctab;
158              
159             sub ydecode {
160 10     10 0 20 my ($self, $a) = @_;
161 10         25 $self->{type} = "Y";
162 10         602 $self->{result} = "EMPTY";
163              
164 10 100 66     672 _fill_crctab() unless @crctab || !$self->{crc};
165              
166 10         209 my @lines = @$a;
167              
168 10         19 my ($ydec_part, $ydec_line, $ydec_size, $ydec_name, $ydec_pcrc,
169             $ydec_begin, $ydec_end);
170 0         0 my $pcrc;
171              
172 10         33 while ( $_ = shift(@lines) ) {
173             # Newlines a fakes and should not be decoded.
174 494         722 chomp;
175 494         1286 s/\r//g;
176             # If we've started decoding $ydec_name will be set.
177 494 100       1173 if ( !$ydec_name ) {
178             # Skip until beginning of yDecoded part.
179 342 100       1009 next unless /^=ybegin/;
180 18 100       85 if ( / part=(\d+)/ ) {
181 13         36 $ydec_part = $1;
182             }
183              
184 18 50       80 if ( / size=(\d+)/ ) {
185 18         59 $self->{size} = $ydec_size = $1;
186             }
187             else {
188 0         0 die("Mandatory field 'size' missing\n");
189             }
190 18 50       69 if ( / line=(\d+)/ ) {
191 18         31 $ydec_line = $1;
192             }
193 18 50       219 if( / name=(.*)$/ ) {
194 18         77 $ydec_name = $self->{neat}->($1);
195 18         71 $self->{file} = $self->{destdir} . $ydec_name;
196 18         34 $self->{name} = $ydec_name;
197 18 100 100     373 if ( !defined($ydec_part) || $ydec_part == 1 ) {
198 10 100       245 warn("Decoding(yEnc) to ", $self->{file}, "\n")
199             if $self->{verbose};
200 10 100       165 if ( -s $self->{file} ) {
201 4 100       19 if ( $self->{force} ) {
202 2         450 unlink($self->{file});
203             }
204             else {
205 2         7 $self->{size} = -s _;
206 2         5 $self->{result} = "DUP";
207 2         4 last;
208             }
209             }
210             }
211             }
212             else {
213 0         0 die("Unknown attach name\n");
214             }
215              
216             # Multipart messages contain more information on.
217             # the second line.
218 16 100       38 if ( $ydec_part ) {
219 12         18 $_ = shift(@lines);
220 12         28 chomp;
221 12         39 s/\r//g;
222 12 50       47 if ( /^=ypart/ ) {
223 12 50       49 if ( / begin=(\d+)/ ) {
224             # We need this to check if the size of this message
225             # is correct.
226 12         22 $ydec_begin = $1;
227 12         17 $pcrc = 0xffffffff;
228 12         19 undef $ydec_pcrc;
229             }
230             else {
231 0         0 warn("No begin field found in part, ignoring\n");
232 0         0 undef $ydec_part;
233             }
234 12 50       44 if ( / end=(\d+)/ ) {
235             # We need this to calculate the size of this message.
236 12         23 $ydec_end = $1;
237             }
238             else {
239 0         0 warn("No end field found in part, ignoring");
240 0         0 undef $ydec_part;
241             }
242             }
243             else {
244 0         0 warn("Article described as multipart message, however ".
245             "it doesn't seem that way\n");
246 0         0 undef $ydec_part;
247             }
248             }
249             else {
250 4         8 $pcrc = 0xffffffff;
251             }
252              
253             # If the $ydec_part is different from 1
254             # we need to open the file for appending.
255 16 100       253 if ( -e $self->{file} ) {
256 8 50 33     43 if ( defined($ydec_part) && $ydec_part != 1 ) {
    0          
257             # If we have a multipart message, the file exists
258             # and we are not at the first part, we should just
259             # open the file as an append. We assume that this is
260             # the multipart we were already processing.
261             #print "Opening $ydec_name for appending\n";
262 8 50       705 if ( !open(OUT, ">>".$self->{file}) ) {
263 0         0 die("Couldn't open ".$self->{file}.
264             " for appending: $!\n");
265             }
266             }
267             elsif ( !open(OUT, ">".$self->{file}) ) {
268 0         0 die("Couldn't create ".$self->{file}.": $!\n");
269             }
270             }
271             else {
272             # File doesn't exist. We open it for writing O' so plain.
273 8 50 66     41 if ( defined($ydec_part) && $ydec_part != 1 ) {
274 0         0 die("Missing ".$self->{file}. " for appending: $!\n");
275             }
276 8 50       1231 if ( !open(OUT, ">".$self->{file}) ) {
277 0         0 die("Couldn't create ".$self->{file}.": $!\n");
278             }
279 8         27 $self->{result} = "FAIL";
280             }
281             # Cancel any file translations.
282 16         42 binmode(OUT);
283             # Excellent.. We have determed all the info for this file we
284             # need.. Skip till next line, this should contain the real
285             # data.
286 16         41 next;
287             }
288              
289             # Looking for the end tag.
290 152 100       548 if ( /^=yend/ ) {
291             # We are done.. Check the sanity of article.
292             # and unset $ydec_name in case that there are more
293             # ydecoded files in the same article.
294 16         33 $self->{result} = "OK";
295 16 100       135 if ( / part=(\d+)/ ) {
296 12 50       422 if ( $ydec_part != $1 ) {
297 0         0 die("Part number '$1' different from beginning part '$ydec_part'\n");
298             }
299             }
300 16 50       74 if ( / size=(\d+)/ ) {
301             # Check size, but first calculate it.
302 16         20 my $size;
303 16 100       44 if ( defined($ydec_part) ) {
304 12         37 $size = ($ydec_end - $ydec_begin + 1);
305             }
306             else {
307 4         8 $size = $ydec_size;
308             }
309 16 50       204 if ( $1 != $size ) {
310 0         0 die("Size '$1' different from beginning size '$size'\n");
311             }
312             }
313 16 100 66     499 if ( / pcrc32=([0-9a-f]+)/i && @crctab ) {
314 12 50 33     38 if ( defined($ydec_pcrc) && ($ydec_pcrc != $1) ) {
315 0         0 die("CRC '$1' different from beginning CRC '$ydec_pcrc'\n");
316             }
317 12         28 $ydec_pcrc = hex($1);
318 12         16 $pcrc = $pcrc ^ 0xffffffff;
319 12 50       28 if ( $pcrc == $ydec_pcrc ) {
320 12 100       1322 warn("Part $ydec_part, checksum OK\n")
321             if $self->{verbose};
322             }
323             else {
324 0         0 warn(sprintf("Part $ydec_part, checksum mismatch, ".
325             "got 0x%08x, expected 0x%08x\n",
326             $pcrc, $ydec_pcrc));
327             }
328              
329             }
330 16 50 66     93 if ( !defined($ydec_part) && / crc32=([0-9a-f]+)/i && @crctab ) {
      66        
331 4         10 $ydec_pcrc = hex($1);
332 4         5 $pcrc = $pcrc ^ 0xffffffff;
333 4 50       10 if ( $pcrc == $ydec_pcrc ) {
334 4 100       80 warn("Checksum OK\n")
335             if $self->{verbose};
336             }
337             else {
338 0         0 warn(sprintf("Checksum mismatch, ".
339             "got 0x%08x, expected 0x%08x\n",
340             $pcrc, $ydec_pcrc));
341             }
342              
343             }
344 16         27 undef $ydec_name;
345             # Dont encode the endline, we skip to the next line
346             # in search for any more parts.
347 16         49 next;
348             }
349              
350             # If we got here, we are within an encoded article, an
351             # we will take meassures to decode it.
352             # We decode line by line.
353              
354             # Decoder by jvromans@squirrel.nl.
355 136         204 s/=(.)/chr(ord($1)+(256-64) & 255)/ge;
  0         0  
356 136         203 tr{\000-\377}{\326-\377\000-\325};
357              
358 136         180 my $data = $_;
359             # CRC check code by jvromans@squirrel.nl.
360 136 50       247 if ( @crctab ) {
361 136         2962 foreach ( split(//, $data) ) {
362 16408         27811 $pcrc = $crctab[($pcrc^ord($_))&0xff] ^ (($pcrc >> 8) & 0x00ffffff);
363             }
364             }
365              
366 136         2198 print OUT $data;
367 136 100       3490 $self->{_md5}->add($data) if $self->{md5};
368             }
369              
370 10         628 close(OUT);
371 10 100       86 $self->{md5} = $self->{_md5}->b64digest if $self->{md5};
372 10         25 push(@{$self->{parts}},
  10         119  
373             { type => $self->{type},
374             size => $self->{size},
375             md5 => $self->{md5},
376             result => $self->{result},
377             name => $self->{name},
378             file => $self->{file} });
379 10         48 return $self->{result};
380             }
381              
382             sub _fill_crctab {
383 2     2   64 @crctab =
384             ( 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
385             0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
386             0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
387             0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
388             0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
389             0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
390             0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
391             0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
392             0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
393             0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
394             0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
395             0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
396             0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
397             0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
398             0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
399             0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
400             0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
401             0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
402             0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
403             0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
404             0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
405             0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
406             0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
407             0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
408             0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
409             0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
410             0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
411             0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
412             0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
413             0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
414             0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
415             0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
416             0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
417             0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
418             0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
419             0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
420             0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
421             0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
422             0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
423             0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
424             0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
425             0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
426             0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
427             );
428             }
429              
430             sub ydecode_ydecode {
431 0     0 0 0 my ($self, $a, $name) = @_;
432 0         0 my $tmp = $self->{tmpdir} . "mfetch.$$.";
433              
434 0         0 $self->{type} = "Y";
435 0 0       0 if ( $name ) {
436 0         0 $self->{file} = $self->{destdir} . $name;
437 0 0       0 warn("Decoding(ydecode) to ", $self->{file}, "\n")
438             if $self->{verbose};
439 0 0       0 if ( -s $self->{file} ) {
440 0 0       0 if ( $self->{force} ) {
441 0         0 unlink($self->{file});
442             }
443             else {
444 0         0 $self->{size} = -s _;
445 0         0 $self->{result} = "DUP";
446 0         0 goto QXIT;
447             }
448             }
449             }
450              
451 0         0 my @files;
452 0         0 my $copy = 0;
453 0         0 my $part;
454 0         0 foreach ( @$a ) {
455 0 0 0     0 if ( $copy && /^=yend/ ) {
456 0         0 print TMP $_;
457 0         0 close(TMP);
458 0         0 $copy = 0;
459 0         0 next;
460             }
461 0 0 0     0 if ( !$copy && /^=ybegin.*\s+part=(\d+)/ ) {
462 0         0 my $file = sprintf("$tmp%03d", $part = $1);
463 0         0 $files[$1-1] = $file;
464 0 0       0 $copy = $1 if /\s+line=(\d+)/;
465 0 0       0 $self->{size} = $1 if /\s+size=(\d+)/;
466 0 0       0 $self->{name} = $1 if /\s+name=(.+)/;
467 0         0 $self->{file} = $self->{destdir} . $self->{name};
468 0 0       0 if ( -s $self->{file} ) {
469 0 0       0 if ( $self->{force} ) {
470 0         0 unlink($self->{file});
471             }
472             else {
473 0         0 $self->{size} = -s _;
474 0         0 $self->{result} = "DUP";
475 0         0 goto QXIT;
476             }
477             }
478 0 0       0 open(TMP, ">$file") || die("$file: $!\n");
479 0         0 binmode(TMP);
480 0         0 $copy++;
481             }
482 0 0       0 if ( $copy > 1 ) { # check length
483             # If it starts with an unescaped period, the line will be
484             # one too short. Add the period since ydecode requires it.
485 0 0 0     0 if ( /^\./ && length($_) == $copy ) {
486 0         0 $_ = ".$_";
487             }
488             }
489 0 0       0 print TMP $_ if $copy;
490             }
491              
492 0 0       0 system("ydecode", "-k",
493             $self->{destdir} ? "--output=".$self->{destdir} : (),
494             @files);
495              
496 0         0 $self->{result} = "FAIL";
497 0 0       0 if ( -s $self->{file} == $self->{size} ) {
498 0         0 unlink(@files);
499 0 0       0 if ( $self->{md5} ) {
500 0 0       0 open(F, $self->{file})
501             or die($self->{file} . " (reopen) $!\n");
502 0         0 binmode(F);
503 0         0 local($/) = undef;
504 0         0 $self->{_md5}->add();
505 0         0 close(F);
506 0         0 $self->{md5} = $self->{_md5}->b64digest;
507             }
508 0         0 $self->{result} = "OK";
509             }
510             QXIT:
511 0         0 push(@{$self->{parts}},
  0         0  
512             { type => $self->{type},
513             size => $self->{size},
514             md5 => $self->{md5},
515             result => $self->{result},
516             name => $self->{name},
517             file => $self->{file} });
518 0         0 return $self->{result};
519             }
520              
521             sub mimedecode {
522 5     5 0 10 my ($self, $a) = @_;
523              
524 5         1162 require MIME::Parser;
525              
526 5         163958 $self->{type} = "M";
527 5         208 my $parser = new MIME::Parser;
528             # Store everything in memory.
529 5         869 $parser->output_to_core(1);
530 5         62 my $e = $parser->parse_data($a);
531              
532 5 50 33     155032 unless ( defined $e->{ME_Parts} && @{$e->{ME_Parts}} ) {
  5         40  
533 0         0 $e->{ME_Parts} = [ $e ];
534             }
535              
536 5         11 foreach my $part ( @{$e->{ME_Parts}} ) {
  5         18  
537 15         22 my $name;
538 15         32 foreach ( 'Content-Type', 'Content-Disposition' ) {
539              
540 30         101 my $ct = $part->{mail_inet_head}->{mail_hdr_hash}->{$_};
541 30 100 66     87 next unless defined $ct && defined ($ct = ${$ct->[0]});
  25         112  
542 25 100       126 if ( $ct =~ m{((file)?name)="([^"]+)"}i ) {
543 10         42 $name = $self->{name} = $self->{neat}->($3);
544 10         44 $self->{file} = $self->{destdir} . $name;
545 10 100       58 warn("Decoding(MIME) to ", $self->{file}, "\n")
546             if $self->{verbose};
547 10 50 33     127 if ( -s $self->{file} && !$self->{force} ) {
548 0         0 $self->{size} = -s _;
549 0         0 $self->{result} = "DUP";
550 0         0 push(@{$self->{parts}},
  0         0  
551             { type => $self->{type},
552             size => $self->{size},
553             result => $self->{result},
554             name => $self->{name},
555             file => $self->{file} });
556 0         0 next;
557             }
558             }
559             }
560              
561             # Skip body.
562 15 100       44 next unless $name;
563 10 50       62 next if $name eq $self->{destdir}."body";
564              
565             # Skip duplicates.
566 10 50 33     72 if ( -s $name && !$self->{force} ) {
567 0         0 $self->{size} = -s _;
568 0         0 $self->{result} = "DUP";
569 0         0 push(@{$self->{parts}},
  0         0  
570             { type => $self->{type},
571             size => $self->{size},
572             result => $self->{result},
573             name => $self->{name},
574             file => $self->{file} });
575 0         0 next;
576             }
577              
578             # Store it.
579 10         24 my $bh = $part->{ME_Bodyhandle};
580 10 50 33     96 if ( $bh && defined $bh->{MBC_Data} && open (OUT, ">".$self->{file}) ) {
      33        
581 0         0 binmode(OUT);
582 0         0 my $size = 0;
583 0         0 foreach ( @{$bh->{MBC_Data}} ) {
  0         0  
584 0         0 print OUT $_;
585 0 0       0 $self->{_md5}->add($_) if $self->{md5};
586 0         0 $size += length($_);
587             }
588 0         0 close (OUT);
589 0 0       0 $self->{md5} = $self->{_md5}->b64digest if $self->{md5};
590 0         0 $self->{size} = $size;
591 0         0 $self->{result} = "OK";
592 0         0 push(@{$self->{parts}},
  0         0  
593             { type => $self->{type},
594             size => $self->{size},
595             md5 => $self->{md5},
596             result => $self->{result},
597             name => $self->{name},
598             file => $self->{file} });
599             }
600             else {
601 10         23 $self->{result} = "FAIL";
602 10         16 push(@{$self->{parts}},
  10         93  
603             { type => $self->{type},
604             result => $self->{result},
605             name => $self->{name},
606             file => $self->{file} });
607             }
608             }
609              
610             # Return values for the first file.
611 5         12 while ( my($k,$v) = each(%{$self->{parts}->[0]}) ) {
  25         97  
612 20         47 $self->{$k} = $v;
613             }
614 5         408 return $self->{result};
615              
616             }
617              
618             sub _neat {
619 30     30   121 local ($_) = @_;
620 30         57 s/^\[a-z]://i;
621 30         147 s/^.*?([^\\]+$)/$1/;
622             # Spaces and unprintables to _.
623 30         71 s/\s+/_/g;
624 30         61 s/\.\.+/./g;
625 30         50 s/[\0-\040'`"\177-\240\/]/_/g;
626             # Remove leading dots.
627 30         47 s/^\.+//;
628 30         97 $_;
629             }
630              
631             1;
632              
633             __END__