Filename | /usr/local/lib/perl5/site_perl/Mail/SpamAssassin/Message/Node.pm |
Statements | Executed 1119595 statements in 8.21s |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
36038 | 9 | 2 | 2.59s | 3.71s | header | Mail::SpamAssassin::Message::Node::
789 | 2 | 2 | 1.29s | 3.90s | get_all_headers | Mail::SpamAssassin::Message::Node::
25966 | 8 | 6 | 1.24s | 2.85s | get_header | Mail::SpamAssassin::Message::Node::
936 | 4 | 1 | 945ms | 1.25s | delete_header | Mail::SpamAssassin::Message::Node::
8191 | 1 | 1 | 559ms | 701ms | _decode_header | Mail::SpamAssassin::Message::Node::
124601 | 14 | 1 | 510ms | 510ms | CORE:subst (opcode) | Mail::SpamAssassin::Message::Node::
57114 | 3 | 1 | 196ms | 196ms | CORE:regcomp (opcode) | Mail::SpamAssassin::Message::Node::
66604 | 9 | 1 | 190ms | 190ms | CORE:match (opcode) | Mail::SpamAssassin::Message::Node::
789 | 1 | 1 | 175ms | 175ms | CORE:sort (opcode) | Mail::SpamAssassin::Message::Node::
702 | 1 | 1 | 95.9ms | 136ms | find_parts | Mail::SpamAssassin::Message::Node::
409 | 1 | 1 | 74.4ms | 826ms | decode | Mail::SpamAssassin::Message::Node::
1275 | 3 | 2 | 71.0ms | 23.0s | rendered | Mail::SpamAssassin::Message::Node::
936 | 1 | 1 | 31.3ms | 36.3ms | raw_header | Mail::SpamAssassin::Message::Node::
1866 | 1 | 1 | 21.1ms | 21.1ms | is_leaf | Mail::SpamAssassin::Message::Node::
622 | 3 | 1 | 18.4ms | 18.4ms | new | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 16.3ms | 43.4ms | BEGIN@45 | Mail::SpamAssassin::Message::Node::
388 | 1 | 1 | 11.8ms | 15.1ms | add_body_part | Mail::SpamAssassin::Message::Node::
217 | 1 | 1 | 11.4ms | 12.6ms | _html_render | Mail::SpamAssassin::Message::Node::
425 | 1 | 1 | 11.0ms | 14.7ms | invisible_rendered | Mail::SpamAssassin::Message::Node::
425 | 1 | 1 | 9.02ms | 15.2ms | visible_rendered | Mail::SpamAssassin::Message::Node::
118 | 1 | 1 | 5.64ms | 15.1ms | __decode_header | Mail::SpamAssassin::Message::Node::
250 | 2 | 1 | 1.99ms | 1.99ms | CORE:substcont (opcode) | Mail::SpamAssassin::Message::Node::
118 | 1 | 1 | 1.80ms | 1.80ms | _normalize | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 675µs | 5.38ms | BEGIN@49 | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 89µs | 98µs | BEGIN@37 | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 34µs | 102µs | BEGIN@39 | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 34µs | 61µs | BEGIN@38 | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 28µs | 759µs | BEGIN@44 | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 26µs | 228µs | BEGIN@46 | Mail::SpamAssassin::Message::Node::
1 | 1 | 1 | 13µs | 13µs | BEGIN@43 | Mail::SpamAssassin::Message::Node::
0 | 0 | 0 | 0s | 0s | content_summary | Mail::SpamAssassin::Message::Node::
0 | 0 | 0 | 0s | 0s | finish | Mail::SpamAssassin::Message::Node::
0 | 0 | 0 | 0s | 0s | raw | Mail::SpamAssassin::Message::Node::
0 | 0 | 0 | 0s | 0s | set_rendered | Mail::SpamAssassin::Message::Node::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # <@LICENSE> | ||||
2 | # Licensed to the Apache Software Foundation (ASF) under one or more | ||||
3 | # contributor license agreements. See the NOTICE file distributed with | ||||
4 | # this work for additional information regarding copyright ownership. | ||||
5 | # The ASF licenses this file to you under the Apache License, Version 2.0 | ||||
6 | # (the "License"); you may not use this file except in compliance with | ||||
7 | # the License. You may obtain a copy of the License at: | ||||
8 | # | ||||
9 | # http://www.apache.org/licenses/LICENSE-2.0 | ||||
10 | # | ||||
11 | # Unless required by applicable law or agreed to in writing, software | ||||
12 | # distributed under the License is distributed on an "AS IS" BASIS, | ||||
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
14 | # See the License for the specific language governing permissions and | ||||
15 | # limitations under the License. | ||||
16 | # </@LICENSE> | ||||
17 | |||||
18 | =head1 NAME | ||||
19 | |||||
20 | Mail::SpamAssassin::Message::Node - decode, render, and make available MIME message parts | ||||
21 | |||||
22 | =head1 SYNOPSIS | ||||
23 | |||||
24 | =head1 DESCRIPTION | ||||
25 | |||||
26 | This module will encapsulate an email message and allow access to | ||||
27 | the various MIME message parts. | ||||
28 | |||||
29 | =head1 PUBLIC METHODS | ||||
30 | |||||
31 | =over 4 | ||||
32 | |||||
33 | =cut | ||||
34 | |||||
35 | package Mail::SpamAssassin::Message::Node; | ||||
36 | |||||
37 | 2 | 65µs | 2 | 106µs | # spent 98µs (89+9) within Mail::SpamAssassin::Message::Node::BEGIN@37 which was called:
# once (89µs+9µs) by Mail::SpamAssassin::Message::BEGIN@55 at line 37 # spent 98µs making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@37
# spent 8µs making 1 call to strict::import |
38 | 2 | 76µs | 2 | 88µs | # spent 61µs (34+27) within Mail::SpamAssassin::Message::Node::BEGIN@38 which was called:
# once (34µs+27µs) by Mail::SpamAssassin::Message::BEGIN@55 at line 38 # spent 61µs making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@38
# spent 27µs making 1 call to warnings::import |
39 | 2 | 90µs | 2 | 170µs | # spent 102µs (34+68) within Mail::SpamAssassin::Message::Node::BEGIN@39 which was called:
# once (34µs+68µs) by Mail::SpamAssassin::Message::BEGIN@55 at line 39 # spent 102µs making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@39
# spent 68µs making 1 call to re::import |
40 | |||||
41 | 1 | 25µs | require 5.008001; # needs utf8::is_utf8() | ||
42 | |||||
43 | 2 | 74µs | 1 | 13µs | # spent 13µs within Mail::SpamAssassin::Message::Node::BEGIN@43 which was called:
# once (13µs+0s) by Mail::SpamAssassin::Message::BEGIN@55 at line 43 # spent 13µs making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@43 |
44 | 2 | 78µs | 2 | 1.49ms | # spent 759µs (28+731) within Mail::SpamAssassin::Message::Node::BEGIN@44 which was called:
# once (28µs+731µs) by Mail::SpamAssassin::Message::BEGIN@55 at line 44 # spent 759µs making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@44
# spent 731µs making 1 call to Exporter::import |
45 | 2 | 333µs | 1 | 43.4ms | # spent 43.4ms (16.3+27.1) within Mail::SpamAssassin::Message::Node::BEGIN@45 which was called:
# once (16.3ms+27.1ms) by Mail::SpamAssassin::Message::BEGIN@55 at line 45 # spent 43.4ms making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@45 |
46 | 2 | 243µs | 2 | 431µs | # spent 228µs (26+203) within Mail::SpamAssassin::Message::Node::BEGIN@46 which was called:
# once (26µs+203µs) by Mail::SpamAssassin::Message::BEGIN@55 at line 46 # spent 228µs making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@46
# spent 203µs making 1 call to Exporter::import |
47 | |||||
48 | our($enc_utf8, $enc_w1252, $have_encode_detector); | ||||
49 | # spent 5.38ms (675µs+4.70) within Mail::SpamAssassin::Message::Node::BEGIN@49 which was called:
# once (675µs+4.70ms) by Mail::SpamAssassin::Message::BEGIN@55 at line 55 | ||||
50 | 1 | 4µs | eval { require Encode } | ||
51 | 2 | 17µs | 1 | 346µs | and do { $enc_utf8 = Encode::find_encoding('UTF-8'); # spent 346µs making 1 call to Encode::find_encoding |
52 | 1 | 7µs | 1 | 1.35ms | $enc_w1252 = Encode::find_encoding('Windows-1252') }; # spent 1.35ms making 1 call to Encode::find_encoding |
53 | 1 | 333µs | eval { require Encode::Detect::Detector } | ||
54 | 2 | 21µs | and do { $have_encode_detector = 1 }; | ||
55 | 1 | 7.92ms | 1 | 5.38ms | }; # spent 5.38ms making 1 call to Mail::SpamAssassin::Message::Node::BEGIN@49 |
56 | |||||
57 | =item new() | ||||
58 | |||||
59 | Generates an empty Node object and returns it. Typically only called | ||||
60 | by functions in Message. | ||||
61 | |||||
62 | =cut | ||||
63 | |||||
64 | # spent 18.4ms within Mail::SpamAssassin::Message::Node::new which was called 622 times, avg 30µs/call:
# 234 times (7.26ms+0s) by Mail::SpamAssassin::Message::new at line 115 of Mail/SpamAssassin/Message.pm, avg 31µs/call
# 197 times (5.34ms+0s) by Mail::SpamAssassin::Message::_parse_multipart at line 873 of Mail/SpamAssassin/Message.pm, avg 27µs/call
# 191 times (5.82ms+0s) by Mail::SpamAssassin::Message::_parse_multipart at line 952 of Mail/SpamAssassin/Message.pm, avg 30µs/call | ||||
65 | 622 | 1.48ms | my $class = shift; | ||
66 | 622 | 1.50ms | $class = ref($class) || $class; | ||
67 | |||||
68 | 622 | 5.75ms | my $self = { | ||
69 | headers => {}, | ||||
70 | raw_headers => {}, | ||||
71 | header_order => [] | ||||
72 | }; | ||||
73 | |||||
74 | # deal with any parameters | ||||
75 | 622 | 1.34ms | my($opts) = @_; | ||
76 | 622 | 2.53ms | $self->{normalize} = $opts->{'normalize'} || 0; | ||
77 | |||||
78 | 622 | 1.80ms | bless($self,$class); | ||
79 | 622 | 6.19ms | $self; | ||
80 | } | ||||
81 | |||||
82 | =item find_parts() | ||||
83 | |||||
84 | Used to search the tree for specific MIME parts. An array of matching | ||||
85 | Node objects (pointers into the tree) is returned. The parameters that | ||||
86 | can be passed in are (in order, all scalars): | ||||
87 | |||||
88 | Regexp - Used to match against each part's Content-Type header, | ||||
89 | specifically the type and not the rest of the header. ie: "Content-type: | ||||
90 | text/html; encoding=quoted-printable" has a type of "text/html". If no | ||||
91 | regexp is specified, find_parts() will return an empty array. | ||||
92 | |||||
93 | Only_leaves - By default, find_parts() will return any part that matches | ||||
94 | the regexp, including multipart. If you only want to see leaves of the | ||||
95 | tree (ie: parts that aren't multipart), set this to true (1). | ||||
96 | |||||
97 | Recursive - By default, when find_parts() finds a multipart which has | ||||
98 | parts underneath it, it will recurse through all sub-children. If set to 0, | ||||
99 | only look at the part and any direct children of the part. | ||||
100 | |||||
101 | =cut | ||||
102 | |||||
103 | # Used to find any MIME parts whose simple content-type matches a given regexp | ||||
104 | # Searches it's own and any children parts. Returns an array of MIME | ||||
105 | # objects which match. Our callers may expect the default behavior which is a | ||||
106 | # depth-first array of parts. | ||||
107 | # | ||||
108 | # spent 136ms (95.9+40.0) within Mail::SpamAssassin::Message::Node::find_parts which was called 702 times, avg 194µs/call:
# 702 times (95.9ms+40.0ms) by Mail::SpamAssassin::Message::find_parts at line 423 of Mail/SpamAssassin/Message.pm, avg 194µs/call | ||||
109 | 702 | 1.83ms | my ($self, $re, $onlyleaves, $recursive) = @_; | ||
110 | |||||
111 | # Didn't pass an RE? Just abort. | ||||
112 | 702 | 2.12ms | return () unless defined $re && $re ne ''; | ||
113 | |||||
114 | 702 | 1.32ms | $onlyleaves = 0 unless defined $onlyleaves; | ||
115 | |||||
116 | 702 | 1.33ms | my $depth; | ||
117 | 702 | 1.40ms | if (defined $recursive && $recursive == 0) { | ||
118 | $depth = 1; | ||||
119 | } | ||||
120 | |||||
121 | 702 | 1.37ms | my @ret; | ||
122 | 702 | 2.49ms | my @search = ( $self ); | ||
123 | |||||
124 | 702 | 14.1ms | while (my $part = shift @search) { | ||
125 | # If this object matches, mark it for return. | ||||
126 | 1866 | 14.7ms | 1866 | 21.1ms | my $amialeaf = $part->is_leaf(); # spent 21.1ms making 1866 calls to Mail::SpamAssassin::Message::Node::is_leaf, avg 11µs/call |
127 | |||||
128 | 1866 | 48.0ms | 3732 | 18.9ms | if ( $part->{'type'} =~ /$re/ && (!$onlyleaves || $amialeaf) ) { # spent 11.7ms making 1866 calls to Mail::SpamAssassin::Message::Node::CORE:regcomp, avg 6µs/call
# spent 7.26ms making 1866 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 4µs/call |
129 | 1275 | 2.80ms | push(@ret, $part); | ||
130 | } | ||||
131 | |||||
132 | 1866 | 7.80ms | if ( !$amialeaf && (!defined $depth || $depth > 0)) { | ||
133 | 591 | 1.12ms | $depth-- if defined $depth; | ||
134 | 1182 | 4.64ms | unshift(@search, @{$part->{'body_parts'}}); | ||
135 | } | ||||
136 | } | ||||
137 | |||||
138 | 702 | 6.62ms | return @ret; | ||
139 | } | ||||
140 | |||||
141 | =item header() | ||||
142 | |||||
143 | Stores and retrieves headers from a specific MIME part. The first | ||||
144 | parameter is the header name. If there is no other parameter, the header | ||||
145 | is retrieved. If there is a second parameter, the header is stored. | ||||
146 | |||||
147 | Header names are case-insensitive and are stored in both raw and | ||||
148 | decoded form. Using header(), only the decoded form is retrievable. | ||||
149 | |||||
150 | For retrieval, if header() is called in an array context, an array will | ||||
151 | be returned with each header entry in a different element. In a scalar | ||||
152 | context, the last specific header is returned. | ||||
153 | |||||
154 | ie: If 'Subject' is specified as the header, and there are 2 Subject | ||||
155 | headers in a message, the last/bottom one in the message is returned in | ||||
156 | scalar context or both are returned in array context. | ||||
157 | |||||
158 | =cut | ||||
159 | |||||
160 | # Store or retrieve headers from a given MIME object | ||||
161 | # | ||||
162 | # spent 3.71s (2.59+1.12) within Mail::SpamAssassin::Message::Node::header which was called 36038 times, avg 103µs/call:
# 25966 times (1.45s+150ms) by Mail::SpamAssassin::Message::Node::get_header at line 863, avg 62µs/call
# 7410 times (936ms+893ms) by Mail::SpamAssassin::Message::new at line 282 of Mail/SpamAssassin/Message.pm, avg 247µs/call
# 425 times (20.0ms+3.38ms) by Mail::SpamAssassin::Message::_parse_normal at line 1034 of Mail/SpamAssassin/Message.pm, avg 55µs/call
# 425 times (17.1ms+4.63ms) by Mail::SpamAssassin::Message::_parse_normal at line 1042 of Mail/SpamAssassin/Message.pm, avg 51µs/call
# 409 times (19.1ms+3.19ms) by Mail::SpamAssassin::Message::Node::decode at line 335, avg 54µs/call
# 393 times (66.9ms+33.1ms) by Mail::SpamAssassin::Message::_parse_multipart at line 965 of Mail/SpamAssassin/Message.pm, avg 254µs/call
# 388 times (45.6ms+31.3ms) by Mail::SpamAssassin::Message::_parse_multipart at line 978 of Mail/SpamAssassin/Message.pm, avg 198µs/call
# 388 times (20.0ms+2.70ms) by Mail::SpamAssassin::Message::_parse_multipart at line 922 of Mail/SpamAssassin/Message.pm, avg 58µs/call
# 234 times (12.6ms+1.47ms) by Mail::SpamAssassin::Message::new at line 363 of Mail/SpamAssassin/Message.pm, avg 60µs/call | ||||
163 | 36038 | 63.7ms | my $self = shift; | ||
164 | 36038 | 76.2ms | my $rawkey = shift; | ||
165 | |||||
166 | 36038 | 60.4ms | return unless defined $rawkey; | ||
167 | |||||
168 | # we're going to do things case insensitively | ||||
169 | 36038 | 90.3ms | my $key = lc($rawkey); | ||
170 | |||||
171 | # Trim whitespace off of the header keys | ||||
172 | 36038 | 561ms | 36038 | 136ms | $key =~ s/^\s+//; # spent 136ms making 36038 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 4µs/call |
173 | 36038 | 492ms | 36038 | 86.6ms | $key =~ s/\s+$//; # spent 86.6ms making 36038 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 2µs/call |
174 | |||||
175 | 36038 | 64.1ms | if (@_) { | ||
176 | 8191 | 21.9ms | my $raw_value = shift; | ||
177 | 8191 | 14.4ms | return unless defined $raw_value; | ||
178 | |||||
179 | 16382 | 83.9ms | push @{ $self->{'header_order'} }, $rawkey; | ||
180 | 8191 | 30.0ms | if ( !exists $self->{'headers'}->{$key} ) { | ||
181 | 7183 | 58.6ms | $self->{'headers'}->{$key} = []; | ||
182 | 7183 | 22.8ms | $self->{'raw_headers'}->{$key} = []; | ||
183 | } | ||||
184 | |||||
185 | 8191 | 19.7ms | my $dec_value = $raw_value; | ||
186 | 8191 | 166ms | 8191 | 54.0ms | $dec_value =~ s/\n[ \t]+/ /gs; # spent 54.0ms making 8191 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 7µs/call |
187 | 8191 | 147ms | 8191 | 84.6ms | $dec_value =~ s/\s+$//s; # spent 84.6ms making 8191 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 10µs/call |
188 | 8191 | 135ms | 8191 | 61.1ms | $dec_value =~ s/^\s+//s; # spent 61.1ms making 8191 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 7µs/call |
189 | 16382 | 103ms | 8191 | 701ms | push @{ $self->{'headers'}->{$key} }, $self->_decode_header($dec_value,$key); # spent 701ms making 8191 calls to Mail::SpamAssassin::Message::Node::_decode_header, avg 86µs/call |
190 | |||||
191 | 16382 | 82.6ms | push @{ $self->{'raw_headers'}->{$key} }, $raw_value; | ||
192 | |||||
193 | 8191 | 123ms | return $self->{'headers'}->{$key}->[-1]; | ||
194 | } | ||||
195 | |||||
196 | 27847 | 50.8ms | if (wantarray) { | ||
197 | 27013 | 148ms | return unless exists $self->{'headers'}->{$key}; | ||
198 | 49268 | 612ms | return @{ $self->{'headers'}->{$key} }; | ||
199 | } | ||||
200 | else { | ||||
201 | 834 | 7.34ms | return '' unless exists $self->{'headers'}->{$key}; | ||
202 | 383 | 5.22ms | return $self->{'headers'}->{$key}->[-1]; | ||
203 | } | ||||
204 | } | ||||
205 | |||||
206 | =item raw_header() | ||||
207 | |||||
208 | Retrieves the raw version of headers from a specific MIME part. The only | ||||
209 | parameter is the header name. Header names are case-insensitive. | ||||
210 | |||||
211 | For retrieval, if raw_header() is called in an array context, an array | ||||
212 | will be returned with each header entry in a different element. In a | ||||
213 | scalar context, the last specific header is returned. | ||||
214 | |||||
215 | ie: If 'Subject' is specified as the header, and there are 2 Subject | ||||
216 | headers in a message, the last/bottom one in the message is returned in | ||||
217 | scalar context or both are returned in array context. | ||||
218 | |||||
219 | =cut | ||||
220 | |||||
221 | # Retrieve raw headers from a given MIME object | ||||
222 | # | ||||
223 | # spent 36.3ms (31.3+5.02) within Mail::SpamAssassin::Message::Node::raw_header which was called 936 times, avg 39µs/call:
# 936 times (31.3ms+5.02ms) by Mail::SpamAssassin::PerMsgStatus::_get at line 1982 of Mail/SpamAssassin/PerMsgStatus.pm, avg 39µs/call | ||||
224 | 936 | 1.87ms | my $self = shift; | ||
225 | 936 | 2.54ms | my $key = lc(shift); | ||
226 | |||||
227 | # Trim whitespace off of the header keys | ||||
228 | 936 | 9.78ms | 936 | 2.71ms | $key =~ s/^\s+//; # spent 2.71ms making 936 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 3µs/call |
229 | 936 | 8.41ms | 936 | 2.31ms | $key =~ s/\s+$//; # spent 2.31ms making 936 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 2µs/call |
230 | |||||
231 | 936 | 1.82ms | if (wantarray) { | ||
232 | 936 | 9.44ms | return unless exists $self->{'raw_headers'}->{$key}; | ||
233 | 468 | 17.9ms | return @{ $self->{'raw_headers'}->{$key} }; | ||
234 | } | ||||
235 | else { | ||||
236 | return '' unless exists $self->{'raw_headers'}->{$key}; | ||||
237 | return $self->{'raw_headers'}->{$key}->[-1]; | ||||
238 | } | ||||
239 | } | ||||
240 | |||||
241 | =item add_body_part() | ||||
242 | |||||
243 | Adds a Node child object to the current node object. | ||||
244 | |||||
245 | =cut | ||||
246 | |||||
247 | # Add a MIME child part to ourselves | ||||
248 | # spent 15.1ms (11.8+3.35) within Mail::SpamAssassin::Message::Node::add_body_part which was called 388 times, avg 39µs/call:
# 388 times (11.8ms+3.35ms) by Mail::SpamAssassin::Message::_parse_multipart at line 930 of Mail/SpamAssassin/Message.pm, avg 39µs/call | ||||
249 | 388 | 869µs | my($self, $part) = @_; | ||
250 | |||||
251 | 388 | 4.25ms | 388 | 3.35ms | dbg("message: added part, type: ".$part->{'type'}); # spent 3.35ms making 388 calls to Mail::SpamAssassin::Logger::dbg, avg 9µs/call |
252 | 776 | 5.41ms | push @{ $self->{'body_parts'} }, $part; | ||
253 | } | ||||
254 | |||||
255 | =item is_leaf() | ||||
256 | |||||
257 | Returns true if the tree node in question is a leaf of the tree (ie: | ||||
258 | has no children of its own). Note: This function may return odd results | ||||
259 | unless the message has been mime parsed via _do_parse()! | ||||
260 | |||||
261 | =cut | ||||
262 | |||||
263 | # spent 21.1ms within Mail::SpamAssassin::Message::Node::is_leaf which was called 1866 times, avg 11µs/call:
# 1866 times (21.1ms+0s) by Mail::SpamAssassin::Message::Node::find_parts at line 126, avg 11µs/call | ||||
264 | 1866 | 3.45ms | my($self) = @_; | ||
265 | 1866 | 22.5ms | return !exists $self->{'body_parts'}; | ||
266 | } | ||||
267 | |||||
268 | =item raw() | ||||
269 | |||||
270 | Return a reference to the the raw array. Treat this as READ ONLY. | ||||
271 | |||||
272 | =cut | ||||
273 | |||||
274 | sub raw { | ||||
275 | my $self = shift; | ||||
276 | |||||
277 | # Ok, if we're called we are expected to return an array. | ||||
278 | # so if it's a file reference, read in the message into an array... | ||||
279 | # | ||||
280 | # NOTE: that "ref undef" works, so don't bother checking for a defined var | ||||
281 | # first. | ||||
282 | if (ref $self->{'raw'} eq 'GLOB') { | ||||
283 | my $fd = $self->{'raw'}; | ||||
284 | seek($fd, 0, 0) or die "message: cannot rewind file: $!"; | ||||
285 | |||||
286 | # dbg("message: (raw) reading mime part from a temporary file"); | ||||
287 | my($nread,$raw_str); $raw_str = ''; | ||||
288 | while ( $nread=sysread($fd, $raw_str, 16384, length $raw_str) ) { } | ||||
289 | defined $nread or die "error reading: $!"; | ||||
290 | my @array = split(/^/m, $raw_str, -1); | ||||
291 | |||||
292 | dbg("message: empty message read") if $raw_str eq ''; | ||||
293 | return \@array; | ||||
294 | } | ||||
295 | |||||
296 | return $self->{'raw'}; | ||||
297 | } | ||||
298 | |||||
299 | =item decode() | ||||
300 | |||||
301 | If necessary, decode the part text as base64 or quoted-printable. | ||||
302 | The decoded text will be returned as a scalar string. An optional length | ||||
303 | parameter can be passed in which limits how much decoded data is returned. | ||||
304 | If the scalar isn't needed, call with "0" as a parameter. | ||||
305 | |||||
306 | =cut | ||||
307 | |||||
308 | # spent 826ms (74.4+752) within Mail::SpamAssassin::Message::Node::decode which was called 409 times, avg 2.02ms/call:
# 409 times (74.4ms+752ms) by Mail::SpamAssassin::Message::Node::rendered at line 604, avg 2.02ms/call | ||||
309 | 409 | 1.20ms | my($self, $bytes) = @_; | ||
310 | |||||
311 | 409 | 1.75ms | if ( !exists $self->{'decoded'} ) { | ||
312 | # Someone is looking for a decoded part where there is no raw data | ||||
313 | # (multipart or subparsed message, etc.) Just return undef. | ||||
314 | 409 | 920µs | return if !exists $self->{'raw'}; | ||
315 | |||||
316 | 409 | 749µs | my $raw; | ||
317 | |||||
318 | # if the part is held in a temp file, read it into the scalar | ||||
319 | 409 | 2.89ms | if (ref $self->{'raw'} eq 'GLOB') { | ||
320 | my $fd = $self->{'raw'}; | ||||
321 | seek($fd, 0, 0) or die "message: cannot rewind file: $!"; | ||||
322 | |||||
323 | # dbg("message: (decode) reading mime part from a temporary file"); | ||||
324 | my($nread,$raw_str); $raw = ''; | ||||
325 | while ( $nread=sysread($fd, $raw, 16384, length $raw) ) { } | ||||
326 | defined $nread or die "error reading: $!"; | ||||
327 | |||||
328 | dbg("message: empty message read from a temp file") if $raw eq ''; | ||||
329 | } | ||||
330 | else { | ||||
331 | # create a new scalar from the raw array in memory | ||||
332 | 818 | 27.5ms | $raw = join('', @{$self->{'raw'}}); | ||
333 | } | ||||
334 | |||||
335 | 409 | 3.91ms | 409 | 22.3ms | my $encoding = lc $self->header('content-transfer-encoding') || ''; # spent 22.3ms making 409 calls to Mail::SpamAssassin::Message::Node::header, avg 54µs/call |
336 | |||||
337 | 409 | 2.48ms | if ( $encoding eq 'quoted-printable' ) { | ||
338 | 202 | 1.37ms | 202 | 1.37ms | dbg("message: decoding quoted-printable"); # spent 1.37ms making 202 calls to Mail::SpamAssassin::Logger::dbg, avg 7µs/call |
339 | 202 | 2.18ms | 202 | 642ms | $self->{'decoded'} = Mail::SpamAssassin::Util::qp_decode($raw); # spent 642ms making 202 calls to Mail::SpamAssassin::Util::qp_decode, avg 3.18ms/call |
340 | 202 | 12.4ms | 202 | 3.06ms | $self->{'decoded'} =~ s/\015\012/\012/gs; # spent 3.06ms making 202 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 15µs/call |
341 | } | ||||
342 | elsif ( $encoding eq 'base64' ) { | ||||
343 | 55 | 383µs | 55 | 419µs | dbg("message: decoding base64"); # spent 419µs making 55 calls to Mail::SpamAssassin::Logger::dbg, avg 8µs/call |
344 | |||||
345 | # if it's not defined or is 0, do the whole thing, otherwise only decode | ||||
346 | # a portion | ||||
347 | 55 | 221µs | if ($bytes) { | ||
348 | return Mail::SpamAssassin::Util::base64_decode($raw, $bytes); | ||||
349 | } | ||||
350 | else { | ||||
351 | # Generate the decoded output | ||||
352 | 55 | 607µs | 55 | 74.4ms | $self->{'decoded'} = Mail::SpamAssassin::Util::base64_decode($raw); # spent 74.4ms making 55 calls to Mail::SpamAssassin::Util::base64_decode, avg 1.35ms/call |
353 | } | ||||
354 | |||||
355 | 55 | 963µs | 55 | 351µs | if ( $self->{'type'} =~ m@^(?:text|message)\b/@i ) { # spent 351µs making 55 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 6µs/call |
356 | 55 | 6.96ms | 55 | 6.55ms | $self->{'decoded'} =~ s/\015\012/\012/gs; # spent 6.55ms making 55 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 119µs/call |
357 | } | ||||
358 | } | ||||
359 | else { | ||||
360 | # Encoding is one of 7bit, 8bit, binary or x-something | ||||
361 | 152 | 874µs | if ( $encoding ) { | ||
362 | 106 | 1.08ms | 106 | 833µs | dbg("message: decoding other encoding type ($encoding), ignoring"); # spent 833µs making 106 calls to Mail::SpamAssassin::Logger::dbg, avg 8µs/call |
363 | } | ||||
364 | else { | ||||
365 | 46 | 326µs | 46 | 313µs | dbg("message: no encoding detected"); # spent 313µs making 46 calls to Mail::SpamAssassin::Logger::dbg, avg 7µs/call |
366 | } | ||||
367 | 152 | 716µs | $self->{'decoded'} = $raw; | ||
368 | } | ||||
369 | } | ||||
370 | |||||
371 | 409 | 929µs | if ( !defined $bytes || $bytes ) { | ||
372 | 409 | 862µs | if ( !defined $bytes ) { | ||
373 | # force a copy | ||||
374 | 409 | 9.04ms | return '' . $self->{'decoded'}; | ||
375 | } | ||||
376 | else { | ||||
377 | return substr($self->{'decoded'}, 0, $bytes); | ||||
378 | } | ||||
379 | } | ||||
380 | } | ||||
381 | |||||
382 | # Look at a text scalar and determine whether it should be rendered | ||||
383 | # as text/html. | ||||
384 | # | ||||
385 | # This is not a public function. | ||||
386 | # | ||||
387 | # spent 12.6ms (11.4+1.19) within Mail::SpamAssassin::Message::Node::_html_render which was called 217 times, avg 58µs/call:
# 217 times (11.4ms+1.19ms) by Mail::SpamAssassin::Message::Node::rendered at line 609, avg 58µs/call | ||||
388 | 217 | 11.1ms | 217 | 1.19ms | if ($_[0] =~ m/^(.{0,18}?<(?:body|head|html|img|pre|table|title)(?:\s.{0,18}?)?>)/is) # spent 1.19ms making 217 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 5µs/call |
389 | { | ||||
390 | my $pad = $1; | ||||
391 | my $count = 0; | ||||
392 | $count += ($pad =~ tr/\n//d) * 2; | ||||
393 | $count += ($pad =~ tr/\n//cd); | ||||
394 | return ($count < 24); | ||||
395 | } | ||||
396 | 217 | 1.81ms | return 0; | ||
397 | } | ||||
398 | |||||
399 | # Decode character set of a given text to perl characters (Unicode), | ||||
400 | # then encode into UTF-8 octets if requested. | ||||
401 | # | ||||
402 | # spent 1.80ms within Mail::SpamAssassin::Message::Node::_normalize which was called 118 times, avg 15µs/call:
# 118 times (1.80ms+0s) by Mail::SpamAssassin::Message::Node::__decode_header at line 783, avg 15µs/call | ||||
403 | 118 | 220µs | my $self = $_[0]; | ||
404 | # my $data = $_[1]; # avoid copying large strings | ||||
405 | 118 | 309µs | my $charset_declared = $_[2]; | ||
406 | 118 | 208µs | my $return_decoded = $_[3]; # true: Unicode characters, false: UTF-8 octets | ||
407 | |||||
408 | 118 | 1.33ms | return $_[1] unless $self->{normalize} && $enc_utf8; | ||
409 | |||||
410 | warn "message: _normalize() was given characters, expected bytes: $_[1]\n" | ||||
411 | if utf8::is_utf8($_[1]); | ||||
412 | |||||
413 | # workaround for Encode::decode taint laundering bug [rt.cpan.org #84879] | ||||
414 | my $data_taint = substr($_[1], 0, 0); # empty string, tainted like $data | ||||
415 | |||||
416 | if (!defined $charset_declared || $charset_declared eq '') { | ||||
417 | $charset_declared = 'us-ascii'; | ||||
418 | } | ||||
419 | |||||
420 | # number of characters with code above 127 | ||||
421 | my $cnt_8bits = $_[1] =~ tr/\x00-\x7F//c; | ||||
422 | |||||
423 | if (!$cnt_8bits && | ||||
424 | $charset_declared =~ | ||||
425 | /^(?: (?:US-)?ASCII | ANSI[_ ]? X3\.4- (?:1986|1968) | | ||||
426 | ISO646-US )\z/xsi) | ||||
427 | { # declared as US-ASCII (a.k.a. ANSI X3.4-1986) and it really is | ||||
428 | dbg("message: kept, charset is US-ASCII as declared"); | ||||
429 | return $_[1]; # is all-ASCII, no need for decoding | ||||
430 | } | ||||
431 | |||||
432 | if (!$cnt_8bits && | ||||
433 | $charset_declared =~ | ||||
434 | /^(?: ISO[ -]?8859 (?: - \d{1,2} )? | Windows-\d{4} | | ||||
435 | UTF-?8 | (KOI8|EUC)-[A-Z]{1,2} | | ||||
436 | Big5 | GBK | GB[ -]?18030 (?:-20\d\d)? )\z/xsi) | ||||
437 | { # declared as extended ASCII, but it is actually a plain 7-bit US-ASCII | ||||
438 | dbg("message: kept, charset is US-ASCII, declared %s", $charset_declared); | ||||
439 | return $_[1]; # is all-ASCII, no need for decoding | ||||
440 | } | ||||
441 | |||||
442 | # Try first to strictly decode based on a declared character set. | ||||
443 | |||||
444 | my $rv; | ||||
445 | if ($charset_declared =~ /^UTF-?8\z/i) { | ||||
446 | # attempt decoding as strict UTF-8 (flags: FB_CROAK | LEAVE_SRC) | ||||
447 | if (eval { $rv = $enc_utf8->decode($_[1], 1|8); defined $rv }) { | ||||
448 | dbg("message: decoded as declared charset UTF-8"); | ||||
449 | return $_[1] if !$return_decoded; | ||||
450 | $rv .= $data_taint; # carry taintedness over, avoid Encode bug | ||||
451 | return $rv; # decoded | ||||
452 | } else { | ||||
453 | dbg("message: failed decoding as declared charset UTF-8"); | ||||
454 | }; | ||||
455 | |||||
456 | } elsif ($cnt_8bits && | ||||
457 | eval { $rv = $enc_utf8->decode($_[1], 1|8); defined $rv }) { | ||||
458 | dbg("message: decoded as charset UTF-8, declared %s", $charset_declared); | ||||
459 | return $_[1] if !$return_decoded; | ||||
460 | $rv .= $data_taint; # carry taintedness over, avoid Encode bug | ||||
461 | return $rv; # decoded | ||||
462 | |||||
463 | } elsif ($charset_declared =~ /^(?:US-)?ASCII\z/i) { | ||||
464 | # declared as US-ASCII but contains 8-bit characters, makes no sense | ||||
465 | # to attempt decoding first as strict US-ASCII as we know it would fail | ||||
466 | |||||
467 | } else { | ||||
468 | # try decoding as a declared character set | ||||
469 | |||||
470 | # -> http://en.wikipedia.org/wiki/Windows-1252 | ||||
471 | # Windows-1252 character encoding is a superset of ISO 8859-1, but differs | ||||
472 | # from the IANA's ISO-8859-1 by using displayable characters rather than | ||||
473 | # control characters in the 80 to 9F (hex) range. [...] | ||||
474 | # It is very common to mislabel Windows-1252 text with the charset label | ||||
475 | # ISO-8859-1. A common result was that all the quotes and apostrophes | ||||
476 | # (produced by "smart quotes" in word-processing software) were replaced | ||||
477 | # with question marks or boxes on non-Windows operating systems, making | ||||
478 | # text difficult to read. Most modern web browsers and e-mail clients | ||||
479 | # treat the MIME charset ISO-8859-1 as Windows-1252 to accommodate | ||||
480 | # such mislabeling. This is now standard behavior in the draft HTML 5 | ||||
481 | # specification, which requires that documents advertised as ISO-8859-1 | ||||
482 | # actually be parsed with the Windows-1252 encoding. | ||||
483 | # | ||||
484 | my($chset, $decoder); | ||||
485 | if ($charset_declared =~ /^(?: ISO-?8859-1 | Windows-1252 | CP1252 )\z/xi) { | ||||
486 | $chset = 'Windows-1252'; $decoder = $enc_w1252; | ||||
487 | } else { | ||||
488 | $chset = $charset_declared; $decoder = Encode::find_encoding($chset); | ||||
489 | if (!$decoder && $chset =~ /^GB[ -]?18030(?:-20\d\d)?\z/i) { | ||||
490 | $decoder = Encode::find_encoding('GBK'); # a subset of GB18030 | ||||
491 | dbg("message: no decoder for a declared charset %s, using GBK", | ||||
492 | $chset) if $decoder; | ||||
493 | } | ||||
494 | } | ||||
495 | if (!$decoder) { | ||||
496 | dbg("message: failed decoding, no decoder for a declared charset %s", | ||||
497 | $chset); | ||||
498 | } else { | ||||
499 | eval { $rv = $decoder->decode($_[1], 1|8) }; # FB_CROAK | LEAVE_SRC | ||||
500 | if (lc $chset eq lc $charset_declared) { | ||||
501 | dbg("message: %s as declared charset %s", | ||||
502 | defined $rv ? 'decoded' : 'failed decoding', $charset_declared); | ||||
503 | } else { | ||||
504 | dbg("message: %s as charset %s, declared %s", | ||||
505 | defined $rv ? 'decoded' : 'failed decoding', | ||||
506 | $chset, $charset_declared); | ||||
507 | } | ||||
508 | } | ||||
509 | } | ||||
510 | |||||
511 | # If the above failed, check if it is US-ASCII, possibly extended by few | ||||
512 | # NBSP or SHY characters from ISO-8859-* or Windows-1252, or containing | ||||
513 | # some popular punctuation or special characters from Windows-1252 in | ||||
514 | # the \x80-\x9F range (which is unassigned in ISO-8859-*). | ||||
515 | # Note that Windows-1252 is a proper superset of ISO-8859-1. | ||||
516 | # | ||||
517 | if (!defined $rv && !$cnt_8bits) { | ||||
518 | dbg("message: kept, guessed charset is US-ASCII, declared %s", | ||||
519 | $charset_declared); | ||||
520 | return $_[1]; # is all-ASCII, no need for decoding | ||||
521 | |||||
522 | } elsif (!defined $rv && $enc_w1252 && | ||||
523 | # ASCII NBSP (c) SHY ' " ... '".- TM | ||||
524 | $_[1] !~ tr/\x00-\x7F\xA0\xA9\xAD\x82\x84\x85\x91-\x97\x99//c) | ||||
525 | { # ASCII + NBSP + SHY + some punctuation characters | ||||
526 | # NBSP (A0) and SHY (AD) are at the same position in ISO-8859-* too | ||||
527 | # consider also: AE (r), 80 Euro | ||||
528 | eval { $rv = $enc_w1252->decode($_[1], 1|8) }; # FB_CROAK | LEAVE_SRC | ||||
529 | # the above can't fail, but keep code general just in case | ||||
530 | dbg("message: %s as guessed charset %s, declared %s", | ||||
531 | defined $rv ? 'decoded' : 'failed decoding', | ||||
532 | 'Windows-1252', $charset_declared); | ||||
533 | } | ||||
534 | |||||
535 | # If we were unsuccessful so far, try some guesswork | ||||
536 | # based on Encode::Detect::Detector . | ||||
537 | |||||
538 | if (defined $rv) { | ||||
539 | # done, no need for guesswork | ||||
540 | } elsif (!$have_encode_detector) { | ||||
541 | dbg("message: Encode::Detect::Detector not available, declared %s failed", | ||||
542 | $charset_declared); | ||||
543 | } else { | ||||
544 | my $charset_detected = Encode::Detect::Detector::detect($_[1]); | ||||
545 | if ($charset_detected && lc $charset_detected ne lc $charset_declared) { | ||||
546 | my $decoder = Encode::find_encoding($charset_detected); | ||||
547 | if (!$decoder && $charset_detected =~ /^GB[ -]?18030(?:-20\d\d)?\z/i) { | ||||
548 | $decoder = Encode::find_encoding('GBK'); # a subset of GB18030 | ||||
549 | dbg("message: no decoder for a detected charset %s, using GBK", | ||||
550 | $charset_detected) if $decoder; | ||||
551 | } | ||||
552 | if (!$decoder) { | ||||
553 | dbg("message: failed decoding, no decoder for a detected charset %s", | ||||
554 | $charset_detected); | ||||
555 | } else { | ||||
556 | eval { $rv = $decoder->decode($_[1], 1|8) }; # FB_CROAK | LEAVE_SRC | ||||
557 | dbg("message: %s as detected charset %s, declared %s", | ||||
558 | defined $rv ? 'decoded' : 'failed decoding', | ||||
559 | $charset_detected, $charset_declared); | ||||
560 | } | ||||
561 | } | ||||
562 | } | ||||
563 | |||||
564 | if (!defined $rv) { # all decoding attempts failed so far, probably garbage | ||||
565 | # go for Windows-1252 which can't fail | ||||
566 | eval { $rv = $enc_w1252->decode($_[1]) }; | ||||
567 | dbg("message: %s as last-resort charset %s, declared %s", | ||||
568 | defined $rv ? 'decoded' : 'failed decoding', | ||||
569 | 'Windows-1252', $charset_declared); | ||||
570 | } | ||||
571 | |||||
572 | if (!defined $rv) { # just in case - all decoding attempts failed so far | ||||
573 | return $_[1]; # garbage-in / garbage-out, return unchanged octets | ||||
574 | } | ||||
575 | # decoding octets to characters was successful | ||||
576 | if (!$return_decoded) { | ||||
577 | # utf8::encode() is much faster than $enc_utf8->encode on utf8-flagged arg | ||||
578 | utf8::encode($rv); # encode Unicode characters to UTF-8 octets | ||||
579 | } | ||||
580 | $rv .= $data_taint; # carry taintedness over, avoid Encode bug | ||||
581 | return $rv; | ||||
582 | } | ||||
583 | |||||
584 | =item rendered() | ||||
585 | |||||
586 | render_text() takes the given text/* type MIME part, and attempts to | ||||
587 | render it into a text scalar. It will always render text/html, and will | ||||
588 | use a heuristic to determine if other text/* parts should be considered | ||||
589 | text/html. Two scalars are returned: the rendered type (either text/html | ||||
590 | or whatever the original type was), and the rendered text. | ||||
591 | |||||
592 | =cut | ||||
593 | |||||
594 | # spent 23.0s (71.0ms+23.0) within Mail::SpamAssassin::Message::Node::rendered which was called 1275 times, avg 18.1ms/call:
# 425 times (61.4ms+23.0s) by Mail::SpamAssassin::Message::get_body_text_array_common at line 1128 of Mail/SpamAssassin/Message.pm, avg 54.2ms/call
# 425 times (6.11ms+94µs) by Mail::SpamAssassin::Message::Node::visible_rendered at line 697, avg 15µs/call
# 425 times (3.52ms+103µs) by Mail::SpamAssassin::Message::Node::invisible_rendered at line 709, avg 9µs/call | ||||
595 | 1275 | 2.42ms | my ($self) = @_; | ||
596 | |||||
597 | 1275 | 5.55ms | if (!exists $self->{rendered}) { | ||
598 | # We only know how to render text/plain and text/html ... | ||||
599 | # Note: for bug 4843, make sure to skip text/calendar parts | ||||
600 | # we also want to skip things like text/x-vcard | ||||
601 | # text/x-aol is ignored here, but looks like text/html ... | ||||
602 | 457 | 7.51ms | 457 | 3.75ms | return(undef,undef) unless ( $self->{'type'} =~ /^text\/(?:plain|html)$/i ); # spent 3.75ms making 457 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 8µs/call |
603 | |||||
604 | 409 | 3.93ms | 409 | 826ms | my $text = $self->decode; # QP and Base64 decoding, bytes # spent 826ms making 409 calls to Mail::SpamAssassin::Message::Node::decode, avg 2.02ms/call |
605 | 409 | 1.33ms | my $text_len = length($text); # num of bytes in original charset encoding | ||
606 | |||||
607 | # render text/html always, or any other text|text/plain part as text/html | ||||
608 | # based on a heuristic which simulates a certain common mail client | ||||
609 | 409 | 11.4ms | 840 | 16.0ms | if ($text ne '' && ($self->{'type'} =~ m{^text/html$}i || # spent 12.6ms making 217 calls to Mail::SpamAssassin::Message::Node::_html_render, avg 58µs/call
# spent 3.39ms making 623 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 5µs/call |
610 | ($self->{'type'} =~ m{^text/plain$}i && | ||||
611 | _html_render(substr($text, 0, 23))))) | ||||
612 | { | ||||
613 | 189 | 645µs | $self->{rendered_type} = 'text/html'; | ||
614 | |||||
615 | # will input text to HTML::Parser be provided as Unicode characters? | ||||
616 | 189 | 417µs | my $character_semantics = 0; # $text is in bytes | ||
617 | 189 | 3.68ms | 188 | 1.12ms | if ($self->{normalize} && $enc_utf8) { # charset decoding requested # spent 1.12ms making 188 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 6µs/call |
618 | # Provide input to HTML::Parser as Unicode characters | ||||
619 | # which avoids a HTML::Parser bug in utf8_mode | ||||
620 | # https://rt.cpan.org/Public/Bug/Display.html?id=99755 | ||||
621 | # Avoid unnecessary step of encoding-then-decoding by telling | ||||
622 | # subroutine _normalize() to return Unicode text. See Bug 7133 | ||||
623 | # | ||||
624 | $character_semantics = 1; # $text will be in characters | ||||
625 | $text = $self->_normalize($text, $self->{charset}, 1); # bytes to chars | ||||
626 | } elsif (!defined $self->{charset} || | ||||
627 | $self->{charset} =~ /^(?:US-ASCII|UTF-8)\z/i) { | ||||
628 | # With some luck input can be interpreted as UTF-8, do not warn. | ||||
629 | # It is still possible to hit the HTML::Parses utf8_mode bug however. | ||||
630 | } else { | ||||
631 | dbg("message: 'normalize_charset' is off, encoding will likely ". | ||||
632 | 46 | 399µs | 46 | 352µs | "be misinterpreted; declared charset: %s", $self->{charset}); # spent 352µs making 46 calls to Mail::SpamAssassin::Logger::dbg, avg 8µs/call |
633 | } | ||||
634 | # the 0 requires decoded HTML results to be in bytes (not characters) | ||||
635 | 189 | 2.31ms | 189 | 65.6ms | my $html = Mail::SpamAssassin::HTML->new($character_semantics,0); # object # spent 65.6ms making 189 calls to Mail::SpamAssassin::HTML::new, avg 347µs/call |
636 | |||||
637 | 189 | 1.78ms | 189 | 21.7s | $html->parse($text); # parse+render text # spent 21.7s making 189 calls to Mail::SpamAssassin::HTML::parse, avg 115ms/call |
638 | |||||
639 | # resulting HTML-decoded text is in bytes, likely encoded as UTF-8 | ||||
640 | 189 | 2.22ms | 189 | 10.5ms | $self->{rendered} = $html->get_rendered_text(); # spent 10.5ms making 189 calls to Mail::SpamAssassin::HTML::get_rendered_text, avg 56µs/call |
641 | 189 | 1.68ms | 189 | 203ms | $self->{visible_rendered} = $html->get_rendered_text(invisible => 0); # spent 203ms making 189 calls to Mail::SpamAssassin::HTML::get_rendered_text, avg 1.07ms/call |
642 | 189 | 1.61ms | 189 | 163ms | $self->{invisible_rendered} = $html->get_rendered_text(invisible => 1); # spent 163ms making 189 calls to Mail::SpamAssassin::HTML::get_rendered_text, avg 865µs/call |
643 | 189 | 1.79ms | 189 | 1.53ms | $self->{html_results} = $html->get_results(); # spent 1.53ms making 189 calls to Mail::SpamAssassin::HTML::get_results, avg 8µs/call |
644 | |||||
645 | # end-of-document result values that require looking at the text | ||||
646 | 189 | 490µs | my $r = $self->{html_results}; # temporary reference for brevity | ||
647 | |||||
648 | # count the number of spaces in the rendered text (likely UTF-8 octets) | ||||
649 | 189 | 1.48ms | my $space = $self->{rendered} =~ tr/ \t\n\r\x0b//; | ||
650 | # we may want to add the count of other Unicode whitespace characters | ||||
651 | |||||
652 | 189 | 848µs | $r->{html_length} = length $self->{rendered}; # bytes (likely UTF-8) | ||
653 | 189 | 800µs | $r->{non_space_len} = $r->{html_length} - $space; | ||
654 | 189 | 10.7ms | $r->{ratio} = ($text_len - $r->{html_length}) / $text_len if $text_len; | ||
655 | } | ||||
656 | |||||
657 | else { # plain text | ||||
658 | 220 | 547µs | if ($self->{normalize} && $enc_utf8) { | ||
659 | # request transcoded result as UTF-8 octets! | ||||
660 | $text = $self->_normalize($text, $self->{charset}, 0); | ||||
661 | } | ||||
662 | 220 | 884µs | $self->{rendered_type} = $self->{type}; | ||
663 | 220 | 1.25ms | $self->{rendered} = $self->{'visible_rendered'} = $text; | ||
664 | 220 | 788µs | $self->{'invisible_rendered'} = ''; | ||
665 | } | ||||
666 | } | ||||
667 | |||||
668 | 1227 | 13.5ms | return ($self->{rendered_type}, $self->{rendered}); | ||
669 | } | ||||
670 | |||||
671 | =item set_rendered($text, $type) | ||||
672 | |||||
673 | Set the rendered text and type for the given part. If type is not | ||||
674 | specified, and text is a defined value, a default of 'text/plain' is used. | ||||
675 | This can be used, for instance, to render non-text parts using plugins. | ||||
676 | |||||
677 | =cut | ||||
678 | |||||
679 | sub set_rendered { | ||||
680 | my ($self, $text, $type) = @_; | ||||
681 | |||||
682 | $type = 'text/plain' if (!defined $type && defined $text); | ||||
683 | |||||
684 | $self->{'rendered_type'} = $type; | ||||
685 | $self->{'rendered'} = $self->{'visible_rendered'} = $text; | ||||
686 | $self->{'invisible_rendered'} = defined $text ? '' : undef; | ||||
687 | } | ||||
688 | |||||
689 | =item visible_rendered() | ||||
690 | |||||
691 | Render and return the visible text in this part. | ||||
692 | |||||
693 | =cut | ||||
694 | |||||
695 | # spent 15.2ms (9.02+6.20) within Mail::SpamAssassin::Message::Node::visible_rendered which was called 425 times, avg 36µs/call:
# 425 times (9.02ms+6.20ms) by Mail::SpamAssassin::Message::get_body_text_array_common at line 1128 of Mail/SpamAssassin/Message.pm, avg 36µs/call | ||||
696 | 425 | 866µs | my ($self) = @_; | ||
697 | 425 | 3.27ms | 425 | 6.20ms | $self->rendered(); # ignore return, we want just this: # spent 6.20ms making 425 calls to Mail::SpamAssassin::Message::Node::rendered, avg 15µs/call |
698 | 425 | 13.9ms | return ($self->{rendered_type}, $self->{visible_rendered}); | ||
699 | } | ||||
700 | |||||
701 | =item invisible_rendered() | ||||
702 | |||||
703 | Render and return the invisible text in this part. | ||||
704 | |||||
705 | =cut | ||||
706 | |||||
707 | # spent 14.7ms (11.0+3.62) within Mail::SpamAssassin::Message::Node::invisible_rendered which was called 425 times, avg 35µs/call:
# 425 times (11.0ms+3.62ms) by Mail::SpamAssassin::Message::get_body_text_array_common at line 1128 of Mail/SpamAssassin/Message.pm, avg 35µs/call | ||||
708 | 425 | 824µs | my ($self) = @_; | ||
709 | 425 | 2.62ms | 425 | 3.62ms | $self->rendered(); # ignore return, we want just this: # spent 3.62ms making 425 calls to Mail::SpamAssassin::Message::Node::rendered, avg 9µs/call |
710 | 425 | 4.62ms | return ($self->{rendered_type}, $self->{invisible_rendered}); | ||
711 | } | ||||
712 | |||||
713 | =item content_summary() | ||||
714 | |||||
715 | Returns an array of scalars describing the mime parts of the message. | ||||
716 | Note: This function requires that the message be parsed first! | ||||
717 | |||||
718 | =cut | ||||
719 | |||||
720 | # return an array with scalars describing mime parts | ||||
721 | sub content_summary { | ||||
722 | my($self) = @_; | ||||
723 | |||||
724 | my @ret = ( [ $self->{'type'} ] ); | ||||
725 | my @search; | ||||
726 | |||||
727 | if (exists $self->{'body_parts'}) { | ||||
728 | my $count = @{$self->{'body_parts'}}; | ||||
729 | for(my $i=0; $i<$count; $i++) { | ||||
730 | push(@search, [ $i+1, $self->{'body_parts'}->[$i] ]); | ||||
731 | } | ||||
732 | } | ||||
733 | |||||
734 | while(my $part = shift @search) { | ||||
735 | my($index, $part) = @{$part}; | ||||
736 | push(@{$ret[$index]}, $part->{'type'}); | ||||
737 | if (exists $part->{'body_parts'}) { | ||||
738 | unshift(@search, map { [ $index, $_ ] } @{$part->{'body_parts'}}); | ||||
739 | } | ||||
740 | } | ||||
741 | |||||
742 | return map { join(",", @{$_}) } @ret; | ||||
743 | } | ||||
744 | |||||
745 | =item delete_header() | ||||
746 | |||||
747 | Delete the specified header (decoded and raw) from the Node information. | ||||
748 | |||||
749 | =cut | ||||
750 | |||||
751 | # spent 1.25s (945ms+305ms) within Mail::SpamAssassin::Message::Node::delete_header which was called 936 times, avg 1.33ms/call:
# 234 times (247ms+88.9ms) by Mail::SpamAssassin::Message::Metadata::parse_received_headers at line 273 of Mail/SpamAssassin/Message/Metadata/Received.pm, avg 1.43ms/call
# 234 times (257ms+71.5ms) by Mail::SpamAssassin::Message::Metadata::parse_received_headers at line 276 of Mail/SpamAssassin/Message/Metadata/Received.pm, avg 1.40ms/call
# 234 times (232ms+72.7ms) by Mail::SpamAssassin::Message::Metadata::parse_received_headers at line 275 of Mail/SpamAssassin/Message/Metadata/Received.pm, avg 1.30ms/call
# 234 times (209ms+71.6ms) by Mail::SpamAssassin::Message::Metadata::parse_received_headers at line 274 of Mail/SpamAssassin/Message/Metadata/Received.pm, avg 1.20ms/call | ||||
752 | 936 | 2.34ms | my($self, $hdr) = @_; | ||
753 | |||||
754 | 1872 | 621ms | 51216 | 134ms | foreach ( grep(/^${hdr}$/i, keys %{$self->{'headers'}}) ) { # spent 89.2ms making 25608 calls to Mail::SpamAssassin::Message::Node::CORE:regcomp, avg 3µs/call
# spent 45.2ms making 25608 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 2µs/call |
755 | delete $self->{'headers'}->{$_}; | ||||
756 | delete $self->{'raw_headers'}->{$_}; | ||||
757 | } | ||||
758 | |||||
759 | 1872 | 620ms | 59280 | 170ms | my @neworder = grep(!/^${hdr}$/i, @{$self->{'header_order'}}); # spent 94.8ms making 29640 calls to Mail::SpamAssassin::Message::Node::CORE:regcomp, avg 3µs/call
# spent 75.5ms making 29640 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 3µs/call |
760 | 936 | 22.0ms | $self->{'header_order'} = \@neworder; | ||
761 | } | ||||
762 | |||||
763 | # decode a header appropriately. don't bother adding it to the pod documents. | ||||
764 | # spent 15.1ms (5.64+9.42) within Mail::SpamAssassin::Message::Node::__decode_header which was called 118 times, avg 128µs/call:
# 118 times (5.64ms+9.42ms) by Mail::SpamAssassin::Message::Node::_decode_header at line 819, avg 128µs/call | ||||
765 | 118 | 1.20ms | my ( $self, $encoding, $cte, $data ) = @_; | ||
766 | |||||
767 | 118 | 562µs | if ( $cte eq 'B' ) { | ||
768 | # base 64 encoded | ||||
769 | 15 | 154µs | 15 | 1.25ms | $data = Mail::SpamAssassin::Util::base64_decode($data); # spent 1.25ms making 15 calls to Mail::SpamAssassin::Util::base64_decode, avg 83µs/call |
770 | } | ||||
771 | elsif ( $cte eq 'Q' ) { | ||||
772 | # quoted printable | ||||
773 | |||||
774 | # the RFC states that in the encoded text, "_" is equal to "=20" | ||||
775 | 103 | 1.13ms | 103 | 435µs | $data =~ s/_/=20/g; # spent 435µs making 103 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 4µs/call |
776 | |||||
777 | 103 | 749µs | 103 | 5.95ms | $data = Mail::SpamAssassin::Util::qp_decode($data); # spent 5.95ms making 103 calls to Mail::SpamAssassin::Util::qp_decode, avg 58µs/call |
778 | } | ||||
779 | else { | ||||
780 | # not possible since the input has already been limited to 'B' and 'Q' | ||||
781 | die "message: unknown encoding type '$cte' in RFC2047 header"; | ||||
782 | } | ||||
783 | 118 | 1.71ms | 118 | 1.80ms | return $self->_normalize($data, $encoding, 0); # transcode to UTF-8 octets # spent 1.80ms making 118 calls to Mail::SpamAssassin::Message::Node::_normalize, avg 15µs/call |
784 | } | ||||
785 | |||||
786 | # Decode base64 and quoted-printable in headers according to RFC2047. | ||||
787 | # | ||||
788 | # spent 701ms (559+142) within Mail::SpamAssassin::Message::Node::_decode_header which was called 8191 times, avg 86µs/call:
# 8191 times (559ms+142ms) by Mail::SpamAssassin::Message::Node::header at line 189, avg 86µs/call | ||||
789 | 8191 | 78.5ms | my($self, $header_field_body, $header_field_name) = @_; | ||
790 | |||||
791 | 8191 | 19.7ms | return '' unless defined $header_field_body && $header_field_body ne ''; | ||
792 | |||||
793 | # deal with folding and cream the newlines and such | ||||
794 | 7950 | 101ms | 7950 | 23.8ms | $header_field_body =~ s/\n[ \t]+/\n /g; # spent 23.8ms making 7950 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 3µs/call |
795 | 7950 | 84.8ms | 7950 | 17.4ms | $header_field_body =~ s/\015?\012//gs; # spent 17.4ms making 7950 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 2µs/call |
796 | |||||
797 | 7950 | 152ms | 7950 | 52.2ms | if ($header_field_name =~ # spent 52.2ms making 7950 calls to Mail::SpamAssassin::Message::Node::CORE:match, avg 7µs/call |
798 | /^ (?: (?: Received | (?:Resent-)? (?: Message-ID | Date ) | | ||||
799 | MIME-Version | References | In-Reply-To ) \z | ||||
800 | | (?: List- | Content- ) ) /xsi ) { | ||||
801 | # Bug 6945: some header fields must not be processed for MIME encoding | ||||
802 | |||||
803 | } else { | ||||
804 | 4903 | 33.6ms | local($1,$2,$3); | ||
805 | |||||
806 | # Multiple encoded sections must ignore the interim whitespace. | ||||
807 | # To avoid possible FPs with (\s+(?==\?))?, look for the whole RE | ||||
808 | # separated by whitespace. | ||||
809 | 4903 | 79.7ms | 5010 | 13.7ms | 1 while $header_field_body =~ # spent 13.0ms making 4917 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 3µs/call
# spent 719µs making 93 calls to Mail::SpamAssassin::Message::Node::CORE:substcont, avg 8µs/call |
810 | s{ ( = \? [A-Za-z0-9_-]+ \? [bqBQ] \? [^?]* \? = ) \s+ | ||||
811 | {$1$2}xsg; | ||||
812 | |||||
813 | |||||
814 | # transcode properly encoded RFC 2047 substrings into UTF-8 octets, | ||||
815 | # leave everything else unchanged as it is supposed to be UTF-8 (RFC 6532) | ||||
816 | # or plain US-ASCII | ||||
817 | 4903 | 69.5ms | 5060 | 19.5ms | $header_field_body =~ # spent 18.3ms making 4903 calls to Mail::SpamAssassin::Message::Node::CORE:subst, avg 4µs/call
# spent 1.28ms making 157 calls to Mail::SpamAssassin::Message::Node::CORE:substcont, avg 8µs/call |
818 | s{ (?: = \? ([A-Za-z0-9_-]+) \? ([bqBQ]) \? ([^?]*) \? = ) } | ||||
819 | 118 | 1.60ms | 118 | 15.1ms | { $self->__decode_header($1, uc($2), $3) }xsge; # spent 15.1ms making 118 calls to Mail::SpamAssassin::Message::Node::__decode_header, avg 128µs/call |
820 | } | ||||
821 | |||||
822 | # dbg("message: _decode_header %s: %s", $header_field_name, $header_field_body); | ||||
823 | 7950 | 108ms | return $header_field_body; | ||
824 | } | ||||
825 | |||||
826 | =item get_header() | ||||
827 | |||||
828 | Retrieve a specific header. Will have a newline at the end and will be | ||||
829 | unfolded. The first parameter is the header name (case-insensitive), | ||||
830 | and the second parameter (optional) is whether or not to return the | ||||
831 | raw header. | ||||
832 | |||||
833 | If get_header() is called in an array context, an array will be returned | ||||
834 | with each header entry in a different element. In a scalar context, | ||||
835 | the last specific header is returned. | ||||
836 | |||||
837 | ie: If 'Subject' is specified as the header, and there are 2 Subject | ||||
838 | headers in a message, the last/bottom one in the message is returned in | ||||
839 | scalar context or both are returned in array context. | ||||
840 | |||||
841 | Btw, returning the last header field (not the first) happens to be consistent | ||||
842 | with DKIM signatures, which search for and cover multiple header fields | ||||
843 | bottom-up according to the 'h' tag. Let's keep it this way. | ||||
844 | |||||
845 | =cut | ||||
846 | |||||
847 | # spent 2.85s (1.24+1.60) within Mail::SpamAssassin::Message::Node::get_header which was called 25966 times, avg 110µs/call:
# 21560 times (1.05s+1.37s) by Mail::SpamAssassin::Message::Node::get_all_headers at line 914, avg 113µs/call
# 1190 times (38.3ms+54.1ms) by Mail::SpamAssassin::PerMsgStatus::_get at line 1982 of Mail/SpamAssassin/PerMsgStatus.pm, avg 78µs/call
# 1170 times (38.4ms+58.9ms) by Mail::SpamAssassin::Message::Metadata::parse_received_headers at line 128 of Mail/SpamAssassin/Message/Metadata/Received.pm, avg 83µs/call
# 555 times (37.1ms+30.0ms) by Mail::SpamAssassin::Plugin::Bayes::get_msgid at line 989 of Mail/SpamAssassin/Plugin/Bayes.pm, avg 121µs/call
# 555 times (26.5ms+31.6ms) by Mail::SpamAssassin::Plugin::Bayes::get_msgid at line 976 of Mail/SpamAssassin/Plugin/Bayes.pm, avg 105µs/call
# 468 times (23.3ms+26.9ms) by Mail::SpamAssassin::Message::get_body_text_array_common at line 1118 of Mail/SpamAssassin/Message.pm, avg 107µs/call
# 234 times (17.9ms+16.5ms) by Mail::SpamAssassin::Message::Metadata::parse_received_headers at line 121 of Mail/SpamAssassin/Message/Metadata/Received.pm, avg 147µs/call
# 234 times (7.34ms+11.3ms) by main::wanted at line 568 of /usr/local/bin/sa-learn, avg 80µs/call | ||||
848 | 25966 | 64.4ms | my ($self, $hdr, $raw) = @_; | ||
849 | 25966 | 45.8ms | $raw ||= 0; | ||
850 | |||||
851 | # And now pick up all the entries into a list | ||||
852 | # This is assumed to include a newline at the end ... | ||||
853 | # This is also assumed to have removed continuation bits ... | ||||
854 | |||||
855 | # Deal with the possibility that header() or raw_header() returns undef | ||||
856 | 25966 | 44.3ms | my @hdrs; | ||
857 | 25966 | 99.9ms | if ( $raw ) { | ||
858 | if (@hdrs = $self->raw_header($hdr)) { | ||||
859 | s/\015?\012\s+/ /gs for @hdrs; | ||||
860 | } | ||||
861 | } | ||||
862 | else { | ||||
863 | 25966 | 249ms | 25966 | 1.60s | if (@hdrs = $self->header($hdr)) { # spent 1.60s making 25966 calls to Mail::SpamAssassin::Message::Node::header, avg 62µs/call |
864 | 23603 | 199ms | $_ .= "\n" for @hdrs; | ||
865 | } | ||||
866 | } | ||||
867 | |||||
868 | 25966 | 48.9ms | if (wantarray) { | ||
869 | 22984 | 361ms | return @hdrs; | ||
870 | } | ||||
871 | else { | ||||
872 | 2982 | 48.1ms | return @hdrs ? $hdrs[-1] : undef; | ||
873 | } | ||||
874 | } | ||||
875 | |||||
876 | =item get_all_headers() | ||||
877 | |||||
878 | Retrieve all headers. Each header will have a newline at the end and | ||||
879 | will be unfolded. The first parameter (optional) is whether or not to | ||||
880 | return the raw headers, and the second parameter (optional) is whether | ||||
881 | or not to include the mbox separator. | ||||
882 | |||||
883 | If get_all_header() is called in an array context, an array will be | ||||
884 | returned with each header entry in a different element. In a scalar | ||||
885 | context, the headers are returned in a single scalar. | ||||
886 | |||||
887 | =back | ||||
888 | |||||
889 | =cut | ||||
890 | |||||
891 | # build it and it will not bomb | ||||
892 | # spent 3.90s (1.29+2.60) within Mail::SpamAssassin::Message::Node::get_all_headers which was called 789 times, avg 4.94ms/call:
# 555 times (912ms+1.89s) by Mail::SpamAssassin::Message::receive_date at line 699 of Mail/SpamAssassin/Message.pm, avg 5.04ms/call
# 234 times (383ms+715ms) by Mail::SpamAssassin::Plugin::Bayes::_tokenize_headers at line 1293 of Mail/SpamAssassin/Plugin/Bayes.pm, avg 4.69ms/call | ||||
893 | 789 | 1.84ms | my ($self, $raw, $include_mbox) = @_; | ||
894 | 789 | 1.93ms | $raw ||= 0; | ||
895 | 789 | 2.48ms | $include_mbox ||= 0; | ||
896 | |||||
897 | 789 | 1.47ms | my @lines; | ||
898 | |||||
899 | # precalculate destination positions based on order of appearance | ||||
900 | 789 | 1.89ms | my $i = 0; | ||
901 | 789 | 1.58ms | my %locations; | ||
902 | 1578 | 8.68ms | for my $k (@{$self->{header_order}}) { | ||
903 | 49948 | 332ms | push(@{$locations{lc($k)}}, $i++); | ||
904 | } | ||||
905 | |||||
906 | # process headers in order of first appearance | ||||
907 | 789 | 1.86ms | my $header; | ||
908 | 789 | 2.12ms | my $size = 0; | ||
909 | 80741 | 195ms | 789 | 175ms | HEADER: for my $name (sort { $locations{$a}->[0] <=> $locations{$b}->[0] } # spent 175ms making 789 calls to Mail::SpamAssassin::Message::Node::CORE:sort, avg 221µs/call |
910 | keys %locations) | ||||
911 | { | ||||
912 | # get all same-name headers and poke into correct position | ||||
913 | 21560 | 41.0ms | my $positions = $locations{$name}; | ||
914 | 21560 | 237ms | 21560 | 2.43s | for my $contents ($self->get_header($name, $raw)) { # spent 2.43s making 21560 calls to Mail::SpamAssassin::Message::Node::get_header, avg 113µs/call |
915 | 49948 | 132ms | my $position = shift @{$positions}; | ||
916 | 24974 | 81.2ms | $size += length($name) + length($contents) + 2; | ||
917 | 24974 | 43.0ms | if ($size > MAX_HEADER_LENGTH) { | ||
918 | $self->{'truncated_header'} = 1; | ||||
919 | last HEADER; | ||||
920 | } | ||||
921 | 24974 | 258ms | $lines[$position] = $self->{header_order}->[$position].":".$contents; | ||
922 | } | ||||
923 | } | ||||
924 | |||||
925 | # skip undefined lines if we truncated | ||||
926 | 789 | 2.50ms | @lines = grep { defined $_ } @lines if $self->{'truncated_header'}; | ||
927 | |||||
928 | 789 | 2.23ms | splice @lines, 0, 0, $self->{mbox_sep} if ( $include_mbox && exists $self->{mbox_sep} ); | ||
929 | |||||
930 | 789 | 36.9ms | return wantarray ? @lines : join ('', @lines); | ||
931 | } | ||||
932 | |||||
933 | # legacy public API; now a no-op. | ||||
934 | sub finish { } | ||||
935 | |||||
936 | # --------------------------------------------------------------------------- | ||||
937 | |||||
938 | 1 | 18µs | 1; | ||
939 | __END__ | ||||
# spent 190ms within Mail::SpamAssassin::Message::Node::CORE:match which was called 66604 times, avg 3µs/call:
# 29640 times (75.5ms+0s) by Mail::SpamAssassin::Message::Node::delete_header at line 759, avg 3µs/call
# 25608 times (45.2ms+0s) by Mail::SpamAssassin::Message::Node::delete_header at line 754, avg 2µs/call
# 7950 times (52.2ms+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 797, avg 7µs/call
# 1866 times (7.26ms+0s) by Mail::SpamAssassin::Message::Node::find_parts at line 128, avg 4µs/call
# 623 times (3.39ms+0s) by Mail::SpamAssassin::Message::Node::rendered at line 609, avg 5µs/call
# 457 times (3.75ms+0s) by Mail::SpamAssassin::Message::Node::rendered at line 602, avg 8µs/call
# 217 times (1.19ms+0s) by Mail::SpamAssassin::Message::Node::_html_render at line 388, avg 5µs/call
# 188 times (1.12ms+0s) by Mail::SpamAssassin::Message::Node::rendered at line 617, avg 6µs/call
# 55 times (351µs+0s) by Mail::SpamAssassin::Message::Node::decode at line 355, avg 6µs/call | |||||
# spent 196ms within Mail::SpamAssassin::Message::Node::CORE:regcomp which was called 57114 times, avg 3µs/call:
# 29640 times (94.8ms+0s) by Mail::SpamAssassin::Message::Node::delete_header at line 759, avg 3µs/call
# 25608 times (89.2ms+0s) by Mail::SpamAssassin::Message::Node::delete_header at line 754, avg 3µs/call
# 1866 times (11.7ms+0s) by Mail::SpamAssassin::Message::Node::find_parts at line 128, avg 6µs/call | |||||
# spent 175ms within Mail::SpamAssassin::Message::Node::CORE:sort which was called 789 times, avg 221µs/call:
# 789 times (175ms+0s) by Mail::SpamAssassin::Message::Node::get_all_headers at line 909, avg 221µs/call | |||||
# spent 510ms within Mail::SpamAssassin::Message::Node::CORE:subst which was called 124601 times, avg 4µs/call:
# 36038 times (136ms+0s) by Mail::SpamAssassin::Message::Node::header at line 172, avg 4µs/call
# 36038 times (86.6ms+0s) by Mail::SpamAssassin::Message::Node::header at line 173, avg 2µs/call
# 8191 times (84.6ms+0s) by Mail::SpamAssassin::Message::Node::header at line 187, avg 10µs/call
# 8191 times (61.1ms+0s) by Mail::SpamAssassin::Message::Node::header at line 188, avg 7µs/call
# 8191 times (54.0ms+0s) by Mail::SpamAssassin::Message::Node::header at line 186, avg 7µs/call
# 7950 times (23.8ms+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 794, avg 3µs/call
# 7950 times (17.4ms+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 795, avg 2µs/call
# 4917 times (13.0ms+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 809, avg 3µs/call
# 4903 times (18.3ms+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 817, avg 4µs/call
# 936 times (2.71ms+0s) by Mail::SpamAssassin::Message::Node::raw_header at line 228, avg 3µs/call
# 936 times (2.31ms+0s) by Mail::SpamAssassin::Message::Node::raw_header at line 229, avg 2µs/call
# 202 times (3.06ms+0s) by Mail::SpamAssassin::Message::Node::decode at line 340, avg 15µs/call
# 103 times (435µs+0s) by Mail::SpamAssassin::Message::Node::__decode_header at line 775, avg 4µs/call
# 55 times (6.55ms+0s) by Mail::SpamAssassin::Message::Node::decode at line 356, avg 119µs/call | |||||
# spent 1.99ms within Mail::SpamAssassin::Message::Node::CORE:substcont which was called 250 times, avg 8µs/call:
# 157 times (1.28ms+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 817, avg 8µs/call
# 93 times (719µs+0s) by Mail::SpamAssassin::Message::Node::_decode_header at line 809, avg 8µs/call |