| 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__ |