1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
#!/usr/bin/perl
use strict;
use warnings;
use HTTP::MultiPartParser qw[];
use Hash::MultiValue qw[];
use IO::File qw[SEEK_SET];
use File::Temp qw[];
# extracts name and filename values from Content-Disposition header.
# returns the escaped value, due to different behaviour across browsers.
# (see https://gist.github.com/chansen/7163968)
sub extract_form_data {
local $_ = shift;
# Fast exit for common form-data disposition
if (/\A form-data; \s name="((?:[^"]|\\")*)" (?: ;\s filename="((?:[^"]|\\")*)" )? \z/x) {
return ($1, $2);
}
# disposition type must be form-data
s/\A \s* form-data \s* ; //xi
or return;
my (%p, $k, $v);
while (length) {
s/ ^ \s+ //x;
s/ \s+ $ //x;
# skip empty parameters and unknown tokens
next if s/^ [^\s"=;]* \s* ; //x;
# parameter name (token)
s/^ ([^\s"=;]+) \s* = \s* //x
or return;
$k = lc $1;
# quoted parameter value
if (s/^ "((?:[^"]|\\")*)" \s* (?: ; | $) //x) {
$v = $1;
}
# unquoted parameter value (token)
elsif (s/^ ([^\s";]*) \s* (?: ; | $) //x) {
$v = $1;
}
else {
return;
}
if ($k eq 'name' || $k eq 'filename') {
return if exists $p{$k};
$p{$k} = $v;
}
}
return exists $p{name} ? @p{qw(name filename)} : ();
}
my $params = Hash::MultiValue->new;
my $uploads = Hash::MultiValue->new;
my $part;
my $parser = HTTP::MultiPartParser->new(
boundary => '----------0xKhTmLbOuNdArY',
on_header => sub {
my ($headers) = @_;
my $disposition;
foreach (@$headers) {
if (/\A Content-Disposition: [\x09\x20]* (.*)/xi) {
$disposition = $1;
last;
}
}
(defined $disposition)
or die q/Content-Disposition header is missing/;
my ($name, $filename) = extract_form_data($disposition);
(defined $name)
or die qq/Invalid Content-Disposition: '$disposition'/;
$part = {
name => $name,
headers => $headers,
};
if (defined $filename) {
$part->{filename} = $filename;
if (length $filename) {
my $fh = File::Temp->new(UNLINK => 1);
$part->{fh} = $fh;
$part->{tempname} = $fh->filename;
}
}
},
on_body => sub {
my ($chunk, $final) = @_;
my $fh = $part->{fh};
if ($fh) {
print $fh $chunk
or die qq/Could not write to file handle: '$!'/;
if ($final) {
seek($fh, 0, SEEK_SET)
or die qq/Could not rewind file handle: '$!'/;
$part->{size} = -s $fh;
$uploads->add($part->{name}, $part);
}
}
else {
$part->{data} .= $chunk;
if ($final) {
$params->add($part->{name}, $part->{data});
}
}
}
);
open my $fh, '<:raw', 't/data/001-content.dat'
or die;
while () {
my $n = read($fh, my $buffer, 1024);
unless ($n) {
die qq/Could not read from fh: '$!'/
unless defined $n;
last;
}
$parser->parse($buffer);
}
$parser->finish;
|