File Coverage

blib/lib/MIME/Expander.pm
Criterion Covered Total %
statement 163 163 100.0
branch 64 74 86.4
condition 33 45 73.3
subroutine 25 25 100.0
pod 9 11 81.8
total 294 318 92.4


line stmt bran cond sub pod time code
1             package MIME::Expander;
2              
3 3     3   60474 use strict;
  3         6  
  3         112  
4 3     3   16 use warnings;
  3         7  
  3         96  
5 3     3   15 use vars qw($VERSION);
  3         10  
  3         199  
6             $VERSION = '0.01';
7              
8 3     3   11 use vars qw($DEBUG);
  3         6  
  3         248  
9             $DEBUG = 0;
10              
11 3     3   12 use vars qw($PrefixGuess $PrefixPlugin @DefaultGuesser @EnabledPlugins);
  3         6  
  3         297  
12             BEGIN {
13 3     3   7 $PrefixGuess = 'MIME::Expander::Guess';
14 3         5 $PrefixPlugin = 'MIME::Expander::Plugin';
15 3         6 @DefaultGuesser = ('MMagic', 'FileName');
16 3         42 @EnabledPlugins = ();
17             }
18              
19 3     3   8709 use Email::MIME;
  3         296770  
  3         91  
20 3     3   33 use Email::MIME::ContentType ();
  3         7  
  3         58  
21 3     3   2956 use MIME::Type;
  3         8995  
  3         183  
22 3     3   23538 use Module::Load;
  3         3220  
  3         21  
23 3     3   2917 use Module::Pluggable search_path => $PrefixPlugin, sub_name => 'expanders';
  3         39739  
  3         24  
24              
25             sub import {
26 3     3   1152 my $class = shift;
27 3         45 @EnabledPlugins = @_;
28             }
29              
30             sub regulate_type {
31 93 100   93 1 535916 return undef unless( defined $_[1] );
32 52         144 my $type = $_[1];
33              
34             # There is regexp from Email::MIME::ContentType 1.015
35 52         138 my $tspecials = quotemeta '()<>@,;:\\"/[]?=';
36 52         531 my $discrete = qr/[^$tspecials]+/;
37 52         381 my $composite = qr/[^$tspecials]+/;
38 52         251 my $params = qr/;.*/;
39 52 100       880 return undef unless( $type =~ m[ ^ ($discrete) / ($composite) \s* ($params)? $ ]x );
40              
41 50         305 my $ct = Email::MIME::ContentType::parse_content_type($type);
42 50 50 33     2404 return undef if( ! $ct->{discrete} or ! $ct->{composite} );
43 50         939 return MIME::Type->simplified(join('/',$ct->{discrete}, $ct->{composite}));
44             }
45              
46             sub debug {
47 172     172 0 337 my $self = shift;
48 172 50       415 my $msg = shift or return;
49 172 50       859 printf STDERR "# %s: %s\n", $self, $msg if( $DEBUG );
50             }
51              
52             sub new {
53 17     17 0 47236 my $class = shift;
54 17   33     115 $class = ref $class || $class;
55 17         90 my $self = {
56             expects => [],
57             guesser => undef,
58             depth => undef,
59             };
60 17         50 bless $self, $class;
61 17         65 return $self->init(@_);
62             }
63              
64             sub init {
65 21     21 1 39 my $self = shift;
66 21         27 my $args;
67 21 100       71 if( 0 == @_ % 2 ){
68 18         45 $args = { @_ }
69             }else{
70 3   50     14 $args = shift || {};
71             }
72              
73 21 100       95 $self->expects(
74             exists $args->{expects} ? $args->{expects} : [] );
75              
76 21 50       84 $self->guesser(
77             exists $args->{guesser} ? $args->{guesser} : undef );
78              
79 21 100       78 $self->depth(
80             exists $args->{depth} ? $args->{depth} : undef );
81              
82 21         70 return $self;
83             }
84              
85             sub expects {
86 127     127 1 1195 my $self = shift;
87 127 100       281 if( @_ ){
88 26         60 $self->{expects} = shift;
89 26 100 100     196 die "setting value is not acceptable, it requires an reference of ARRAY"
90             if( defined $self->{expects} and ref($self->{expects}) ne 'ARRAY' );
91             }
92 126         399 return $self->{expects};
93             }
94              
95             sub is_expected {
96 53     53 1 118 my $self = shift;
97 53 50       318 my $type = shift or undef;
98 53 100       360 die "invalid type $type that has not looks as mime/type"
99             if( $type !~ m,^.+/.+$, );
100 52 100       170 return () unless( $self->expects );
101 47 100       87 for my $regexp ( map { ref $_ ? $_ : qr/$_/ } @{$self->expects} ){
  38         353  
  47         88  
102 33 100       233 return 1 if( $type =~ $regexp );
103             }
104 38         315 return ();
105             }
106              
107             sub depth {
108 52     52 1 1047 my $self = shift;
109 52 100       168 if( @_ ){
110 25         40 $self->{depth} = shift;
111 25 100 100     103 die "setting value is not acceptable, it requires a native number"
112             if( defined $self->{depth} and $self->{depth} =~ /\D/ );
113             }
114 51         194 return $self->{depth};
115             }
116              
117             sub guesser {
118 75     75 1 2220 my $self = shift;
119 75 100       186 if( @_ ){
120 26         45 $self->{guesser} = shift;
121 26 100 100     171 die "setting value is not acceptable, it requires an reference of CODE or ARRAY"
      100        
122             if( defined $self->{guesser}
123             and ref($self->{guesser}) ne 'CODE'
124             and ref($self->{guesser}) ne 'ARRAY');
125             }
126 74         169 return $self->{guesser};
127             }
128              
129             sub guess_type_of {
130 44     44 1 84 my $self = shift;
131 44 50       148 my $ref_data = shift or die "missing mandatory parameter";
132 44   50     128 my $info = shift || {};
133            
134 44         88 my $type = undef;
135 44         145 my $routine = $self->guesser;
136              
137 44 100       126 if( ref $routine eq 'CODE' ){
138 2         6 $type = $self->guesser->($ref_data, $info);
139              
140             }else{
141 42         52 my @routines;
142 42 100       94 if( ref $routine eq 'ARRAY' ){
143 2         7 @routines = @$routine;
144             }else{
145 40         138 @routines = @DefaultGuesser;
146             }
147 42         89 for my $klass ( @routines ){
148 43 50       361 $klass = join('::', $PrefixGuess, $klass) if( $klass !~ /:/ );
149 43         200 Module::Load::load $klass;
150 43         3296 $type = $self->regulate_type( $klass->type($ref_data, $info) );
151 43 100 66     5305 last if( $type and $type ne 'application/octet-stream');
152             }
153             }
154 44   100     265 return ($type || 'application/octet-stream');
155             }
156              
157             sub plugin_for {
158 42     42 1 758 my $self = shift;
159 42         58 my $type = shift;
160              
161 42         64 my $plugin = undef;
162 42         232 for my $available ( $self->expanders ){
163              
164 169         226821 my $klass = undef;
165 169 100       409 unless( @EnabledPlugins ){
166 161         280 $klass = $available;
167             }else{
168 8         14 for my $enable ( @EnabledPlugins ){
169 8 100       23 $enable = join('::', $PrefixPlugin, $enable)
170             if( $enable !~ /:/ );
171 8 100       24 if( $available eq $enable ){
172 2         4 $klass = $available;
173 2         5 last;
174             }
175             }
176 8 100       21 next unless( $klass );
177             }
178            
179 163         537 Module::Load::load $klass;
180 163 100       10081 if( $klass->is_acceptable( $type ) ){
181 21         169 $plugin = $klass->new;
182 21         49 last;
183             }
184             }
185 42         185 return $plugin;
186             }
187              
188             sub _create_media {
189 38     38   77 my $self = shift;
190 38 50       121 my $ref_data = shift or die "missing mandatory parameter";
191 38   50     99 my $info = shift || {};
192              
193 38         227 my $type = $self->regulate_type($info->{content_type});
194 38 50 33     173 if( ! $type or $type eq 'application/octet-stream' ){
195 38         153 $type = $self->guess_type_of($ref_data, $info);
196             }
197              
198 38         568 return Email::MIME->create(
199             attributes => {
200             content_type => $type,
201             encoding => 'binary',
202             filename => $info->{filename},
203             },
204             body => $ref_data,
205             );
206             }
207              
208             sub walk {
209 8     8 1 4061 my $self = shift;
210 8         14 my $data = shift;
211 8         14 my $callback = shift;
212 8   50     48 my $info = shift || {};
213 8         14 my $c = 0;
214              
215 8 100       51 my @medias = ($self->_create_media(
216             ref $data eq 'SCALAR' ? $data : \$data,
217             $info));
218              
219             # reset vars for depth option
220 8         10531 my $ptr = 0;
221 8         15 my $limit = 0;
222 8         12 my $level = 1;
223 8         14 my $bound = scalar @medias;
224            
225             # when expandable contents, then append it to @medias
226 8         41 while( my $media = shift @medias ){
227 38         82 $self->debug("====> shift media, remains=[@{[ scalar @medias ]}]");
  38         226  
228              
229 38         149 my $type = $media->content_type;
230 38         1739 my $plugin = $self->plugin_for($type);
231 38   100     131 $self->debug("* type is [$type], plugin_for [@{[ $plugin || '' ]}]");
  38         2017  
232              
233 38 100 100     263 if( $limit or $self->is_expected( $type ) or ! $plugin ){
      100        
234             # expected or un-expandable data
235 23         59 $self->debug("=> limited, expected or un-expandable data");
236 23 50       169 $callback->($media) if( ref $callback eq 'CODE' );
237 23         1770 ++$c;
238             }else{
239             # expand more
240 15         50 $self->debug("==> expand more");
241             # note: undocumented api is used ->{body}
242             $plugin->expand( $media->{body} , sub {
243 30     30   3252 push @medias, $self->_create_media( @_ );
244 15         185 });
245             }
246              
247 38         1155 ++$ptr;
248 38         235 $self->debug("incremented ptr=[$ptr] | bound=[$bound] level=[$level]");
249 38 100       400 if( $bound <= $ptr ){
250            
251 18 100 66     80 if( $self->depth and $self->depth <= $level ){
252 2         6 $limit = 1;
253 2         660 $self->debug("set limit to TRUE");
254             }
255 18         258 $bound += scalar @medias;
256 18         33 ++$level;
257 18         84 $self->debug("updated bound=[$bound] level=[$level]");
258             }
259             }
260            
261 8         77 return $c;
262             }
263              
264              
265             1;
266             __END__