File Coverage

blib/lib/Convert/BulkDecoder.pm
Criterion Covered Total %
statement 166 298 55.7
branch 88 174 50.5
condition 20 57 35.0
subroutine 8 10 80.0
pod 0 6 0.0
total 282 545 51.7


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 jv $
5             # Author : Johan Vromans
6             # Created On : Wed Jan 29 16:59:58 2003
7             # Last Modified By: Johan Vromans
8             # Last Modified On: Sat Jul 9 23:12:45 2022
9             # Update Count : 89
10             # Status : Unknown, Use with caution!
11              
12             $VERSION = "1.04";
13              
14 5     5   216240 use strict;
  5         9  
  5         153  
15 5     5   2004 use integer;
  5         59  
  5         20  
16              
17             sub new {
18 20     20 0 29359 my ($pkg, %atts) = @_;
19 20 50       57 $pkg = ref $pkg if ref $pkg;
20              
21 20         98 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 20         88 foreach ( keys(%$self) ) {
35 160 100       250 if ( defined($atts{$_}) ) {
36 56         90 $self->{$_} = delete($atts{$_});
37             }
38             }
39              
40             # Bail of if any remain.
41 20         55 my $err = "";
42 20         47 foreach my $k ( sort keys %atts ) {
43 0         0 $err .= $pkg . ": invalid constructor attribute: $k\n";
44             }
45 20 50       33 die($err) if $err;
46              
47             # Polish.
48 20         35 foreach ( $self->{destdir}, $self->{tmpdir} ) {
49 40 100       74 next unless $_;
50 28         46 $_ .= "/";
51 28         135 s;/+$;/;;
52             }
53              
54 20 100       42 if ( $self->{md5} ) {
55 8         39 require Digest::MD5;
56 8         40 $self->{_md5} = Digest::MD5->new;
57             }
58              
59 20         109 $self;
60             }
61              
62             sub decode {
63              
64 20     20 0 74 my ($self, $a) = @_;
65              
66             # Try uudecode, or find out better.
67 20         38 my $ret = $self->uudecode($a);
68              
69             # MIME.
70 20 50       53 $ret = $self->mimedecode($a) if $ret eq 'M';
71              
72             # yEnc.
73 20 100       53 $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 20         42 $ret;
79             }
80              
81             sub uudecode {
82 20     20 0 30 my ($self, $a) = @_;
83              
84 20         23 my $doing = 0;
85 20         24 my $size = 0;
86 20         22 my $name;
87 20         35 $self->{result} = "EMPTY";
88              
89             # Process the message lines.
90 20         31 foreach ( @$a ) {
91 908 100       1176 if ( $doing ) { # uudecoding...
92 528 100       798 if ( /^end/ ) {
93 8         387 close(OUT);
94 8 100       49 $self->{md5} = $self->{_md5}->b64digest if $self->{md5};
95 8         16 $self->{size} = $size;
96 8         9 $doing = 2; # done
97 8         13 $self->{result} = "OK";
98 8         13 last;
99             }
100             # Select lines to process.
101 520 100       903 next if /[a-z]/;
102 384 100       643 next unless int((((ord() - 32) & 077) + 2) / 3)
103             == int(length() / 4);
104             # Decode.
105 376         714 my $t = unpack("u",$_);
106 376 50       680 print OUT $t or die("print(".$self->{file}."): $!\n");
107 376         395 $size += length($t);
108 376 100       748 $self->{_md5}->add($t) if $self->{md5};
109 376         492 next;
110             }
111              
112             # Check for MIME.
113 380 50       566 if ( m;^content-type:.*(image/|multipart);i ) {
114 0         0 return 'M'; # MIME
115             }
116              
117 380 100       566 if ( m/^=ybegin\s+.*\s+name=(.+)/i ) {
118 10         61 return "Y$1"; # yEnc
119             }
120              
121             # Otherwise, search for the uudecode 'begin' line.
122 370 100       565 if ( /^begin\s+\d+\s+(.+)$/ ) {
123 10         59 $name = $self->{neat}->($1);
124 10         23 $self->{type} = "U";
125 10         17 $self->{name} = $name;
126 10         17 $self->{file} = $self->{destdir} . $name;
127 10         12 $doing = 2; # Done
128             warn("Decoding(UU) to ", $self->{file}, "\n")
129 10 100       165 if $self->{verbose};
130             # Skip duplicates.
131             # Note that testing for -s fails if it is a
132             # notexisting symlink.
133 10 100 66     159 if ( (-l $self->{file} || -s _ ) && !$self->{force} ) {
      100        
134 2         6 $self->{size} = -s _;
135 2         3 $self->{result} = "DUP";
136 2         5 last;
137             }
138              
139             open (OUT, ">".$self->{file})
140 8 50       453 or die("create(".$self->{file}."): $!\n");
141 8         28 binmode(OUT);
142 8         11 $doing = 1; # Doing
143 8         13 $self->{result} = "FAIL";
144 8         28 next;
145             }
146             }
147 10         54 push(@{$self->{parts}},
148             { type => $self->{type},
149             size => $self->{size},
150             md5 => $self->{md5},
151             result => $self->{result},
152             name => $self->{name},
153 10         10 file => $self->{file} });
154 10         22 return $self->{result};
155             }
156              
157             my @crctab;
158              
159             sub ydecode {
160 10     10 0 15 my ($self, $a) = @_;
161 10         14 $self->{type} = "Y";
162 10         13 $self->{result} = "EMPTY";
163              
164 10 100 66     29 _fill_crctab() unless @crctab || !$self->{crc};
165              
166 10         59 my @lines = @$a;
167              
168 10         20 my ($ydec_part, $ydec_line, $ydec_size, $ydec_name, $ydec_pcrc,
169             $ydec_begin, $ydec_end);
170 10         0 my $pcrc;
171              
172 10         22 while ( $_ = shift(@lines) ) {
173             # Newlines a fakes and should not be decoded.
174 494         626 chomp;
175 494         987 s/\r//g;
176             # If we've started decoding $ydec_name will be set.
177 494 100       742 if ( !$ydec_name ) {
178             # Skip until beginning of yDecoded part.
179 342 100       656 next unless /^=ybegin/;
180 18 100       48 if ( / part=(\d+)/ ) {
181 13         21 $ydec_part = $1;
182             }
183              
184 18 50       47 if ( / size=(\d+)/ ) {
185 18         41 $self->{size} = $ydec_size = $1;
186             }
187             else {
188 0         0 die("Mandatory field 'size' missing\n");
189             }
190 18 50       50 if ( / line=(\d+)/ ) {
191 18         31 $ydec_line = $1;
192             }
193 18 50       46 if( / name=(.*)$/ ) {
194 18         40 $ydec_name = $self->{neat}->($1);
195 18         46 $self->{file} = $self->{destdir} . $ydec_name;
196 18         22 $self->{name} = $ydec_name;
197 18 100 100     55 if ( !defined($ydec_part) || $ydec_part == 1 ) {
198             warn("Decoding(yEnc) to ", $self->{file}, "\n")
199 10 100       156 if $self->{verbose};
200 10 100       129 if ( -s $self->{file} ) {
201 4 100       14 if ( $self->{force} ) {
202 2         59 unlink($self->{file});
203             }
204             else {
205 2         5 $self->{size} = -s _;
206 2         3 $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         19 $_ = shift(@lines);
220 12         19 chomp;
221 12         30 s/\r//g;
222 12 50       30 if ( /^=ypart/ ) {
223 12 50       37 if ( / begin=(\d+)/ ) {
224             # We need this to check if the size of this message
225             # is correct.
226 12         19 $ydec_begin = $1;
227 12         15 $pcrc = 0xffffffff;
228 12         13 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       28 if ( / end=(\d+)/ ) {
235             # We need this to calculate the size of this message.
236 12         16 $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         11 $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       166 if ( -e $self->{file} ) {
256 8 50 33     32 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       413 if ( !open(OUT, ">>".$self->{file}) ) {
263             die("Couldn't open ".$self->{file}.
264 0         0 " 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     32 if ( defined($ydec_part) && $ydec_part != 1 ) {
274 0         0 die("Missing ".$self->{file}. " for appending: $!\n");
275             }
276 8 50       430 if ( !open(OUT, ">".$self->{file}) ) {
277 0         0 die("Couldn't create ".$self->{file}.": $!\n");
278             }
279 8         39 $self->{result} = "FAIL";
280             }
281             # Cancel any file translations.
282 16         41 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         42 next;
287             }
288              
289             # Looking for the end tag.
290 152 100       247 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         28 $self->{result} = "OK";
295 16 100       45 if ( / part=(\d+)/ ) {
296 12 50       33 if ( $ydec_part != $1 ) {
297 0         0 die("Part number '$1' different from beginning part '$ydec_part'\n");
298             }
299             }
300 16 50       41 if ( / size=(\d+)/ ) {
301             # Check size, but first calculate it.
302 16         18 my $size;
303 16 100       31 if ( defined($ydec_part) ) {
304 12         20 $size = ($ydec_end - $ydec_begin + 1);
305             }
306             else {
307 4         6 $size = $ydec_size;
308             }
309 16 50       38 if ( $1 != $size ) {
310 0         0 die("Size '$1' different from beginning size '$size'\n");
311             }
312             }
313 16 100 66     70 if ( / pcrc32=([0-9a-f]+)/i && @crctab ) {
314 12 50 33     24 if ( defined($ydec_pcrc) && ($ydec_pcrc != $1) ) {
315 0         0 die("CRC '$1' different from beginning CRC '$ydec_pcrc'\n");
316             }
317 12         21 $ydec_pcrc = hex($1);
318 12         14 $pcrc = $pcrc ^ 0xffffffff;
319 12 50       17 if ( $pcrc == $ydec_pcrc ) {
320             warn("Part $ydec_part, checksum OK\n")
321 12 100       183 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     61 if ( !defined($ydec_part) && / crc32=([0-9a-f]+)/i && @crctab ) {
      66        
331 4         8 $ydec_pcrc = hex($1);
332 4         14 $pcrc = $pcrc ^ 0xffffffff;
333 4 50       6 if ( $pcrc == $ydec_pcrc ) {
334             warn("Checksum OK\n")
335 4 100       65 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         32 undef $ydec_name;
345             # Dont encode the endline, we skip to the next line
346             # in search for any more parts.
347 16         35 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         155 s/=(.)/chr(ord($1)+(256-64) & 255)/ge;
  0         0  
356 136         174 tr{\000-\377}{\326-\377\000-\325};
357              
358 136         164 my $data = $_;
359             # CRC check code by jvromans@squirrel.nl.
360 136 50       205 if ( @crctab ) {
361 136         1494 foreach ( split(//, $data) ) {
362 16408         20299 $pcrc = $crctab[($pcrc^ord($_))&0xff] ^ (($pcrc >> 8) & 0x00ffffff);
363             }
364             }
365              
366 136         858 print OUT $data;
367 136 100       439 $self->{_md5}->add($data) if $self->{md5};
368             }
369              
370 10         213 close(OUT);
371 10 100       52 $self->{md5} = $self->{_md5}->b64digest if $self->{md5};
372 10         60 push(@{$self->{parts}},
373             { type => $self->{type},
374             size => $self->{size},
375             md5 => $self->{md5},
376             result => $self->{result},
377             name => $self->{name},
378 10         13 file => $self->{file} });
379 10         28 return $self->{result};
380             }
381              
382             sub _fill_crctab {
383 2     2   51 @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             warn("Decoding(ydecode) to ", $self->{file}, "\n")
438 0 0       0 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             system("ydecode", "-k",
493 0 0       0 $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             open(F, $self->{file})
501 0 0       0 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}},
512             { type => $self->{type},
513             size => $self->{size},
514             md5 => $self->{md5},
515             result => $self->{result},
516             name => $self->{name},
517 0         0 file => $self->{file} });
518 0         0 return $self->{result};
519             }
520              
521             sub mimedecode {
522 0     0 0 0 my ($self, $a) = @_;
523              
524 0         0 require MIME::Parser;
525              
526 0         0 $self->{type} = "M";
527 0         0 my $parser = new MIME::Parser;
528             # Store everything in memory.
529 0         0 $parser->output_to_core(1);
530 0         0 my $e = $parser->parse_data($a);
531              
532 0 0 0     0 unless ( defined $e->{ME_Parts} && @{$e->{ME_Parts}} ) {
  0         0  
533 0         0 $e->{ME_Parts} = [ $e ];
534             }
535              
536 0         0 foreach my $part ( @{$e->{ME_Parts}} ) {
  0         0  
537 0         0 my $name;
538 0         0 foreach ( 'Content-Type', 'Content-Disposition' ) {
539              
540 0         0 my $ct = $part->{mail_inet_head}->{mail_hdr_hash}->{$_};
541 0 0 0     0 next unless defined $ct && defined ($ct = ${$ct->[0]});
  0         0  
542 0 0       0 if ( $ct =~ m{((file)?name)="([^"]+)"}i ) {
543 0         0 $name = $self->{name} = $self->{neat}->($3);
544 0         0 $self->{file} = $self->{destdir} . $name;
545             warn("Decoding(MIME) to ", $self->{file}, "\n")
546 0 0       0 if $self->{verbose};
547 0 0 0     0 if ( -s $self->{file} && !$self->{force} ) {
548 0         0 $self->{size} = -s _;
549 0         0 $self->{result} = "DUP";
550 0         0 push(@{$self->{parts}},
551             { type => $self->{type},
552             size => $self->{size},
553             result => $self->{result},
554             name => $self->{name},
555 0         0 file => $self->{file} });
556 0         0 next;
557             }
558             }
559             }
560              
561             # Skip body.
562 0 0       0 next unless $name;
563 0 0       0 next if $name eq $self->{destdir}."body";
564              
565             # Skip duplicates.
566 0 0 0     0 if ( -s $name && !$self->{force} ) {
567 0         0 $self->{size} = -s _;
568 0         0 $self->{result} = "DUP";
569 0         0 push(@{$self->{parts}},
570             { type => $self->{type},
571             size => $self->{size},
572             result => $self->{result},
573             name => $self->{name},
574 0         0 file => $self->{file} });
575 0         0 next;
576             }
577              
578             # Store it.
579 0         0 my $bh = $part->{ME_Bodyhandle};
580 0 0 0     0 if ( $bh && defined $bh->{MBC_Data} && open (OUT, ">".$self->{file}) ) {
      0        
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}},
593             { type => $self->{type},
594             size => $self->{size},
595             md5 => $self->{md5},
596             result => $self->{result},
597             name => $self->{name},
598 0         0 file => $self->{file} });
599             }
600             else {
601 0         0 $self->{result} = "FAIL";
602 0         0 push(@{$self->{parts}},
603             { type => $self->{type},
604             result => $self->{result},
605             name => $self->{name},
606 0         0 file => $self->{file} });
607             }
608             }
609              
610             # Return values for the first file.
611 0         0 while ( my($k,$v) = each(%{$self->{parts}->[0]}) ) {
  0         0  
612 0         0 $self->{$k} = $v;
613             }
614 0         0 return $self->{result};
615              
616             }
617              
618             sub _neat {
619 22     22   68 local ($_) = @_;
620 22         30 s/^\[a-z]://i;
621 22         71 s/^.*?([^\\]+$)/$1/;
622             # Spaces and unprintables to _.
623 22         44 s/\s+/_/g;
624 22         25 s/\.\.+/./g;
625 22         36 s/[\0-\040'`"\177-\240\/]/_/g;
626             # Remove leading dots.
627 22         24 s/^\.+//;
628 22         41 $_;
629             }
630              
631             1;
632              
633             __END__