| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 3 |  |  | 3 |  | 63769 | use strict; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 100 |  | 
| 2 | 3 |  |  | 3 |  | 15 | use warnings; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 153 |  | 
| 3 |  |  |  |  |  |  | package Email::ARF::Report; | 
| 4 |  |  |  |  |  |  | { | 
| 5 |  |  |  |  |  |  | $Email::ARF::Report::VERSION = '0.010'; | 
| 6 |  |  |  |  |  |  | } | 
| 7 |  |  |  |  |  |  | # ABSTRACT: interpret Abuse Reporting Format (ARF) messages | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 3 |  |  | 3 |  | 16 | use Carp (); | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 64 |  | 
| 10 | 3 |  |  | 3 |  | 2763 | use Email::MIME 1.900 (); # ->subtypes | 
|  | 3 |  |  |  |  | 243544 |  | 
|  | 3 |  |  |  |  | 82 |  | 
| 11 | 3 |  |  | 3 |  | 34 | use Email::MIME::ContentType 1.016 (); # type/subtype | 
|  | 3 |  |  |  |  | 50 |  | 
|  | 3 |  |  |  |  | 63 |  | 
| 12 | 3 |  |  | 3 |  | 17 | use Scalar::Util (); | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 52 |  | 
| 13 | 3 |  |  | 3 |  | 2618 | use Params::Util qw(_INSTANCE); | 
|  | 3 |  |  |  |  | 17054 |  | 
|  | 3 |  |  |  |  | 3072 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | sub new { | 
| 17 | 16 |  |  | 16 | 1 | 23091 | my ($class, $source) = @_; | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 16 | 100 |  |  |  | 235 | Carp::croak "no report source provided" unless $source; | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 15 | 100 |  |  |  | 100 | my $mime = Scalar::Util::blessed $source | 
| 22 |  |  |  |  |  |  | ? $source | 
| 23 |  |  |  |  |  |  | : Email::MIME->new($source); | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | Carp::croak "ARF report source could not be interpreted as MIME message" | 
| 26 | 15 | 100 |  |  |  | 7385 | unless eval { $mime->isa('Email::MIME') }; | 
|  | 15 |  |  |  |  | 218 |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 14 |  |  |  |  | 47 | my $ct_header = $mime->content_type; | 
| 29 | 14 |  |  |  |  | 504 | my $ct = Email::MIME::ContentType::parse_content_type($ct_header); | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 14 | 50 | 66 |  |  | 1407 | Carp::croak "non-ARF content type '$ct_header' on ARF report source" | 
|  |  |  | 66 |  |  |  |  | 
| 32 |  |  |  |  |  |  | unless $ct->{type}  eq 'multipart' | 
| 33 |  |  |  |  |  |  | and    $ct->{subtype} eq 'report' | 
| 34 |  |  |  |  |  |  | and    $ct->{attributes}{'report-type'} eq 'feedback-report'; | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 13 | 100 |  |  |  | 51 | Carp::croak "too few subparts for ARF report" unless $mime->subparts >= 3; | 
| 37 |  |  |  |  |  |  |  | 
| 38 | 12 |  |  |  |  | 131 | my ($description_part, $report_part, $original_part) = $mime->subparts; | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 12 |  |  |  |  | 107 | my $report_header = $report_part->content_type; | 
| 41 | 12 |  |  |  |  | 397 | my $report_ct = Email::MIME::ContentType::parse_content_type($report_header); | 
| 42 | 12 | 50 | 33 |  |  | 355 | Carp::croak "bad content type '$report_header' for machine-readable section" | 
| 43 |  |  |  |  |  |  | unless $report_ct->{type}  eq 'message' | 
| 44 |  |  |  |  |  |  | and    $report_ct->{subtype} eq 'feedback-report'; | 
| 45 |  |  |  |  |  |  |  | 
| 46 | 12 |  |  |  |  | 92 | my $self = bless { | 
| 47 |  |  |  |  |  |  | mime             => $mime, | 
| 48 |  |  |  |  |  |  | description_part => $description_part, | 
| 49 |  |  |  |  |  |  | original_part    => $original_part, | 
| 50 |  |  |  |  |  |  | } => $class; | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 12 |  |  |  |  | 41 | $self->{fields} = $self->_email_from_body($report_part, 1)->header_obj; | 
| 53 | 12 |  |  |  |  | 4265 | $self->{original_email} = $self->_email_from_body($original_part); | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 12 |  |  |  |  | 4075 | return $self; | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub _email_from_body { | 
| 59 | 24 |  |  | 24 |  | 36 | my ($self, $src_email, $append_nl) = @_; | 
| 60 |  |  |  |  |  |  |  | 
| 61 | 24 |  |  |  |  | 68 | my $src_email_body = $src_email->body; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 24 |  |  |  |  | 949 | $src_email_body =~ s/\A(\x0d|\x0a)+//g; | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 24 | 100 |  |  |  | 110 | my $email = Email::MIME->new( | 
| 66 |  |  |  |  |  |  | $append_nl ? "$src_email_body\n" : $src_email_body | 
| 67 |  |  |  |  |  |  | ); | 
| 68 |  |  |  |  |  |  | } | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | sub create { | 
| 72 | 3 |  |  | 3 | 1 | 5854 | my ($class, %arg) = @_; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 3 |  |  |  |  | 44 | require Email::MIME::Creator; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 3 |  |  |  |  | 26 | my $description_part = Email::MIME->create( | 
| 77 |  |  |  |  |  |  | attributes => { content_type => 'text/plain' }, | 
| 78 |  |  |  |  |  |  | body       => $arg{description}, | 
| 79 |  |  |  |  |  |  | ); | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 1 |  |  |  |  | 2 | my $original_body = ref $arg{original_email} | 
| 82 |  |  |  |  |  |  | ? Scalar::Util::blessed $arg{original_email} | 
| 83 |  |  |  |  |  |  | ? $arg{original_email}->as_string | 
| 84 | 3 | 100 |  |  |  | 2645 | : ${ $arg{original_email} } | 
|  |  | 100 |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | : $arg{original_email}; | 
| 86 |  |  |  |  |  |  |  | 
| 87 | 3 |  |  |  |  | 92 | $description_part->header_set('Date'); | 
| 88 |  |  |  |  |  |  |  | 
| 89 | 3 |  |  |  |  | 119 | my $original_part = Email::MIME->create( | 
| 90 |  |  |  |  |  |  | attributes => { content_type => 'message/rfc822' }, | 
| 91 |  |  |  |  |  |  | body       => $original_body, | 
| 92 |  |  |  |  |  |  | ); | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 3 |  |  |  |  | 2041 | $original_part->header_set('Date'); | 
| 95 |  |  |  |  |  |  |  | 
| 96 | 3 |  |  |  |  | 12 | my $field_pairs = ref $arg{fields} eq 'HASH' | 
| 97 | 3 | 50 |  |  |  | 113 | ? [ %{ $arg{fields} } ] | 
| 98 |  |  |  |  |  |  | : $arg{fields}; | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 3 |  |  |  |  | 17 | my $fields = Email::Simple->create(header => $field_pairs); | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 3 |  |  |  |  | 958 | $fields->header_set('Date'); | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 3 | 50 |  |  |  | 98 | unless (defined $fields->header('user-agent')) { | 
| 105 | 3 |  | 50 |  |  | 114 | $fields->header_set( | 
| 106 |  |  |  |  |  |  | 'User-Agent', | 
| 107 |  |  |  |  |  |  | "$class/" . ($class->VERSION || '(dev)') | 
| 108 |  |  |  |  |  |  | ); | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 3 | 50 |  |  |  | 99 | unless (defined $fields->header('version')) { | 
| 112 | 3 |  |  |  |  | 64 | $fields->header_set('Version', "1"); | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 3 | 50 |  |  |  | 101 | unless (defined $fields->header('Feedback-Type')) { | 
| 116 | 3 |  |  |  |  | 60 | $fields->header_set('Feedback-Type', "other"); | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 3 |  |  |  |  | 104 | my $report_part = Email::MIME->create( | 
| 120 |  |  |  |  |  |  | attributes => { content_type => 'message/feedback-report' }, | 
| 121 |  |  |  |  |  |  | body       => $fields->header_obj->as_string, | 
| 122 |  |  |  |  |  |  | ); | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 3 |  |  |  |  | 1962 | $report_part->header_set('Date'); | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 3 |  | 50 |  |  | 125 | my $report = Email::MIME->create( | 
|  |  |  | 50 |  |  |  |  | 
| 127 |  |  |  |  |  |  | attributes => { | 
| 128 |  |  |  |  |  |  | # It is so asinine that I need to do this!  Only certain blessed | 
| 129 |  |  |  |  |  |  | # attributes are heeded, here.  The rest are dropped. -- rjbs, 2007-03-21 | 
| 130 |  |  |  |  |  |  | content_type  => 'multipart/report; report-type="feedback-report"', | 
| 131 |  |  |  |  |  |  | }, | 
| 132 |  |  |  |  |  |  | parts  => [ $description_part, $report_part, $original_part ], | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | header     => $arg{header}     || [], | 
| 135 |  |  |  |  |  |  | header_str => $arg{header_str} || [], | 
| 136 |  |  |  |  |  |  | ); | 
| 137 |  |  |  |  |  |  |  | 
| 138 | 3 |  |  |  |  | 10190 | $class->new($report); | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | sub as_email { | 
| 143 | 9 |  |  | 9 | 1 | 4087 | return Email::MIME->new($_[0]->as_string) | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 18 |  |  | 18 | 1 | 89 | sub as_string { $_[0]->{mime}->as_string } | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | sub original_email { | 
| 151 | 9 |  |  | 9 | 1 | 4403 | $_[0]->{original_email} | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 9 |  |  | 9 |  | 39 | sub _description_part { $_[0]->{description_part} } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub description { | 
| 158 | 9 |  |  | 9 | 1 | 4503 | $_[0]->_description_part->body; | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  |  | 
| 161 | 42 |  |  | 42 |  | 167 | sub _fields { $_[0]->{fields} } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | sub field { | 
| 165 | 42 |  |  | 42 | 1 | 7841 | my ($self, $field) = @_; | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 42 |  |  |  |  | 95 | return $self->_fields->header($field); | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  |  | 
| 171 | 9 |  |  | 9 | 1 | 18070 | sub feedback_type { $_[0]->field('Feedback-Type'); } | 
| 172 | 9 |  |  | 9 | 1 | 4533 | sub user_agent    { $_[0]->field('User-Agent');    } | 
| 173 | 9 |  |  | 9 | 1 | 4221 | sub arf_version   { $_[0]->field('Version');       } | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | 1; | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | __END__ |