File Coverage

blib/lib/MIME/Expander.pm
Criterion Covered Total %
statement 159 159 100.0
branch 66 74 89.1
condition 33 46 71.7
subroutine 25 25 100.0
pod 10 11 90.9
total 293 315 93.0


line stmt bran cond sub pod time code
1             package MIME::Expander;
2              
3 3     3   84075 use strict;
  3         6  
  3         131  
4 3     3   15 use warnings;
  3         5  
  3         104  
5 3     3   13 use vars qw($VERSION);
  3         8  
  3         197  
6             $VERSION = '0.02';
7              
8 3     3   15 use vars qw($PrefixGuess $PrefixPlugin @DefaultGuesser @EnabledPlugins);
  3         4  
  3         285  
9             BEGIN {
10 3     3   12 $PrefixGuess = 'MIME::Expander::Guess';
11 3         10 $PrefixPlugin = 'MIME::Expander::Plugin';
12 3         8 @DefaultGuesser = ('MMagic', 'FileName');
13 3         83 @EnabledPlugins = ();
14             }
15              
16 3     3   1436 use Email::MIME;
  3         123019  
  3         68  
17 3     3   19 use Email::MIME::ContentType ();
  3         4  
  3         39  
18 3     3   1373 use MIME::Type;
  3         5246  
  3         783  
19 3     3   1687 use Module::Load;
  3         2602  
  3         30  
20 3     3   1664 use Module::Pluggable search_path => $PrefixPlugin, sub_name => 'expanders';
  3         23299  
  3         63  
21 3     3   1577 use MIME::Expander::Plugin::MessageRFC822;
  3         7  
  3         78  
22 3     3   15 use Scalar::Util 'blessed';
  3         5  
  3         3650  
23              
24             sub import {
25 3     3   534 my $class = shift;
26 3         3792 @EnabledPlugins = @_;
27             }
28              
29             sub regulate_type {
30 349 100   349 1 461989 return undef unless( defined $_[1] );
31 301         364 my $type = $_[1];
32              
33             # There is regexp from Email::MIME::ContentType 1.015
34 301         386 my $tspecials = quotemeta '()<>@,;:\\"/[]?=';
35 301         1357 my $discrete = qr/[^$tspecials]+/;
36 301         848 my $composite = qr/[^$tspecials]+/;
37 301         513 my $params = qr/;.*/;
38 301 100       2326 return undef unless( $type =~ m[ ^ ($discrete) / ($composite) \s* ($params)? $ ]x );
39              
40 299         822 my $ct = Email::MIME::ContentType::parse_content_type($type);
41 299 50 33     6907 return undef if( ! $ct->{discrete} or ! $ct->{composite} );
42 299         1383 return MIME::Type->simplified(join('/',$ct->{discrete}, $ct->{composite}));
43             }
44              
45             sub new {
46 18     18 0 32029 my $class = shift;
47 18   33     97 $class = ref $class || $class;
48 18         77 my $self = {
49             expects => [],
50             guesser => undef,
51             depth => undef,
52             };
53 18         38 bless $self, $class;
54 18         48 return $self->init(@_);
55             }
56              
57             sub init {
58 22     22 1 33 my $self = shift;
59 22         24 my $args;
60 22 100       78 if( 0 == @_ % 2 ){
61 19         34 $args = { @_ }
62             }else{
63 3   50     10 $args = shift || {};
64             }
65              
66 22 100       73 $self->expects(
67             exists $args->{expects} ? $args->{expects} : [] );
68              
69 22 50       77 $self->guesser(
70             exists $args->{guesser} ? $args->{guesser} : undef );
71              
72 22 100       62 $self->depth(
73             exists $args->{depth} ? $args->{depth} : undef );
74              
75 22         54 return $self;
76             }
77              
78             sub expects {
79 144     144 1 763 my $self = shift;
80 144 100       258 if( @_ ){
81 27         48 $self->{expects} = shift;
82 27 100 100     153 die "setting value is not acceptable, it requires an reference of ARRAY"
83             if( defined $self->{expects} and ref($self->{expects}) ne 'ARRAY' );
84             }
85 143         359 return $self->{expects};
86             }
87              
88             sub is_expected {
89 61     61 1 88 my $self = shift;
90 61 50       115 my $type = shift or undef;
91 61 100       281 die "invalid type $type that has not looks as mime/type"
92             if( $type !~ m,^.+/.+$, );
93 60 100       118 return () unless( $self->expects );
94 55 100       76 for my $regexp ( map { ref $_ ? $_ : qr/$_/ } @{$self->expects} ){
  38         258  
  55         95  
95 33 100       145 return 1 if( $type =~ $regexp );
96             }
97 46         298 return ();
98             }
99              
100             sub depth {
101 57     57 1 625 my $self = shift;
102 57 100       129 if( @_ ){
103 26         32 $self->{depth} = shift;
104 26 100 100     91 die "setting value is not acceptable, it requires a native number"
105             if( defined $self->{depth} and $self->{depth} =~ /\D/ );
106             }
107 56         139 return $self->{depth};
108             }
109              
110             sub guesser {
111 83     83 1 1320 my $self = shift;
112 83 100       175 if( @_ ){
113 27         33 $self->{guesser} = shift;
114 27 100 100     106 die "setting value is not acceptable, it requires an reference of CODE or ARRAY"
      100        
115             if( defined $self->{guesser}
116             and ref($self->{guesser}) ne 'CODE'
117             and ref($self->{guesser}) ne 'ARRAY');
118             }
119 82         239 return $self->{guesser};
120             }
121              
122             sub guess_type_of {
123 51     51 1 71 my $self = shift;
124 51 50       143 my $ref_data = shift or die "missing mandatory parameter";
125 51   50     140 my $info = shift || {};
126            
127 51         59 my $type = undef;
128 51         145 my $routine = $self->guesser;
129              
130 51 100       146 if( ref $routine eq 'CODE' ){
131 2         4 $type = $self->guesser->($ref_data, $info);
132              
133             }else{
134 49         53 my @routines;
135 49 100       114 if( ref $routine eq 'ARRAY' ){
136 2         4 @routines = @$routine;
137             }else{
138 47         125 @routines = @DefaultGuesser;
139             }
140 49         88 for my $klass ( @routines ){
141 50 50       254 $klass = join('::', $PrefixGuess, $klass) if( $klass !~ /:/ );
142 50         188 Module::Load::load $klass;
143 50         2897 $type = $self->regulate_type( $klass->type($ref_data, $info) );
144 50 100 66     4142 last if( $type and $type ne 'application/octet-stream');
145             }
146             }
147 51   100     239 return ($type || 'application/octet-stream');
148             }
149              
150             sub plugin_for {
151 50     50 1 437 my $self = shift;
152 50         68 my $type = shift;
153              
154 50         44 my $plugin = undef;
155 50         181 for my $available ( $self->expanders ){
156              
157 203         90552 my $klass = undef;
158 203 100       380 unless( @EnabledPlugins ){
159 195         199 $klass = $available;
160             }else{
161 8         10 for my $enable ( @EnabledPlugins ){
162 8 100       20 $enable = join('::', $PrefixPlugin, $enable)
163             if( $enable !~ /:/ );
164 8 100       17 if( $available eq $enable ){
165 2         4 $klass = $available;
166 2         3 last;
167             }
168             }
169 8 100       18 next unless( $klass );
170             }
171            
172 197         485 Module::Load::load $klass;
173 197 100       8530 if( $klass->is_acceptable( $self->regulate_type($type) ) ){
174 24         123 $plugin = $klass->new;
175 24         56 last;
176             }
177             }
178 50         139 return $plugin;
179             }
180              
181             sub create_media {
182 45     45 1 74 my $self = shift;
183 45 50       108 my $ref_data = shift or die "missing mandatory parameter";
184 45   50     93 my $info = shift || {};
185              
186 45         138 my $type = $self->regulate_type($info->{content_type});
187 45 50 33     137 if( ! $type or $type eq 'application/octet-stream' ){
188 45         110 $type = $self->guess_type_of($ref_data, $info);
189             }
190            
191 45 100       130 if( MIME::Expander::Plugin::MessageRFC822->is_acceptable(
192             $self->regulate_type($type)
193             )){
194 3         15 return Email::MIME->new($ref_data);
195             }else{
196 42         440 return Email::MIME->create(
197             attributes => {
198             content_type => $type,
199             encoding => 'binary',
200             filename => $info->{filename},
201             },
202             body => $ref_data,
203             );
204             }
205             }
206              
207             sub walk {
208 9     9 1 2004 my $self = shift;
209 9         17 my $data = shift;
210 9         13 my $callback = shift;
211 9   50     41 my $info = shift || {};
212 9         10 my $c = 0;
213              
214 9         18 my @medias = ();
215 9 100 66     58 if( blessed($data) and $data->isa('Email::Simple') ){
216 1         2 push @medias, $data;
217             }else{
218 8 100       39 @medias = ($self->create_media(
219             ref $data eq 'SCALAR' ? $data : \$data,
220             $info));
221             }
222              
223             # reset vars for depth option
224 9         10046 my $ptr = 0;
225 9         54 my $limit = 0;
226 9         18 my $level = 1;
227 9         15 my $bound = scalar @medias;
228            
229             # when expandable contents, then append it to @medias
230 9         37 while( my $media = shift @medias ){
231 46         138 my $type = $media->content_type;
232 46         1443 my $plugin = $self->plugin_for($type);
233 46 100 100     197 if( $limit or $self->is_expected( $type ) or ! $plugin ){
      100        
234             # expected or un-expandable data
235 28 50       136 $callback->($media) if( ref $callback eq 'CODE' );
236 28         4956 ++$c;
237             }else{
238             # expand more
239             $plugin->expand( $media , sub {
240 37     37   2827 push @medias, $self->create_media( @_ );
241 18         163 });
242             }
243              
244 46         1036 ++$ptr;
245 46 100       340 if( $bound <= $ptr ){
246 22 100 66     78 if( $self->depth and $self->depth <= $level ){
247 2         3 $limit = 1;
248             }
249 22         40 $bound += scalar @medias;
250 22         405 ++$level;
251             }
252             }
253            
254 9         60 return $c;
255             }
256              
257              
258             1;
259             __END__