| Filename | /usr/share/perl5/HTTP/Message.pm |
| Statements | Executed 17 statements in 10.5ms |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 17µs | 37µs | HTTP::Message::BEGIN@3 |
| 1 | 1 | 1 | 15µs | 73µs | HTTP::Message::BEGIN@4 |
| 1 | 1 | 1 | 13µs | 32µs | HTTP::Message::BEGIN@643 |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::AUTOLOAD |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::DESTROY |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::__ANON__[:25] |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::__ANON__[:28] |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::__ANON__[:644] |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::_boundary |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::_content |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::_elem |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::_parts |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::_set_content |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::_stale_content |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::add_content |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::add_content_utf8 |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::add_part |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::as_string |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::clear |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::clone |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::content |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::content_charset |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::content_ref |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::decodable |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::decode |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::decoded_content |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::dump |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::encode |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::headers |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::headers_as_string |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::new |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::parse |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::parts |
| 0 | 0 | 0 | 0s | 0s | HTTP::Message::protocol |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package HTTP::Message; | ||||
| 2 | |||||
| 3 | 2 | 47µs | 2 | 57µs | # spent 37µs (17+20) within HTTP::Message::BEGIN@3 which was called:
# once (17µs+20µs) by LWP::UserAgent::BEGIN@10 at line 3 # spent 37µs making 1 call to HTTP::Message::BEGIN@3
# spent 20µs making 1 call to strict::import |
| 4 | 2 | 7.88ms | 2 | 130µs | # spent 73µs (15+58) within HTTP::Message::BEGIN@4 which was called:
# once (15µs+58µs) by LWP::UserAgent::BEGIN@10 at line 4 # spent 73µs making 1 call to HTTP::Message::BEGIN@4
# spent 58µs making 1 call to vars::import |
| 5 | 1 | 400ns | $VERSION = "6.06"; | ||
| 6 | |||||
| 7 | 1 | 1.27ms | require HTTP::Headers; | ||
| 8 | 1 | 500ns | require Carp; | ||
| 9 | |||||
| 10 | 1 | 300ns | my $CRLF = "\015\012"; # "\r\n" is not portable | ||
| 11 | 1 | 300ns | unless ($HTTP::URI_CLASS) { | ||
| 12 | 1 | 800ns | if ($ENV{PERL_HTTP_URI_CLASS} | ||
| 13 | && $ENV{PERL_HTTP_URI_CLASS} =~ /^([\w:]+)$/) { | ||||
| 14 | $HTTP::URI_CLASS = $1; | ||||
| 15 | } else { | ||||
| 16 | 1 | 200ns | $HTTP::URI_CLASS = "URI"; | ||
| 17 | } | ||||
| 18 | } | ||||
| 19 | 2 | 33µs | eval "require $HTTP::URI_CLASS"; die $@ if $@; # spent 632µs executing statements in string eval | ||
| 20 | |||||
| 21 | *_utf8_downgrade = defined(&utf8::downgrade) ? | ||||
| 22 | sub { | ||||
| 23 | utf8::downgrade($_[0], 1) or | ||||
| 24 | Carp::croak("HTTP::Message content must be bytes") | ||||
| 25 | } | ||||
| 26 | : | ||||
| 27 | sub { | ||||
| 28 | 1 | 4µs | }; | ||
| 29 | |||||
| 30 | sub new | ||||
| 31 | { | ||||
| 32 | my($class, $header, $content) = @_; | ||||
| 33 | if (defined $header) { | ||||
| 34 | Carp::croak("Bad header argument") unless ref $header; | ||||
| 35 | if (ref($header) eq "ARRAY") { | ||||
| 36 | $header = HTTP::Headers->new(@$header); | ||||
| 37 | } | ||||
| 38 | else { | ||||
| 39 | $header = $header->clone; | ||||
| 40 | } | ||||
| 41 | } | ||||
| 42 | else { | ||||
| 43 | $header = HTTP::Headers->new; | ||||
| 44 | } | ||||
| 45 | if (defined $content) { | ||||
| 46 | _utf8_downgrade($content); | ||||
| 47 | } | ||||
| 48 | else { | ||||
| 49 | $content = ''; | ||||
| 50 | } | ||||
| 51 | |||||
| 52 | bless { | ||||
| 53 | '_headers' => $header, | ||||
| 54 | '_content' => $content, | ||||
| 55 | }, $class; | ||||
| 56 | } | ||||
| 57 | |||||
| 58 | |||||
| 59 | sub parse | ||||
| 60 | { | ||||
| 61 | my($class, $str) = @_; | ||||
| 62 | |||||
| 63 | my @hdr; | ||||
| 64 | while (1) { | ||||
| 65 | if ($str =~ s/^([^\s:]+)[ \t]*: ?(.*)\n?//) { | ||||
| 66 | push(@hdr, $1, $2); | ||||
| 67 | $hdr[-1] =~ s/\r\z//; | ||||
| 68 | } | ||||
| 69 | elsif (@hdr && $str =~ s/^([ \t].*)\n?//) { | ||||
| 70 | $hdr[-1] .= "\n$1"; | ||||
| 71 | $hdr[-1] =~ s/\r\z//; | ||||
| 72 | } | ||||
| 73 | else { | ||||
| 74 | $str =~ s/^\r?\n//; | ||||
| 75 | last; | ||||
| 76 | } | ||||
| 77 | } | ||||
| 78 | local $HTTP::Headers::TRANSLATE_UNDERSCORE; | ||||
| 79 | new($class, \@hdr, $str); | ||||
| 80 | } | ||||
| 81 | |||||
| 82 | |||||
| 83 | sub clone | ||||
| 84 | { | ||||
| 85 | my $self = shift; | ||||
| 86 | my $clone = HTTP::Message->new($self->headers, | ||||
| 87 | $self->content); | ||||
| 88 | $clone->protocol($self->protocol); | ||||
| 89 | $clone; | ||||
| 90 | } | ||||
| 91 | |||||
| 92 | |||||
| 93 | sub clear { | ||||
| 94 | my $self = shift; | ||||
| 95 | $self->{_headers}->clear; | ||||
| 96 | $self->content(""); | ||||
| 97 | delete $self->{_parts}; | ||||
| 98 | return; | ||||
| 99 | } | ||||
| 100 | |||||
| 101 | |||||
| 102 | sub protocol { | ||||
| 103 | shift->_elem('_protocol', @_); | ||||
| 104 | } | ||||
| 105 | |||||
| 106 | sub headers { | ||||
| 107 | my $self = shift; | ||||
| 108 | |||||
| 109 | # recalculation of _content might change headers, so we | ||||
| 110 | # need to force it now | ||||
| 111 | $self->_content unless exists $self->{_content}; | ||||
| 112 | |||||
| 113 | $self->{_headers}; | ||||
| 114 | } | ||||
| 115 | |||||
| 116 | sub headers_as_string { | ||||
| 117 | shift->headers->as_string(@_); | ||||
| 118 | } | ||||
| 119 | |||||
| 120 | |||||
| 121 | sub content { | ||||
| 122 | |||||
| 123 | my $self = $_[0]; | ||||
| 124 | if (defined(wantarray)) { | ||||
| 125 | $self->_content unless exists $self->{_content}; | ||||
| 126 | my $old = $self->{_content}; | ||||
| 127 | $old = $$old if ref($old) eq "SCALAR"; | ||||
| 128 | &_set_content if @_ > 1; | ||||
| 129 | return $old; | ||||
| 130 | } | ||||
| 131 | |||||
| 132 | if (@_ > 1) { | ||||
| 133 | &_set_content; | ||||
| 134 | } | ||||
| 135 | else { | ||||
| 136 | Carp::carp("Useless content call in void context") if $^W; | ||||
| 137 | } | ||||
| 138 | } | ||||
| 139 | |||||
| 140 | |||||
| 141 | sub _set_content { | ||||
| 142 | my $self = $_[0]; | ||||
| 143 | _utf8_downgrade($_[1]); | ||||
| 144 | if (!ref($_[1]) && ref($self->{_content}) eq "SCALAR") { | ||||
| 145 | ${$self->{_content}} = $_[1]; | ||||
| 146 | } | ||||
| 147 | else { | ||||
| 148 | die "Can't set content to be a scalar reference" if ref($_[1]) eq "SCALAR"; | ||||
| 149 | $self->{_content} = $_[1]; | ||||
| 150 | delete $self->{_content_ref}; | ||||
| 151 | } | ||||
| 152 | delete $self->{_parts} unless $_[2]; | ||||
| 153 | } | ||||
| 154 | |||||
| 155 | |||||
| 156 | sub add_content | ||||
| 157 | { | ||||
| 158 | my $self = shift; | ||||
| 159 | $self->_content unless exists $self->{_content}; | ||||
| 160 | my $chunkref = \$_[0]; | ||||
| 161 | $chunkref = $$chunkref if ref($$chunkref); # legacy | ||||
| 162 | |||||
| 163 | _utf8_downgrade($$chunkref); | ||||
| 164 | |||||
| 165 | my $ref = ref($self->{_content}); | ||||
| 166 | if (!$ref) { | ||||
| 167 | $self->{_content} .= $$chunkref; | ||||
| 168 | } | ||||
| 169 | elsif ($ref eq "SCALAR") { | ||||
| 170 | ${$self->{_content}} .= $$chunkref; | ||||
| 171 | } | ||||
| 172 | else { | ||||
| 173 | Carp::croak("Can't append to $ref content"); | ||||
| 174 | } | ||||
| 175 | delete $self->{_parts}; | ||||
| 176 | } | ||||
| 177 | |||||
| 178 | sub add_content_utf8 { | ||||
| 179 | my($self, $buf) = @_; | ||||
| 180 | utf8::upgrade($buf); | ||||
| 181 | utf8::encode($buf); | ||||
| 182 | $self->add_content($buf); | ||||
| 183 | } | ||||
| 184 | |||||
| 185 | sub content_ref | ||||
| 186 | { | ||||
| 187 | my $self = shift; | ||||
| 188 | $self->_content unless exists $self->{_content}; | ||||
| 189 | delete $self->{_parts}; | ||||
| 190 | my $old = \$self->{_content}; | ||||
| 191 | my $old_cref = $self->{_content_ref}; | ||||
| 192 | if (@_) { | ||||
| 193 | my $new = shift; | ||||
| 194 | Carp::croak("Setting content_ref to a non-ref") unless ref($new); | ||||
| 195 | delete $self->{_content}; # avoid modifying $$old | ||||
| 196 | $self->{_content} = $new; | ||||
| 197 | $self->{_content_ref}++; | ||||
| 198 | } | ||||
| 199 | $old = $$old if $old_cref; | ||||
| 200 | return $old; | ||||
| 201 | } | ||||
| 202 | |||||
| 203 | |||||
| 204 | sub content_charset | ||||
| 205 | { | ||||
| 206 | my $self = shift; | ||||
| 207 | if (my $charset = $self->content_type_charset) { | ||||
| 208 | return $charset; | ||||
| 209 | } | ||||
| 210 | |||||
| 211 | # time to start guessing | ||||
| 212 | my $cref = $self->decoded_content(ref => 1, charset => "none"); | ||||
| 213 | |||||
| 214 | # Unicode BOM | ||||
| 215 | for ($$cref) { | ||||
| 216 | return "UTF-8" if /^\xEF\xBB\xBF/; | ||||
| 217 | return "UTF-32LE" if /^\xFF\xFE\x00\x00/; | ||||
| 218 | return "UTF-32BE" if /^\x00\x00\xFE\xFF/; | ||||
| 219 | return "UTF-16LE" if /^\xFF\xFE/; | ||||
| 220 | return "UTF-16BE" if /^\xFE\xFF/; | ||||
| 221 | } | ||||
| 222 | |||||
| 223 | if ($self->content_is_xml) { | ||||
| 224 | # http://www.w3.org/TR/2006/REC-xml-20060816/#sec-guessing | ||||
| 225 | # XML entity not accompanied by external encoding information and not | ||||
| 226 | # in UTF-8 or UTF-16 encoding must begin with an XML encoding declaration, | ||||
| 227 | # in which the first characters must be '<?xml' | ||||
| 228 | for ($$cref) { | ||||
| 229 | return "UTF-32BE" if /^\x00\x00\x00</; | ||||
| 230 | return "UTF-32LE" if /^<\x00\x00\x00/; | ||||
| 231 | return "UTF-16BE" if /^(?:\x00\s)*\x00</; | ||||
| 232 | return "UTF-16LE" if /^(?:\s\x00)*<\x00/; | ||||
| 233 | if (/^\s*(<\?xml[^\x00]*?\?>)/) { | ||||
| 234 | if ($1 =~ /\sencoding\s*=\s*(["'])(.*?)\1/) { | ||||
| 235 | my $enc = $2; | ||||
| 236 | $enc =~ s/^\s+//; $enc =~ s/\s+\z//; | ||||
| 237 | return $enc if $enc; | ||||
| 238 | } | ||||
| 239 | } | ||||
| 240 | } | ||||
| 241 | return "UTF-8"; | ||||
| 242 | } | ||||
| 243 | elsif ($self->content_is_html) { | ||||
| 244 | # look for <META charset="..."> or <META content="..."> | ||||
| 245 | # http://dev.w3.org/html5/spec/Overview.html#determining-the-character-encoding | ||||
| 246 | require IO::HTML; | ||||
| 247 | # Use relaxed search to match previous versions of HTTP::Message: | ||||
| 248 | my $encoding = IO::HTML::find_charset_in($$cref, { encoding => 1, | ||||
| 249 | need_pragma => 0 }); | ||||
| 250 | return $encoding->mime_name if $encoding; | ||||
| 251 | } | ||||
| 252 | elsif ($self->content_type eq "application/json") { | ||||
| 253 | for ($$cref) { | ||||
| 254 | # RFC 4627, ch 3 | ||||
| 255 | return "UTF-32BE" if /^\x00\x00\x00./s; | ||||
| 256 | return "UTF-32LE" if /^.\x00\x00\x00/s; | ||||
| 257 | return "UTF-16BE" if /^\x00.\x00./s; | ||||
| 258 | return "UTF-16LE" if /^.\x00.\x00/s; | ||||
| 259 | return "UTF-8"; | ||||
| 260 | } | ||||
| 261 | } | ||||
| 262 | if ($self->content_type =~ /^text\//) { | ||||
| 263 | for ($$cref) { | ||||
| 264 | if (length) { | ||||
| 265 | return "US-ASCII" unless /[\x80-\xFF]/; | ||||
| 266 | require Encode; | ||||
| 267 | eval { | ||||
| 268 | Encode::decode_utf8($_, Encode::FB_CROAK() | Encode::LEAVE_SRC()); | ||||
| 269 | }; | ||||
| 270 | return "UTF-8" unless $@; | ||||
| 271 | return "ISO-8859-1"; | ||||
| 272 | } | ||||
| 273 | } | ||||
| 274 | } | ||||
| 275 | |||||
| 276 | return undef; | ||||
| 277 | } | ||||
| 278 | |||||
| 279 | |||||
| 280 | sub decoded_content | ||||
| 281 | { | ||||
| 282 | my($self, %opt) = @_; | ||||
| 283 | my $content_ref; | ||||
| 284 | my $content_ref_iscopy; | ||||
| 285 | |||||
| 286 | eval { | ||||
| 287 | $content_ref = $self->content_ref; | ||||
| 288 | die "Can't decode ref content" if ref($content_ref) ne "SCALAR"; | ||||
| 289 | |||||
| 290 | if (my $h = $self->header("Content-Encoding")) { | ||||
| 291 | $h =~ s/^\s+//; | ||||
| 292 | $h =~ s/\s+$//; | ||||
| 293 | for my $ce (reverse split(/\s*,\s*/, lc($h))) { | ||||
| 294 | next unless $ce; | ||||
| 295 | next if $ce eq "identity"; | ||||
| 296 | if ($ce eq "gzip" || $ce eq "x-gzip") { | ||||
| 297 | require IO::Uncompress::Gunzip; | ||||
| 298 | my $output; | ||||
| 299 | IO::Uncompress::Gunzip::gunzip($content_ref, \$output, Transparent => 0) | ||||
| 300 | or die "Can't gunzip content: $IO::Uncompress::Gunzip::GunzipError"; | ||||
| 301 | $content_ref = \$output; | ||||
| 302 | $content_ref_iscopy++; | ||||
| 303 | } | ||||
| 304 | elsif ($ce eq "x-bzip2" or $ce eq "bzip2") { | ||||
| 305 | require IO::Uncompress::Bunzip2; | ||||
| 306 | my $output; | ||||
| 307 | IO::Uncompress::Bunzip2::bunzip2($content_ref, \$output, Transparent => 0) | ||||
| 308 | or die "Can't bunzip content: $IO::Uncompress::Bunzip2::Bunzip2Error"; | ||||
| 309 | $content_ref = \$output; | ||||
| 310 | $content_ref_iscopy++; | ||||
| 311 | } | ||||
| 312 | elsif ($ce eq "deflate") { | ||||
| 313 | require IO::Uncompress::Inflate; | ||||
| 314 | my $output; | ||||
| 315 | my $status = IO::Uncompress::Inflate::inflate($content_ref, \$output, Transparent => 0); | ||||
| 316 | my $error = $IO::Uncompress::Inflate::InflateError; | ||||
| 317 | unless ($status) { | ||||
| 318 | # "Content-Encoding: deflate" is supposed to mean the | ||||
| 319 | # "zlib" format of RFC 1950, but Microsoft got that | ||||
| 320 | # wrong, so some servers sends the raw compressed | ||||
| 321 | # "deflate" data. This tries to inflate this format. | ||||
| 322 | $output = undef; | ||||
| 323 | require IO::Uncompress::RawInflate; | ||||
| 324 | unless (IO::Uncompress::RawInflate::rawinflate($content_ref, \$output)) { | ||||
| 325 | $self->push_header("Client-Warning" => | ||||
| 326 | "Could not raw inflate content: $IO::Uncompress::RawInflate::RawInflateError"); | ||||
| 327 | $output = undef; | ||||
| 328 | } | ||||
| 329 | } | ||||
| 330 | die "Can't inflate content: $error" unless defined $output; | ||||
| 331 | $content_ref = \$output; | ||||
| 332 | $content_ref_iscopy++; | ||||
| 333 | } | ||||
| 334 | elsif ($ce eq "compress" || $ce eq "x-compress") { | ||||
| 335 | die "Can't uncompress content"; | ||||
| 336 | } | ||||
| 337 | elsif ($ce eq "base64") { # not really C-T-E, but should be harmless | ||||
| 338 | require MIME::Base64; | ||||
| 339 | $content_ref = \MIME::Base64::decode($$content_ref); | ||||
| 340 | $content_ref_iscopy++; | ||||
| 341 | } | ||||
| 342 | elsif ($ce eq "quoted-printable") { # not really C-T-E, but should be harmless | ||||
| 343 | require MIME::QuotedPrint; | ||||
| 344 | $content_ref = \MIME::QuotedPrint::decode($$content_ref); | ||||
| 345 | $content_ref_iscopy++; | ||||
| 346 | } | ||||
| 347 | else { | ||||
| 348 | die "Don't know how to decode Content-Encoding '$ce'"; | ||||
| 349 | } | ||||
| 350 | } | ||||
| 351 | } | ||||
| 352 | |||||
| 353 | if ($self->content_is_text || (my $is_xml = $self->content_is_xml)) { | ||||
| 354 | my $charset = lc( | ||||
| 355 | $opt{charset} || | ||||
| 356 | $self->content_type_charset || | ||||
| 357 | $opt{default_charset} || | ||||
| 358 | $self->content_charset || | ||||
| 359 | "ISO-8859-1" | ||||
| 360 | ); | ||||
| 361 | if ($charset eq "none") { | ||||
| 362 | # leave it asis | ||||
| 363 | } | ||||
| 364 | elsif ($charset eq "us-ascii" || $charset eq "iso-8859-1") { | ||||
| 365 | if ($$content_ref =~ /[^\x00-\x7F]/ && defined &utf8::upgrade) { | ||||
| 366 | unless ($content_ref_iscopy) { | ||||
| 367 | my $copy = $$content_ref; | ||||
| 368 | $content_ref = \$copy; | ||||
| 369 | $content_ref_iscopy++; | ||||
| 370 | } | ||||
| 371 | utf8::upgrade($$content_ref); | ||||
| 372 | } | ||||
| 373 | } | ||||
| 374 | else { | ||||
| 375 | require Encode; | ||||
| 376 | eval { | ||||
| 377 | $content_ref = \Encode::decode($charset, $$content_ref, | ||||
| 378 | ($opt{charset_strict} ? Encode::FB_CROAK() : 0) | Encode::LEAVE_SRC()); | ||||
| 379 | }; | ||||
| 380 | if ($@) { | ||||
| 381 | my $retried; | ||||
| 382 | if ($@ =~ /^Unknown encoding/) { | ||||
| 383 | my $alt_charset = lc($opt{alt_charset} || ""); | ||||
| 384 | if ($alt_charset && $charset ne $alt_charset) { | ||||
| 385 | # Retry decoding with the alternative charset | ||||
| 386 | $content_ref = \Encode::decode($alt_charset, $$content_ref, | ||||
| 387 | ($opt{charset_strict} ? Encode::FB_CROAK() : 0) | Encode::LEAVE_SRC()) | ||||
| 388 | unless $alt_charset eq "none"; | ||||
| 389 | $retried++; | ||||
| 390 | } | ||||
| 391 | } | ||||
| 392 | die unless $retried; | ||||
| 393 | } | ||||
| 394 | die "Encode::decode() returned undef improperly" unless defined $$content_ref; | ||||
| 395 | if ($is_xml) { | ||||
| 396 | # Get rid of the XML encoding declaration if present | ||||
| 397 | $$content_ref =~ s/^\x{FEFF}//; | ||||
| 398 | if ($$content_ref =~ /^(\s*<\?xml[^\x00]*?\?>)/) { | ||||
| 399 | substr($$content_ref, 0, length($1)) =~ s/\sencoding\s*=\s*(["']).*?\1//; | ||||
| 400 | } | ||||
| 401 | } | ||||
| 402 | } | ||||
| 403 | } | ||||
| 404 | }; | ||||
| 405 | if ($@) { | ||||
| 406 | Carp::croak($@) if $opt{raise_error}; | ||||
| 407 | return undef; | ||||
| 408 | } | ||||
| 409 | |||||
| 410 | return $opt{ref} ? $content_ref : $$content_ref; | ||||
| 411 | } | ||||
| 412 | |||||
| 413 | |||||
| 414 | sub decodable | ||||
| 415 | { | ||||
| 416 | # should match the Content-Encoding values that decoded_content can deal with | ||||
| 417 | my $self = shift; | ||||
| 418 | my @enc; | ||||
| 419 | # XXX preferably we should determine if the modules are available without loading | ||||
| 420 | # them here | ||||
| 421 | eval { | ||||
| 422 | require IO::Uncompress::Gunzip; | ||||
| 423 | push(@enc, "gzip", "x-gzip"); | ||||
| 424 | }; | ||||
| 425 | eval { | ||||
| 426 | require IO::Uncompress::Inflate; | ||||
| 427 | require IO::Uncompress::RawInflate; | ||||
| 428 | push(@enc, "deflate"); | ||||
| 429 | }; | ||||
| 430 | eval { | ||||
| 431 | require IO::Uncompress::Bunzip2; | ||||
| 432 | push(@enc, "x-bzip2"); | ||||
| 433 | }; | ||||
| 434 | # we don't care about announcing the 'identity', 'base64' and | ||||
| 435 | # 'quoted-printable' stuff | ||||
| 436 | return wantarray ? @enc : join(", ", @enc); | ||||
| 437 | } | ||||
| 438 | |||||
| 439 | |||||
| 440 | sub decode | ||||
| 441 | { | ||||
| 442 | my $self = shift; | ||||
| 443 | return 1 unless $self->header("Content-Encoding"); | ||||
| 444 | if (defined(my $content = $self->decoded_content(charset => "none"))) { | ||||
| 445 | $self->remove_header("Content-Encoding", "Content-Length", "Content-MD5"); | ||||
| 446 | $self->content($content); | ||||
| 447 | return 1; | ||||
| 448 | } | ||||
| 449 | return 0; | ||||
| 450 | } | ||||
| 451 | |||||
| 452 | |||||
| 453 | sub encode | ||||
| 454 | { | ||||
| 455 | my($self, @enc) = @_; | ||||
| 456 | |||||
| 457 | Carp::croak("Can't encode multipart/* messages") if $self->content_type =~ m,^multipart/,; | ||||
| 458 | Carp::croak("Can't encode message/* messages") if $self->content_type =~ m,^message/,; | ||||
| 459 | |||||
| 460 | return 1 unless @enc; # nothing to do | ||||
| 461 | |||||
| 462 | my $content = $self->content; | ||||
| 463 | for my $encoding (@enc) { | ||||
| 464 | if ($encoding eq "identity") { | ||||
| 465 | # nothing to do | ||||
| 466 | } | ||||
| 467 | elsif ($encoding eq "base64") { | ||||
| 468 | require MIME::Base64; | ||||
| 469 | $content = MIME::Base64::encode($content); | ||||
| 470 | } | ||||
| 471 | elsif ($encoding eq "gzip" || $encoding eq "x-gzip") { | ||||
| 472 | require IO::Compress::Gzip; | ||||
| 473 | my $output; | ||||
| 474 | IO::Compress::Gzip::gzip(\$content, \$output, Minimal => 1) | ||||
| 475 | or die "Can't gzip content: $IO::Compress::Gzip::GzipError"; | ||||
| 476 | $content = $output; | ||||
| 477 | } | ||||
| 478 | elsif ($encoding eq "deflate") { | ||||
| 479 | require IO::Compress::Deflate; | ||||
| 480 | my $output; | ||||
| 481 | IO::Compress::Deflate::deflate(\$content, \$output) | ||||
| 482 | or die "Can't deflate content: $IO::Compress::Deflate::DeflateError"; | ||||
| 483 | $content = $output; | ||||
| 484 | } | ||||
| 485 | elsif ($encoding eq "x-bzip2") { | ||||
| 486 | require IO::Compress::Bzip2; | ||||
| 487 | my $output; | ||||
| 488 | IO::Compress::Bzip2::bzip2(\$content, \$output) | ||||
| 489 | or die "Can't bzip2 content: $IO::Compress::Bzip2::Bzip2Error"; | ||||
| 490 | $content = $output; | ||||
| 491 | } | ||||
| 492 | elsif ($encoding eq "rot13") { # for the fun of it | ||||
| 493 | $content =~ tr/A-Za-z/N-ZA-Mn-za-m/; | ||||
| 494 | } | ||||
| 495 | else { | ||||
| 496 | return 0; | ||||
| 497 | } | ||||
| 498 | } | ||||
| 499 | my $h = $self->header("Content-Encoding"); | ||||
| 500 | unshift(@enc, $h) if $h; | ||||
| 501 | $self->header("Content-Encoding", join(", ", @enc)); | ||||
| 502 | $self->remove_header("Content-Length", "Content-MD5"); | ||||
| 503 | $self->content($content); | ||||
| 504 | return 1; | ||||
| 505 | } | ||||
| 506 | |||||
| 507 | |||||
| 508 | sub as_string | ||||
| 509 | { | ||||
| 510 | my($self, $eol) = @_; | ||||
| 511 | $eol = "\n" unless defined $eol; | ||||
| 512 | |||||
| 513 | # The calculation of content might update the headers | ||||
| 514 | # so we need to do that first. | ||||
| 515 | my $content = $self->content; | ||||
| 516 | |||||
| 517 | return join("", $self->{'_headers'}->as_string($eol), | ||||
| 518 | $eol, | ||||
| 519 | $content, | ||||
| 520 | (@_ == 1 && length($content) && | ||||
| 521 | $content !~ /\n\z/) ? "\n" : "", | ||||
| 522 | ); | ||||
| 523 | } | ||||
| 524 | |||||
| 525 | |||||
| 526 | sub dump | ||||
| 527 | { | ||||
| 528 | my($self, %opt) = @_; | ||||
| 529 | my $content = $self->content; | ||||
| 530 | my $chopped = 0; | ||||
| 531 | if (!ref($content)) { | ||||
| 532 | my $maxlen = $opt{maxlength}; | ||||
| 533 | $maxlen = 512 unless defined($maxlen); | ||||
| 534 | if ($maxlen && length($content) > $maxlen * 1.1 + 3) { | ||||
| 535 | $chopped = length($content) - $maxlen; | ||||
| 536 | $content = substr($content, 0, $maxlen) . "..."; | ||||
| 537 | } | ||||
| 538 | |||||
| 539 | $content =~ s/\\/\\\\/g; | ||||
| 540 | $content =~ s/\t/\\t/g; | ||||
| 541 | $content =~ s/\r/\\r/g; | ||||
| 542 | |||||
| 543 | # no need for 3 digits in escape for these | ||||
| 544 | $content =~ s/([\0-\11\13-\037])(?!\d)/sprintf('\\%o',ord($1))/eg; | ||||
| 545 | |||||
| 546 | $content =~ s/([\0-\11\13-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg; | ||||
| 547 | $content =~ s/([^\12\040-\176])/sprintf('\\x{%X}',ord($1))/eg; | ||||
| 548 | |||||
| 549 | # remaining whitespace | ||||
| 550 | $content =~ s/( +)\n/("\\40" x length($1)) . "\n"/eg; | ||||
| 551 | $content =~ s/(\n+)\n/("\\n" x length($1)) . "\n"/eg; | ||||
| 552 | $content =~ s/\n\z/\\n/; | ||||
| 553 | |||||
| 554 | my $no_content = $opt{no_content}; | ||||
| 555 | $no_content = "(no content)" unless defined $no_content; | ||||
| 556 | if ($content eq $no_content) { | ||||
| 557 | # escape our $no_content marker | ||||
| 558 | $content =~ s/^(.)/sprintf('\\x%02X',ord($1))/eg; | ||||
| 559 | } | ||||
| 560 | elsif ($content eq "") { | ||||
| 561 | $content = $no_content; | ||||
| 562 | } | ||||
| 563 | } | ||||
| 564 | |||||
| 565 | my @dump; | ||||
| 566 | push(@dump, $opt{preheader}) if $opt{preheader}; | ||||
| 567 | push(@dump, $self->{_headers}->as_string, $content); | ||||
| 568 | push(@dump, "(+ $chopped more bytes not shown)") if $chopped; | ||||
| 569 | |||||
| 570 | my $dump = join("\n", @dump, ""); | ||||
| 571 | $dump =~ s/^/$opt{prefix}/gm if $opt{prefix}; | ||||
| 572 | |||||
| 573 | print $dump unless defined wantarray; | ||||
| 574 | return $dump; | ||||
| 575 | } | ||||
| 576 | |||||
| 577 | |||||
| 578 | sub parts { | ||||
| 579 | my $self = shift; | ||||
| 580 | if (defined(wantarray) && (!exists $self->{_parts} || ref($self->{_content}) eq "SCALAR")) { | ||||
| 581 | $self->_parts; | ||||
| 582 | } | ||||
| 583 | my $old = $self->{_parts}; | ||||
| 584 | if (@_) { | ||||
| 585 | my @parts = map { ref($_) eq 'ARRAY' ? @$_ : $_ } @_; | ||||
| 586 | my $ct = $self->content_type || ""; | ||||
| 587 | if ($ct =~ m,^message/,) { | ||||
| 588 | Carp::croak("Only one part allowed for $ct content") | ||||
| 589 | if @parts > 1; | ||||
| 590 | } | ||||
| 591 | elsif ($ct !~ m,^multipart/,) { | ||||
| 592 | $self->remove_content_headers; | ||||
| 593 | $self->content_type("multipart/mixed"); | ||||
| 594 | } | ||||
| 595 | $self->{_parts} = \@parts; | ||||
| 596 | _stale_content($self); | ||||
| 597 | } | ||||
| 598 | return @$old if wantarray; | ||||
| 599 | return $old->[0]; | ||||
| 600 | } | ||||
| 601 | |||||
| 602 | sub add_part { | ||||
| 603 | my $self = shift; | ||||
| 604 | if (($self->content_type || "") !~ m,^multipart/,) { | ||||
| 605 | my $p = HTTP::Message->new($self->remove_content_headers, | ||||
| 606 | $self->content("")); | ||||
| 607 | $self->content_type("multipart/mixed"); | ||||
| 608 | $self->{_parts} = []; | ||||
| 609 | if ($p->headers->header_field_names || $p->content ne "") { | ||||
| 610 | push(@{$self->{_parts}}, $p); | ||||
| 611 | } | ||||
| 612 | } | ||||
| 613 | elsif (!exists $self->{_parts} || ref($self->{_content}) eq "SCALAR") { | ||||
| 614 | $self->_parts; | ||||
| 615 | } | ||||
| 616 | |||||
| 617 | push(@{$self->{_parts}}, @_); | ||||
| 618 | _stale_content($self); | ||||
| 619 | return; | ||||
| 620 | } | ||||
| 621 | |||||
| 622 | sub _stale_content { | ||||
| 623 | my $self = shift; | ||||
| 624 | if (ref($self->{_content}) eq "SCALAR") { | ||||
| 625 | # must recalculate now | ||||
| 626 | $self->_content; | ||||
| 627 | } | ||||
| 628 | else { | ||||
| 629 | # just invalidate cache | ||||
| 630 | delete $self->{_content}; | ||||
| 631 | delete $self->{_content_ref}; | ||||
| 632 | } | ||||
| 633 | } | ||||
| 634 | |||||
| 635 | |||||
| 636 | # delegate all other method calls the the headers object. | ||||
| 637 | sub AUTOLOAD | ||||
| 638 | { | ||||
| 639 | my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2); | ||||
| 640 | |||||
| 641 | # We create the function here so that it will not need to be | ||||
| 642 | # autoloaded the next time. | ||||
| 643 | 2 | 1.25ms | 2 | 51µs | # spent 32µs (13+19) within HTTP::Message::BEGIN@643 which was called:
# once (13µs+19µs) by LWP::UserAgent::BEGIN@10 at line 643 # spent 32µs making 1 call to HTTP::Message::BEGIN@643
# spent 19µs making 1 call to strict::unimport |
| 644 | *$method = sub { local $Carp::Internal{+__PACKAGE__} = 1; shift->headers->$method(@_) }; | ||||
| 645 | goto &$method; | ||||
| 646 | } | ||||
| 647 | |||||
| 648 | |||||
| 649 | sub DESTROY {} # avoid AUTOLOADing it | ||||
| 650 | |||||
| 651 | |||||
| 652 | # Private method to access members in %$self | ||||
| 653 | sub _elem | ||||
| 654 | { | ||||
| 655 | my $self = shift; | ||||
| 656 | my $elem = shift; | ||||
| 657 | my $old = $self->{$elem}; | ||||
| 658 | $self->{$elem} = $_[0] if @_; | ||||
| 659 | return $old; | ||||
| 660 | } | ||||
| 661 | |||||
| 662 | |||||
| 663 | # Create private _parts attribute from current _content | ||||
| 664 | sub _parts { | ||||
| 665 | my $self = shift; | ||||
| 666 | my $ct = $self->content_type; | ||||
| 667 | if ($ct =~ m,^multipart/,) { | ||||
| 668 | require HTTP::Headers::Util; | ||||
| 669 | my @h = HTTP::Headers::Util::split_header_words($self->header("Content-Type")); | ||||
| 670 | die "Assert" unless @h; | ||||
| 671 | my %h = @{$h[0]}; | ||||
| 672 | if (defined(my $b = $h{boundary})) { | ||||
| 673 | my $str = $self->content; | ||||
| 674 | $str =~ s/\r?\n--\Q$b\E--.*//s; | ||||
| 675 | if ($str =~ s/(^|.*?\r?\n)--\Q$b\E\r?\n//s) { | ||||
| 676 | $self->{_parts} = [map HTTP::Message->parse($_), | ||||
| 677 | split(/\r?\n--\Q$b\E\r?\n/, $str)] | ||||
| 678 | } | ||||
| 679 | } | ||||
| 680 | } | ||||
| 681 | elsif ($ct eq "message/http") { | ||||
| 682 | require HTTP::Request; | ||||
| 683 | require HTTP::Response; | ||||
| 684 | my $content = $self->content; | ||||
| 685 | my $class = ($content =~ m,^(HTTP/.*)\n,) ? | ||||
| 686 | "HTTP::Response" : "HTTP::Request"; | ||||
| 687 | $self->{_parts} = [$class->parse($content)]; | ||||
| 688 | } | ||||
| 689 | elsif ($ct =~ m,^message/,) { | ||||
| 690 | $self->{_parts} = [ HTTP::Message->parse($self->content) ]; | ||||
| 691 | } | ||||
| 692 | |||||
| 693 | $self->{_parts} ||= []; | ||||
| 694 | } | ||||
| 695 | |||||
| 696 | |||||
| 697 | # Create private _content attribute from current _parts | ||||
| 698 | sub _content { | ||||
| 699 | my $self = shift; | ||||
| 700 | my $ct = $self->{_headers}->header("Content-Type") || "multipart/mixed"; | ||||
| 701 | if ($ct =~ m,^\s*message/,i) { | ||||
| 702 | _set_content($self, $self->{_parts}[0]->as_string($CRLF), 1); | ||||
| 703 | return; | ||||
| 704 | } | ||||
| 705 | |||||
| 706 | require HTTP::Headers::Util; | ||||
| 707 | my @v = HTTP::Headers::Util::split_header_words($ct); | ||||
| 708 | Carp::carp("Multiple Content-Type headers") if @v > 1; | ||||
| 709 | @v = @{$v[0]}; | ||||
| 710 | |||||
| 711 | my $boundary; | ||||
| 712 | my $boundary_index; | ||||
| 713 | for (my @tmp = @v; @tmp;) { | ||||
| 714 | my($k, $v) = splice(@tmp, 0, 2); | ||||
| 715 | if ($k eq "boundary") { | ||||
| 716 | $boundary = $v; | ||||
| 717 | $boundary_index = @v - @tmp - 1; | ||||
| 718 | last; | ||||
| 719 | } | ||||
| 720 | } | ||||
| 721 | |||||
| 722 | my @parts = map $_->as_string($CRLF), @{$self->{_parts}}; | ||||
| 723 | |||||
| 724 | my $bno = 0; | ||||
| 725 | $boundary = _boundary() unless defined $boundary; | ||||
| 726 | CHECK_BOUNDARY: | ||||
| 727 | { | ||||
| 728 | for (@parts) { | ||||
| 729 | if (index($_, $boundary) >= 0) { | ||||
| 730 | # must have a better boundary | ||||
| 731 | $boundary = _boundary(++$bno); | ||||
| 732 | redo CHECK_BOUNDARY; | ||||
| 733 | } | ||||
| 734 | } | ||||
| 735 | } | ||||
| 736 | |||||
| 737 | if ($boundary_index) { | ||||
| 738 | $v[$boundary_index] = $boundary; | ||||
| 739 | } | ||||
| 740 | else { | ||||
| 741 | push(@v, boundary => $boundary); | ||||
| 742 | } | ||||
| 743 | |||||
| 744 | $ct = HTTP::Headers::Util::join_header_words(@v); | ||||
| 745 | $self->{_headers}->header("Content-Type", $ct); | ||||
| 746 | |||||
| 747 | _set_content($self, "--$boundary$CRLF" . | ||||
| 748 | join("$CRLF--$boundary$CRLF", @parts) . | ||||
| 749 | "$CRLF--$boundary--$CRLF", | ||||
| 750 | 1); | ||||
| 751 | } | ||||
| 752 | |||||
| 753 | |||||
| 754 | sub _boundary | ||||
| 755 | { | ||||
| 756 | my $size = shift || return "xYzZY"; | ||||
| 757 | require MIME::Base64; | ||||
| 758 | my $b = MIME::Base64::encode(join("", map chr(rand(256)), 1..$size*3), ""); | ||||
| 759 | $b =~ s/[\W]/X/g; # ensure alnum only | ||||
| 760 | $b; | ||||
| 761 | } | ||||
| 762 | |||||
| 763 | |||||
| 764 | 1 | 8µs | 1; | ||
| 765 | |||||
| 766 | |||||
| 767 | __END__ |