line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CGI::Tiny::Multipart; |
2
|
|
|
|
|
|
|
# ABSTRACT: Tiny multipart/form-data form parser |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
# This file is part of CGI::Tiny which is released under: |
5
|
|
|
|
|
|
|
# The Artistic License 2.0 (GPL Compatible) |
6
|
|
|
|
|
|
|
# See the documentation for CGI::Tiny for full license details. |
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
1204
|
use strict; |
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
64
|
|
9
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
54
|
|
10
|
2
|
|
|
2
|
|
10
|
use Exporter 'import'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
192
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = '1.001'; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our @EXPORT_OK = qw(extract_multipart_boundary parse_multipart_form_data); |
15
|
|
|
|
|
|
|
|
16
|
2
|
|
|
2
|
|
14
|
use constant DEFAULT_REQUEST_BODY_BUFFER => 262144; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
2836
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub extract_multipart_boundary { |
19
|
18
|
|
|
18
|
1
|
1338
|
my ($content_type) = @_; |
20
|
18
|
|
|
|
|
143
|
my ($boundary_quoted, $boundary_unquoted) = $content_type =~ m/;\s*boundary\s*=\s*(?:"((?:\\[\\"]|[^"])+)"|([^";]+))/i; |
21
|
18
|
100
|
|
|
|
68
|
$boundary_quoted =~ s/\\([\\"])/$1/g if defined $boundary_quoted; |
22
|
18
|
100
|
|
|
|
117
|
return defined $boundary_quoted ? $boundary_quoted : $boundary_unquoted; |
23
|
|
|
|
|
|
|
} |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub parse_multipart_form_data { |
26
|
25
|
|
|
25
|
1
|
106532
|
my ($input, $length, $boundary, $options) = @_; |
27
|
25
|
|
100
|
|
|
131
|
$options ||= {}; |
28
|
25
|
|
|
|
|
86
|
my $input_is_scalar = ref $input eq 'SCALAR'; |
29
|
25
|
100
|
|
|
|
97
|
binmode $input unless $input_is_scalar; |
30
|
25
|
|
50
|
|
|
86
|
my $remaining = 0 + ($length || 0); |
31
|
25
|
|
|
|
|
79
|
my $next_boundary = "\r\n--$boundary\r\n"; |
32
|
25
|
|
|
|
|
69
|
my $end_boundary = "\r\n--$boundary--"; |
33
|
25
|
|
100
|
|
|
142
|
my $buffer_size = 0 + ($options->{buffer_size} || DEFAULT_REQUEST_BODY_BUFFER); |
34
|
25
|
|
|
|
|
57
|
my $buffer = "\r\n"; |
35
|
25
|
|
|
|
|
64
|
my (%state, @parts, $current); |
36
|
25
|
|
|
|
|
90
|
READER: while ($remaining > 0) { |
37
|
25
|
100
|
|
|
|
65
|
if ($input_is_scalar) { |
38
|
17
|
|
|
|
|
60
|
$buffer .= substr $$input, 0, $remaining; |
39
|
17
|
|
|
|
|
37
|
$remaining = 0; |
40
|
|
|
|
|
|
|
} else { |
41
|
8
|
50
|
|
|
|
22
|
my $chunk = $remaining < $buffer_size ? $remaining : $buffer_size; |
42
|
8
|
50
|
|
|
|
58
|
last unless my $read = read $input, $buffer, $chunk, length $buffer; |
43
|
8
|
|
|
|
|
19
|
$remaining -= $read; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
25
|
50
|
33
|
|
|
147
|
unless ($state{parsing_headers} or $state{parsing_body}) { |
47
|
25
|
|
|
|
|
103
|
my $next_pos = index $buffer, $next_boundary; |
48
|
25
|
|
|
|
|
93
|
my $end_pos = index $buffer, $end_boundary; |
49
|
25
|
100
|
100
|
|
|
181
|
if ($next_pos >= 0 and ($end_pos < 0 or $end_pos > $next_pos)) { |
|
|
100
|
100
|
|
|
|
|
50
|
22
|
|
|
|
|
62
|
substr $buffer, 0, $next_pos + length($next_boundary), ''; |
51
|
22
|
|
|
|
|
61
|
$state{parsing_headers} = 1; |
52
|
22
|
|
|
|
|
211
|
push @parts, $current = {headers => {}, name => undef, filename => undef, size => 0}; |
53
|
|
|
|
|
|
|
} elsif ($end_pos >= 0) { |
54
|
1
|
|
|
|
|
4
|
$state{done} = 1; |
55
|
1
|
|
|
|
|
4
|
last; # end of multipart data |
56
|
|
|
|
|
|
|
} else { |
57
|
2
|
|
|
|
|
9
|
next; # read more to find start of multipart data |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
22
|
|
|
|
|
83
|
while (length $buffer) { |
62
|
234
|
100
|
|
|
|
430
|
if ($state{parsing_headers}) { |
63
|
118
|
|
|
|
|
348
|
while ((my $pos = index $buffer, "\r\n") >= 0) { |
64
|
295
|
100
|
|
|
|
571
|
if ($pos == 0) { # end of headers |
65
|
116
|
|
|
|
|
185
|
$state{parsing_headers} = 0; |
66
|
116
|
|
|
|
|
165
|
$state{parsing_body} = 1; |
67
|
116
|
|
|
|
|
160
|
$state{parsed_optional_crlf} = 0; |
68
|
116
|
|
|
|
|
198
|
last; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
179
|
|
|
|
|
409
|
my $header = substr $buffer, 0, $pos + 2, ''; |
72
|
179
|
|
|
|
|
942
|
my ($name, $value) = split /\s*:\s*/, $header, 2; |
73
|
179
|
100
|
|
|
|
485
|
return undef unless defined $value; |
74
|
177
|
|
|
|
|
1075
|
$value =~ s/\s*\z//; |
75
|
|
|
|
|
|
|
|
76
|
177
|
|
|
|
|
506
|
$current->{headers}{lc $name} = $value; |
77
|
177
|
100
|
|
|
|
449
|
if (lc $name eq 'content-disposition') { |
78
|
115
|
|
|
|
|
730
|
while ($value =~ m/;\s*([^=\s]+)\s*=\s*(?:"((?:\\[\\"]|[^"])*)"|([^";]*))/ig) { |
79
|
160
|
|
|
|
|
514
|
my ($field_name, $field_quoted, $field_unquoted) = ($1, $2, $3); |
80
|
160
|
50
|
66
|
|
|
458
|
next unless lc $field_name eq 'name' or lc $field_name eq 'filename'; |
81
|
160
|
100
|
|
|
|
465
|
$field_quoted =~ s/\\([\\"])/$1/g if defined $field_quoted; |
82
|
160
|
100
|
|
|
|
1005
|
$current->{lc $field_name} = defined $field_quoted ? $field_quoted : $field_unquoted; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
} |
86
|
116
|
50
|
|
|
|
290
|
next READER if $state{parsing_headers}; # read more to find end of headers |
87
|
|
|
|
|
|
|
} else { |
88
|
116
|
|
|
|
|
169
|
my $append = ''; |
89
|
116
|
|
|
|
|
207
|
my $next_pos = index $buffer, $next_boundary; |
90
|
116
|
|
|
|
|
282
|
my $end_pos = index $buffer, $end_boundary; |
91
|
116
|
100
|
66
|
|
|
510
|
if ($next_pos >= 0 and ($end_pos < 0 or $end_pos > $next_pos)) { |
|
|
50
|
66
|
|
|
|
|
|
|
0
|
|
|
|
|
|
92
|
97
|
100
|
66
|
|
|
314
|
if (!$state{parsed_optional_crlf} and $next_pos >= 2) { |
93
|
84
|
|
|
|
|
147
|
substr $buffer, 0, 2, ''; |
94
|
84
|
|
|
|
|
119
|
$next_pos -= 2; |
95
|
84
|
|
|
|
|
119
|
$state{parsed_optional_crlf} = 1; |
96
|
|
|
|
|
|
|
} |
97
|
97
|
|
|
|
|
196
|
$append = substr $buffer, 0, $next_pos, ''; |
98
|
97
|
|
|
|
|
199
|
substr $buffer, 0, length($next_boundary), ''; |
99
|
97
|
|
|
|
|
221
|
$state{parsing_body} = 0; |
100
|
97
|
|
|
|
|
134
|
$state{parsing_headers} = 1; |
101
|
|
|
|
|
|
|
} elsif ($end_pos >= 0) { |
102
|
19
|
50
|
33
|
|
|
100
|
if (!$state{parsed_optional_crlf} and $end_pos >= 2) { |
103
|
19
|
|
|
|
|
42
|
substr $buffer, 0, 2, ''; |
104
|
19
|
|
|
|
|
35
|
$end_pos -= 2; |
105
|
19
|
|
|
|
|
30
|
$state{parsed_optional_crlf} = 1; |
106
|
|
|
|
|
|
|
} |
107
|
19
|
|
|
|
|
47
|
$append = substr $buffer, 0, $end_pos; # no replacement, we're done here |
108
|
19
|
|
|
|
|
30
|
$state{parsing_body} = 0; |
109
|
19
|
|
|
|
|
36
|
$state{done} = 1; |
110
|
|
|
|
|
|
|
} elsif (length($buffer) > length($next_boundary) + 2) { |
111
|
0
|
0
|
|
|
|
0
|
if (!$state{parsed_optional_crlf}) { |
112
|
0
|
|
|
|
|
0
|
substr $buffer, 0, 2, ''; |
113
|
0
|
|
|
|
|
0
|
$state{parsed_optional_crlf} = 1; |
114
|
|
|
|
|
|
|
} |
115
|
0
|
|
|
|
|
0
|
$append = substr $buffer, 0, length($buffer) - length($next_boundary), ''; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
116
|
|
|
|
|
192
|
$current->{size} += length $append; |
119
|
116
|
100
|
100
|
|
|
329
|
unless (defined $current->{filename} and $options->{discard_files}) { |
120
|
103
|
100
|
100
|
|
|
373
|
if ($options->{parse_as_files} or (defined $current->{filename} and !defined $options->{parse_as_files})) { |
|
|
|
100
|
|
|
|
|
121
|
41
|
|
|
|
|
74
|
my $is_eof = !$state{parsing_body}; |
122
|
41
|
100
|
|
|
|
82
|
if (defined $options->{on_file_buffer}) { |
123
|
12
|
|
|
|
|
30
|
$options->{on_file_buffer}->($append, my $dummy = $current, $is_eof); |
124
|
|
|
|
|
|
|
} else { |
125
|
|
|
|
|
|
|
# create temp file even if empty |
126
|
29
|
50
|
|
|
|
86
|
unless (defined $current->{file}) { |
127
|
29
|
|
|
|
|
1247
|
require File::Temp; |
128
|
29
|
100
|
|
|
|
18484
|
$current->{file} = File::Temp->new(@{$options->{tempfile_args} || []}); |
|
29
|
|
|
|
|
218
|
|
129
|
29
|
|
|
|
|
12961
|
binmode $current->{file}; |
130
|
|
|
|
|
|
|
} |
131
|
29
|
|
|
|
|
52
|
print {$current->{file}} $append; |
|
29
|
|
|
|
|
305
|
|
132
|
29
|
50
|
|
|
|
102
|
if ($is_eof) { # finalize temp file |
133
|
29
|
|
|
|
|
903
|
$current->{file}->flush; |
134
|
29
|
|
|
|
|
346
|
seek $current->{file}, 0, 0; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
} else { |
138
|
62
|
50
|
|
|
|
163
|
$current->{content} = '' unless defined $current->{content}; |
139
|
62
|
|
|
|
|
120
|
$current->{content} .= $append; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
116
|
100
|
|
|
|
398
|
last READER if $state{done}; # end of multipart data |
144
|
97
|
50
|
|
|
|
191
|
next READER if $state{parsing_body}; # read more to find end of part |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# new part started |
147
|
97
|
|
|
|
|
488
|
push @parts, $current = {headers => {}, name => undef, filename => undef, size => 0}; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
} |
151
|
23
|
100
|
|
|
|
172
|
return undef unless $state{done}; |
152
|
|
|
|
|
|
|
|
153
|
20
|
|
|
|
|
133
|
return \@parts; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
1; |