line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::Content::MultiPart; |
2
|
60
|
|
|
60
|
|
954
|
use Mojo::Base 'Mojo::Content'; |
|
60
|
|
|
|
|
2549
|
|
|
60
|
|
|
|
|
500
|
|
3
|
|
|
|
|
|
|
|
4
|
60
|
|
|
60
|
|
483
|
use Mojo::Util qw(b64_encode); |
|
60
|
|
|
|
|
209
|
|
|
60
|
|
|
|
|
113981
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
has parts => sub { [] }; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
sub body_contains { |
9
|
39
|
|
|
39
|
1
|
119
|
my ($self, $chunk) = @_; |
10
|
39
|
|
100
|
|
|
68
|
($_->headers_contain($chunk) or $_->body_contains($chunk)) and return 1 for @{$self->parts}; |
|
39
|
|
100
|
|
|
119
|
|
11
|
25
|
|
|
|
|
109
|
return undef; |
12
|
|
|
|
|
|
|
} |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
sub body_size { |
15
|
24
|
|
|
24
|
1
|
58
|
my $self = shift; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Check for existing Content-Length header |
18
|
24
|
100
|
|
|
|
83
|
if (my $len = $self->headers->content_length) { return $len } |
|
2
|
|
|
|
|
15
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Calculate length of whole body |
21
|
22
|
|
|
|
|
94
|
my $len = my $boundary_len = length($self->build_boundary) + 6; |
22
|
22
|
|
|
|
|
59
|
$len += $_->header_size + $_->body_size + $boundary_len for @{$self->parts}; |
|
22
|
|
|
|
|
88
|
|
23
|
|
|
|
|
|
|
|
24
|
22
|
|
|
|
|
92
|
return $len; |
25
|
|
|
|
|
|
|
} |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub build_boundary { |
28
|
49
|
|
|
49
|
1
|
121
|
my $self = shift; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# Check for existing boundary |
31
|
49
|
|
|
|
|
121
|
my $boundary; |
32
|
49
|
100
|
|
|
|
186
|
return $boundary if defined($boundary = $self->boundary); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Generate and check boundary |
35
|
18
|
|
|
|
|
57
|
my $size = 1; |
36
|
18
|
|
|
|
|
47
|
do { |
37
|
18
|
|
|
|
|
267
|
$boundary = b64_encode join('', map chr(rand 256), 1 .. $size++ * 3); |
38
|
18
|
|
|
|
|
187
|
$boundary =~ s/\W/X/g; |
39
|
|
|
|
|
|
|
} while $self->body_contains($boundary); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# Add boundary to Content-Type header |
42
|
18
|
|
|
|
|
80
|
my $headers = $self->headers; |
43
|
18
|
|
|
|
|
72
|
my ($before, $after) = ('multipart/mixed', ''); |
44
|
18
|
100
|
100
|
|
|
76
|
($before, $after) = ($1, $2) if ($headers->content_type // '') =~ m!^(.*multipart/[^;]+)(.*)$!; |
45
|
18
|
|
|
|
|
139
|
$headers->content_type("$before; boundary=$boundary$after"); |
46
|
|
|
|
|
|
|
|
47
|
18
|
|
|
|
|
90
|
return $boundary; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub clone { |
51
|
2
|
|
|
2
|
1
|
6
|
my $self = shift; |
52
|
2
|
50
|
|
|
|
25
|
return undef unless my $clone = $self->SUPER::clone(); |
53
|
2
|
|
|
|
|
7
|
return $clone->parts($self->parts); |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub get_body_chunk { |
57
|
184
|
|
|
184
|
1
|
364
|
my ($self, $offset) = @_; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Body generator |
60
|
184
|
50
|
|
|
|
479
|
return $self->generate_body_chunk($offset) if $self->is_dynamic; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# First boundary |
63
|
184
|
|
66
|
|
|
595
|
my $boundary = $self->{boundary} //= $self->build_boundary; |
64
|
184
|
|
|
|
|
388
|
my $boundary_len = length($boundary) + 6; |
65
|
184
|
|
|
|
|
361
|
my $len = $boundary_len - 2; |
66
|
184
|
100
|
|
|
|
506
|
return substr "--$boundary\x0d\x0a", $offset if $len > $offset; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Skip parts that have already been processed |
69
|
162
|
|
|
|
|
298
|
my $start = 0; |
70
|
162
|
100
|
100
|
|
|
615
|
($len, $start) = ($self->{last_len}, $self->{last_part} + 1) if $self->{offset} && $offset > $self->{offset}; |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# Prepare content part by part |
73
|
162
|
|
|
|
|
397
|
my $parts = $self->parts; |
74
|
162
|
|
|
|
|
511
|
for (my $i = $start; $i < @$parts; $i++) { |
75
|
192
|
|
|
|
|
359
|
my $part = $parts->[$i]; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Headers |
78
|
192
|
|
|
|
|
450
|
my $header_len = $part->header_size; |
79
|
192
|
100
|
|
|
|
543
|
return $part->get_header_chunk($offset - $len) if ($len + $header_len) > $offset; |
80
|
146
|
|
|
|
|
228
|
$len += $header_len; |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Content |
83
|
146
|
|
|
|
|
411
|
my $content_len = $part->body_size; |
84
|
146
|
100
|
|
|
|
458
|
return $part->get_body_chunk($offset - $len) if ($len + $content_len) > $offset; |
85
|
101
|
|
|
|
|
172
|
$len += $content_len; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Boundary |
88
|
101
|
100
|
|
|
|
251
|
if ($#$parts == $i) { |
89
|
47
|
|
|
|
|
106
|
$boundary .= '--'; |
90
|
47
|
|
|
|
|
101
|
$boundary_len += 2; |
91
|
|
|
|
|
|
|
} |
92
|
101
|
100
|
|
|
|
418
|
return substr "\x0d\x0a--$boundary\x0d\x0a", $offset - $len if ($len + $boundary_len) > $offset; |
93
|
52
|
|
|
|
|
100
|
$len += $boundary_len; |
94
|
|
|
|
|
|
|
|
95
|
52
|
|
|
|
|
115
|
@{$self}{qw(last_len last_part offset)} = ($len, $i, $offset); |
|
52
|
|
|
|
|
337
|
|
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
155
|
|
|
155
|
1
|
752
|
sub is_multipart {1} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub new { |
102
|
81
|
|
|
81
|
1
|
2995
|
my $self = shift->SUPER::new(@_); |
103
|
81
|
|
|
|
|
479
|
$self->on(read => \&_read); |
104
|
81
|
|
|
|
|
286
|
return $self; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub _parse_multipart_body { |
108
|
275
|
|
|
275
|
|
556
|
my ($self, $boundary) = @_; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Whole part in buffer |
111
|
275
|
|
|
|
|
930
|
my $pos = index $self->{multipart}, "\x0d\x0a--$boundary"; |
112
|
275
|
100
|
|
|
|
630
|
if ($pos < 0) { |
113
|
176
|
|
|
|
|
345
|
my $len = length($self->{multipart}) - (length($boundary) + 8); |
114
|
176
|
100
|
|
|
|
499
|
return undef unless $len > 0; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Store chunk |
117
|
116
|
|
|
|
|
696
|
my $chunk = substr $self->{multipart}, 0, $len, ''; |
118
|
116
|
|
|
|
|
309
|
$self->parts->[-1] = $self->parts->[-1]->parse($chunk); |
119
|
116
|
|
|
|
|
379
|
return undef; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# Store chunk |
123
|
99
|
|
|
|
|
313
|
my $chunk = substr $self->{multipart}, 0, $pos, ''; |
124
|
99
|
|
|
|
|
326
|
$self->parts->[-1] = $self->parts->[-1]->parse($chunk); |
125
|
99
|
|
|
|
|
580
|
return !!($self->{multi_state} = 'multipart_boundary'); |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub _parse_multipart_boundary { |
129
|
144
|
|
|
144
|
|
314
|
my ($self, $boundary) = @_; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# Boundary begins |
132
|
144
|
100
|
|
|
|
556
|
if ((index $self->{multipart}, "\x0d\x0a--$boundary\x0d\x0a") == 0) { |
133
|
100
|
|
|
|
|
276
|
substr $self->{multipart}, 0, length($boundary) + 6, ''; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# New part |
136
|
100
|
|
|
|
|
322
|
my $part = Mojo::Content::Single->new(relaxed => 1); |
137
|
100
|
|
|
|
|
375
|
$self->emit(part => $part); |
138
|
100
|
|
|
|
|
161
|
push @{$self->parts}, $part; |
|
100
|
|
|
|
|
285
|
|
139
|
100
|
|
|
|
|
521
|
return !!($self->{multi_state} = 'multipart_body'); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# Boundary ends |
143
|
44
|
|
|
|
|
142
|
my $end = "\x0d\x0a--$boundary--"; |
144
|
44
|
100
|
|
|
|
180
|
if ((index $self->{multipart}, $end) == 0) { |
145
|
38
|
|
|
|
|
105
|
substr $self->{multipart}, 0, length $end, ''; |
146
|
38
|
|
|
|
|
83
|
$self->{multi_state} = 'finished'; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
44
|
|
|
|
|
136
|
return undef; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub _parse_multipart_preamble { |
153
|
57
|
|
|
57
|
|
143
|
my ($self, $boundary) = @_; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# No boundary yet |
156
|
57
|
100
|
|
|
|
313
|
return undef if (my $pos = index $self->{multipart}, "--$boundary") < 0; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# Replace preamble with carriage return and line feed |
159
|
40
|
|
|
|
|
109
|
substr $self->{multipart}, 0, $pos, "\x0d\x0a"; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Parse boundary |
162
|
40
|
|
|
|
|
211
|
return !!($self->{multi_state} = 'multipart_boundary'); |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
sub _read { |
166
|
239
|
|
|
239
|
|
481
|
my ($self, $chunk) = @_; |
167
|
|
|
|
|
|
|
|
168
|
239
|
|
|
|
|
1281
|
$self->{multipart} .= $chunk; |
169
|
239
|
|
|
|
|
630
|
my $boundary = $self->boundary; |
170
|
239
|
|
100
|
|
|
1218
|
until (($self->{multi_state} //= 'multipart_preamble') eq 'finished') { |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# Preamble |
173
|
476
|
100
|
|
|
|
1574
|
if ($self->{multi_state} eq 'multipart_preamble') { last unless $self->_parse_multipart_preamble($boundary) } |
|
57
|
100
|
|
|
|
173
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Boundary |
176
|
144
|
100
|
|
|
|
364
|
elsif ($self->{multi_state} eq 'multipart_boundary') { last unless $self->_parse_multipart_boundary($boundary) } |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Body |
179
|
275
|
100
|
|
|
|
651
|
elsif ($self->{multi_state} eq 'multipart_body') { last unless $self->_parse_multipart_body($boundary) } |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# Check buffer size |
183
|
239
|
100
|
50
|
|
|
920
|
@$self{qw(state limit)} = ('finished', 1) if length($self->{multipart} // '') > $self->max_buffer_size; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
1; |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=encoding utf8 |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=head1 NAME |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Mojo::Content::MultiPart - HTTP multipart content |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head1 SYNOPSIS |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
use Mojo::Content::MultiPart; |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
my $multi = Mojo::Content::MultiPart->new; |
199
|
|
|
|
|
|
|
$multi->parse('Content-Type: multipart/mixed; boundary=---foobar'); |
200
|
|
|
|
|
|
|
my $single = $multi->parts->[4]; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head1 DESCRIPTION |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
L is a container for HTTP multipart content, based on L
|
205
|
|
|
|
|
|
|
7230|https://tools.ietf.org/html/rfc7230>, L and L
|
206
|
|
|
|
|
|
|
2388|https://tools.ietf.org/html/rfc2388>. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head1 EVENTS |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
L inherits all events from L and can emit the following new ones. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head2 part |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
$multi->on(part => sub ($multi, $single) {...}); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Emitted when a new L part starts. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
$multi->on(part => sub ($multi, $single) { |
219
|
|
|
|
|
|
|
return unless $single->headers->content_disposition =~ /name="([^"]+)"/; |
220
|
|
|
|
|
|
|
say "Field: $1"; |
221
|
|
|
|
|
|
|
}); |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
L inherits all attributes from L and implements the following new ones. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=head2 parts |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
my $parts = $multi->parts; |
230
|
|
|
|
|
|
|
$multi = $multi->parts([Mojo::Content::Single->new]); |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Content parts embedded in this multipart content, usually L objects. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head1 METHODS |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
L inherits all methods from L and implements the following new ones. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head2 body_contains |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
my $bool = $multi->body_contains('foobarbaz'); |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
Check if content parts contain a specific string. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=head2 body_size |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
my $size = $multi->body_size; |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
Content size in bytes. |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=head2 build_boundary |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
my $boundary = $multi->build_boundary; |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Generate a suitable boundary for content and add it to C header. |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=head2 clone |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
my $clone = $multi->clone; |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Return a new L object cloned from this content if possible, otherwise return C. |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head2 get_body_chunk |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
my $bytes = $multi->get_body_chunk(0); |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
Get a chunk of content starting from a specific position. Note that it might not be possible to get the same chunk |
267
|
|
|
|
|
|
|
twice if content was generated dynamically. |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=head2 is_multipart |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
my $bool = $multi->is_multipart; |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
True, this is a L object. |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=head2 new |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
my $multi = Mojo::Content::MultiPart->new; |
278
|
|
|
|
|
|
|
my $multi |
279
|
|
|
|
|
|
|
= Mojo::Content::MultiPart->new(parts => [Mojo::Content::Single->new]); |
280
|
|
|
|
|
|
|
my $multi |
281
|
|
|
|
|
|
|
= Mojo::Content::MultiPart->new({parts => [Mojo::Content::Single->new]}); |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
Construct a new L object and subscribe to event L with default content |
284
|
|
|
|
|
|
|
parser. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=head1 SEE ALSO |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
L, L, L. |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=cut |