line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTTP::RangeSaver; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
96779
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
116
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION='0.01'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
HTTP::RangeSaver - handle partial content HTTP responses |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 SYNOPSIS |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
use LWP; |
14
|
|
|
|
|
|
|
use HTTP::RangeSaver; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
open(my $fh,'+<','example.mpeg') || die $!; |
17
|
|
|
|
|
|
|
my $req=new HTTP::Request |
18
|
|
|
|
|
|
|
(GET => 'http://www.example.com/example.mpeg'); |
19
|
|
|
|
|
|
|
$req->header(Range => 'bytes='.(-s $fh).'-'); |
20
|
|
|
|
|
|
|
my $saver=new HTTP::RangeSaver($fh); |
21
|
|
|
|
|
|
|
my $ua=new LWP::UserAgent; |
22
|
|
|
|
|
|
|
my $resp=$ua->request($req,$saver->get_callback()); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 DESCRIPTION |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
HTTP::RangeSaver is a helper class for updating an existing file with |
27
|
|
|
|
|
|
|
data from a partial content HTTP response. It understands both of the |
28
|
|
|
|
|
|
|
partial content formats defined in RFC 2616 (a single Content-Range |
29
|
|
|
|
|
|
|
header or a multipart/byteranges Content-Type). For convenience, it |
30
|
|
|
|
|
|
|
also handles complete content HTTP responses (status 200 or 203 rather |
31
|
|
|
|
|
|
|
than 206). |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=cut |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
use fields |
36
|
1
|
|
|
|
|
9
|
qw(fh delta truncate |
37
|
|
|
|
|
|
|
require_partial require_length require_resource |
38
|
|
|
|
|
|
|
methods expected buffer start_boundary end_boundary |
39
|
1
|
|
|
1
|
|
1918
|
type length written partheaders ranges); |
|
1
|
|
|
|
|
2360
|
|
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
use Symbol |
42
|
1
|
|
|
1
|
|
3834
|
qw(qualify_to_ref); |
|
1
|
|
|
|
|
2476
|
|
|
1
|
|
|
|
|
117
|
|
43
|
|
|
|
|
|
|
|
44
|
1
|
|
|
1
|
|
13
|
use HTTP::Headers; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
35
|
|
45
|
|
|
|
|
|
|
use HTTP::Headers::Util |
46
|
1
|
|
|
1
|
|
2006
|
qw(split_header_words); |
|
1
|
|
|
|
|
1302
|
|
|
1
|
|
|
|
|
3659
|
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=over |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=item my $saver=HTTP::RangeSaver->new($fh,%options); |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
$fh is an open filehandle. It must allow seeking and writing. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
%options is a list of key/value pairs for modifying the saver's |
57
|
|
|
|
|
|
|
behaviour. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=over |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=item truncate |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
Pass a true value to make the saver truncate the file to match the full |
64
|
|
|
|
|
|
|
length of the returned entity. Ignored if the server doesn't report a |
65
|
|
|
|
|
|
|
definite length. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=item require_length |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
Pass a true value to make the saver die if the server doesn't report a |
70
|
|
|
|
|
|
|
definite full length for the returned entity. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=item require_partial |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Pass a true value to make the saver die if the server returns a complete |
75
|
|
|
|
|
|
|
entity rather than a partial one. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=item require_resource |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Pass a true value to make the saver die if the server returns an entity |
80
|
|
|
|
|
|
|
that doesn't represent the requested resource (i.e. a 2xx status code |
81
|
|
|
|
|
|
|
other than 200, 203, or 206). This should never happen for a GET |
82
|
|
|
|
|
|
|
request. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=item delta |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
A adjustment to be applied to all file offsets in the destination file. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=back |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=back |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=cut |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
sub new |
95
|
|
|
|
|
|
|
{ |
96
|
13
|
|
|
13
|
1
|
5822
|
my($class,$fh,%params)=@_; |
97
|
13
|
|
|
|
|
17
|
my __PACKAGE__ $self; |
98
|
|
|
|
|
|
|
|
99
|
13
|
|
|
|
|
38
|
$self=fields::new($class); |
100
|
13
|
|
|
|
|
5232
|
$self->{fh}=qualify_to_ref($fh,caller()); |
101
|
13
|
50
|
|
|
|
127
|
if (exists($params{delta})) { |
102
|
0
|
|
|
|
|
0
|
$self->{delta}=int($params{delta}); |
103
|
|
|
|
|
|
|
} else { |
104
|
13
|
|
|
|
|
25
|
$self->{delta}=0; |
105
|
|
|
|
|
|
|
} |
106
|
13
|
|
50
|
|
|
40
|
$self->{truncate}=$params{truncate} && 1; |
107
|
13
|
|
100
|
|
|
36
|
$self->{require_partial}=$params{require_partial} && 1; |
108
|
13
|
|
100
|
|
|
40
|
$self->{require_length}=$params{require_length} && 1; |
109
|
13
|
|
100
|
|
|
44
|
$self->{require_resource}=$params{require_resource} && 1; |
110
|
13
|
|
|
|
|
27
|
$self->{methods}=['init']; |
111
|
13
|
|
|
|
|
24
|
$self->{written}=0; |
112
|
13
|
|
|
|
|
33
|
return $self; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head1 METHODS |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=over |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item my $callback=$saver->get_callback(); |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Returns a closure suitable for passing as the callback function argument |
122
|
|
|
|
|
|
|
to L's request methods. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=cut |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
sub get_callback |
127
|
|
|
|
|
|
|
{ |
128
|
0
|
|
|
0
|
1
|
0
|
my __PACKAGE__ $self=shift(@_); |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
return sub |
131
|
|
|
|
|
|
|
{ |
132
|
0
|
|
|
0
|
|
0
|
$self->process(@_); |
133
|
0
|
|
|
|
|
0
|
}; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item $saver->process($data,$response,$protocol); |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=item $saver->process(@_); # if called directly from the callback function |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
Call this method from your callback function if you want to do more than |
141
|
|
|
|
|
|
|
just save the incoming data (e.g. display a progress indicator). |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=cut |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub process |
146
|
|
|
|
|
|
|
{ |
147
|
219
|
|
|
219
|
1
|
1505
|
my __PACKAGE__ $self=shift(@_); |
148
|
|
|
|
|
|
|
|
149
|
219
|
|
|
|
|
296
|
for my $data (shift(@_)) { |
150
|
219
|
|
|
|
|
225
|
my($len,$methods); |
151
|
|
|
|
|
|
|
|
152
|
219
|
|
|
|
|
338
|
$len=length($data); |
153
|
219
|
|
|
|
|
276
|
$methods=$self->{methods}; |
154
|
219
|
|
|
|
|
444
|
for (my $off=0; $off<$len; ) { |
155
|
241
|
|
|
|
|
224
|
my($method); |
156
|
|
|
|
|
|
|
|
157
|
241
|
|
|
|
|
281
|
$method=$methods->[-1]; |
158
|
241
|
100
|
|
|
|
377
|
if ($off) { |
159
|
13
|
|
|
|
|
40
|
$off+=$self->$method(substr($data,$off),@_); |
160
|
|
|
|
|
|
|
} else { |
161
|
228
|
|
|
|
|
467
|
$off+=$self->$method($data,@_); |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item $saver->get_length(); |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
Returns the total length of the returned entity, or an undefined value |
170
|
|
|
|
|
|
|
if the length is indefinite (or hasn't arrived yet). |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=cut |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub get_length |
175
|
|
|
|
|
|
|
{ |
176
|
7
|
|
|
7
|
1
|
956
|
my __PACKAGE__ $self=shift(@_); |
177
|
7
|
|
|
|
|
8
|
my($length); |
178
|
|
|
|
|
|
|
|
179
|
7
|
|
|
|
|
12
|
$length=$self->{length}; |
180
|
7
|
50
|
33
|
|
|
39
|
undef $length if defined($length) && $length eq '*'; |
181
|
7
|
|
|
|
|
27
|
return $length; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item $saver->get_type(); |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Returns the MIME type of the returned entity, from either the |
187
|
|
|
|
|
|
|
Content-Type header of the response or the first part header of a |
188
|
|
|
|
|
|
|
multipart response. Returns undef if this information hasn't arrived |
189
|
|
|
|
|
|
|
yet. |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=cut |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub get_type |
194
|
|
|
|
|
|
|
{ |
195
|
8
|
|
|
8
|
1
|
482
|
my __PACKAGE__ $self=shift(@_); |
196
|
|
|
|
|
|
|
|
197
|
8
|
|
|
|
|
31
|
return $self->{type}; |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=item $saver->get_written(); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
Returns the total number of bytes written by the saver (so far). Useful |
203
|
|
|
|
|
|
|
for displaying a simple progress indicator. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=cut |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub get_written |
208
|
|
|
|
|
|
|
{ |
209
|
9
|
|
|
9
|
1
|
506
|
my __PACKAGE__ $self=shift(@_); |
210
|
|
|
|
|
|
|
|
211
|
9
|
|
|
|
|
36
|
return $self->{written}; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=item $saver->get_ranges(); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Returns a reference to an array of ranges written by the saver (so far). |
217
|
|
|
|
|
|
|
Each range is represented by a reference to a two-element array containing |
218
|
|
|
|
|
|
|
the first and last byte numbers (ignoring the delta parameter) with the |
219
|
|
|
|
|
|
|
same semantics as in the HTTP protocol. Useful for displaying a complex |
220
|
|
|
|
|
|
|
progress indicator. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=cut |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub get_ranges |
225
|
|
|
|
|
|
|
{ |
226
|
9
|
|
|
9
|
1
|
448
|
my __PACKAGE__ $self=shift(@_); |
227
|
|
|
|
|
|
|
|
228
|
9
|
100
|
|
|
|
10
|
return [map([@{$_}],grep($_->[1]>=$_->[0],@{$self->{ranges} || []}))]; |
|
11
|
|
|
|
|
40
|
|
|
9
|
|
|
|
|
48
|
|
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=item $saver->get_partheaders(); |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Returns a reference to an array of HTTP::Headers objects, one for each |
234
|
|
|
|
|
|
|
part (seen so far) of a multipart response. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=cut |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub get_partheaders |
239
|
|
|
|
|
|
|
{ |
240
|
0
|
|
|
0
|
1
|
0
|
my __PACKAGE__ $self=shift(@_); |
241
|
|
|
|
|
|
|
|
242
|
0
|
0
|
|
|
|
0
|
return [@{$self->{partheaders} || []}]; |
|
0
|
|
|
|
|
0
|
|
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=item $saver->is_incomplete(); |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
Returns true if the saver hasn't seen a complete response yet. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub is_incomplete |
252
|
|
|
|
|
|
|
{ |
253
|
8
|
|
|
8
|
1
|
347
|
my __PACKAGE__ $self=shift(@_); |
254
|
8
|
|
|
|
|
9
|
my($method); |
255
|
|
|
|
|
|
|
|
256
|
8
|
|
|
|
|
14
|
$method=$self->{methods}->[-1]; |
257
|
8
|
|
100
|
|
|
43
|
return $method ne 'ignore' |
258
|
|
|
|
|
|
|
&& $method ne 'indefinite'; |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=back |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=cut |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub ignore |
266
|
|
|
|
|
|
|
{ |
267
|
10
|
|
|
10
|
0
|
12
|
my __PACKAGE__ $self=shift(@_); |
268
|
|
|
|
|
|
|
|
269
|
10
|
|
|
|
|
18
|
for my $data (shift(@_)) { |
270
|
10
|
|
|
|
|
11
|
my($resp)=@_; |
271
|
|
|
|
|
|
|
|
272
|
10
|
|
|
|
|
30
|
$resp->add_content($data); |
273
|
10
|
|
|
|
|
195
|
return length($data); |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
sub indefinite |
278
|
|
|
|
|
|
|
{ |
279
|
1
|
|
|
1
|
0
|
2
|
my __PACKAGE__ $self=shift(@_); |
280
|
|
|
|
|
|
|
|
281
|
1
|
|
|
|
|
3
|
for my $data (shift(@_)) { |
282
|
1
|
|
|
|
|
1
|
my($len); |
283
|
1
|
|
|
|
|
3
|
local($\); |
284
|
|
|
|
|
|
|
|
285
|
1
|
|
|
|
|
2
|
$len=length($data); |
286
|
1
|
50
|
|
|
|
2
|
print {$self->{fh}} $data |
|
1
|
|
|
|
|
28
|
|
287
|
|
|
|
|
|
|
or die "print error: $!"; |
288
|
1
|
|
|
|
|
2
|
$self->{written}+=$len; |
289
|
1
|
|
|
|
|
3
|
$self->{ranges}->[-1]->[1]+=$len; |
290
|
1
|
|
|
|
|
7
|
return $len; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
sub definite |
295
|
|
|
|
|
|
|
{ |
296
|
28
|
|
|
28
|
0
|
33
|
my __PACKAGE__ $self=shift(@_); |
297
|
|
|
|
|
|
|
|
298
|
28
|
|
|
|
|
45
|
for my $data (shift(@_)) { |
299
|
28
|
|
|
|
|
28
|
my($len,$expected); |
300
|
28
|
|
|
|
|
59
|
local($\); |
301
|
|
|
|
|
|
|
|
302
|
28
|
|
|
|
|
37
|
$len=length($data); |
303
|
28
|
|
|
|
|
43
|
$expected=$self->{expected}; |
304
|
28
|
100
|
|
|
|
48
|
if ($len>$expected) { |
305
|
4
|
|
|
|
|
6
|
$len=$expected; |
306
|
4
|
|
|
|
|
5
|
print {$self->{fh}} substr($data,0,$len); |
|
4
|
|
|
|
|
16
|
|
307
|
|
|
|
|
|
|
} else { |
308
|
24
|
|
|
|
|
27
|
print {$self->{fh}} $data; |
|
24
|
|
|
|
|
58
|
|
309
|
|
|
|
|
|
|
} |
310
|
28
|
|
|
|
|
54
|
$self->{ranges}->[-1]->[1]+=$len; |
311
|
28
|
|
|
|
|
30
|
$self->{written}+=$len; |
312
|
28
|
|
|
|
|
34
|
$expected-=$len; |
313
|
28
|
|
|
|
|
35
|
$self->{expected}=$expected; |
314
|
28
|
100
|
|
|
|
50
|
if (!$expected) { |
315
|
7
|
|
|
|
|
8
|
pop(@{$self->{methods}}); |
|
7
|
|
|
|
|
13
|
|
316
|
|
|
|
|
|
|
} |
317
|
28
|
|
|
|
|
149
|
return $len; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub headers |
322
|
|
|
|
|
|
|
{ |
323
|
122
|
|
|
122
|
0
|
136
|
my __PACKAGE__ $self=shift(@_); |
324
|
|
|
|
|
|
|
|
325
|
122
|
|
|
|
|
150
|
for my $data (shift(@_)) { |
326
|
122
|
|
|
|
|
183
|
for my $buffer ($self->{buffer}) { |
327
|
122
|
|
|
|
|
112
|
my($len,$buflen,@lines,$headers,$content_range); |
328
|
|
|
|
|
|
|
|
329
|
122
|
|
|
|
|
148
|
$len=length($data); |
330
|
122
|
|
|
|
|
116
|
$buflen=length($buffer); |
331
|
122
|
|
|
|
|
185
|
$buffer.=$data; |
332
|
122
|
100
|
|
|
|
797
|
$buffer =~ /\x0D?\x0A\x0D?\x0A/ |
333
|
|
|
|
|
|
|
or return $len; |
334
|
6
|
|
|
|
|
14
|
$len=$+[0]-$buflen; |
335
|
6
|
|
|
|
|
22
|
substr($buffer,$-[0])=''; |
336
|
|
|
|
|
|
|
# why is there no HTTP::Headers::parse anyway? |
337
|
6
|
|
|
|
|
22
|
$buffer =~ s/\x0D?\x0A\s+/ /g; |
338
|
6
|
|
|
|
|
30
|
@lines=split(/\x0D?\x0A/,$buffer); |
339
|
6
|
|
|
|
|
9
|
$buffer=''; |
340
|
6
|
|
|
|
|
11
|
$self->{methods}->[-1]='boundary'; |
341
|
6
|
|
|
|
|
26
|
$headers=new HTTP::Headers; |
342
|
6
|
|
|
|
|
48
|
foreach my $line (@lines) { |
343
|
12
|
|
|
|
|
134
|
my($name,$value); |
344
|
|
|
|
|
|
|
|
345
|
12
|
|
|
|
|
53
|
$line =~ s/\s+$//; |
346
|
12
|
50
|
|
|
|
74
|
($name,$value)=($line =~ /^([^\s:]+)\s*:\s*(.*)$/) |
347
|
|
|
|
|
|
|
or die "Malformed part headers"; |
348
|
12
|
|
|
|
|
41
|
$headers->push_header($name,$value); |
349
|
|
|
|
|
|
|
} |
350
|
6
|
|
|
|
|
108
|
push(@{$self->{partheaders}},$headers); |
|
6
|
|
|
|
|
14
|
|
351
|
6
|
|
|
|
|
18
|
$content_range=$headers->header('Content-Range'); |
352
|
6
|
50
|
|
|
|
160
|
defined($content_range) |
353
|
|
|
|
|
|
|
or die "Content-Range missing from part headers"; |
354
|
6
|
|
|
|
|
14
|
$self->content_range($content_range); |
355
|
6
|
100
|
|
|
|
22
|
if (!defined($self->{type})) { |
356
|
3
|
|
|
|
|
8
|
$self->{type}=$headers->header('Content-Type'); |
357
|
|
|
|
|
|
|
} |
358
|
6
|
|
|
|
|
110
|
return $len; |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
sub boundary |
364
|
|
|
|
|
|
|
{ |
365
|
67
|
|
|
67
|
0
|
99
|
my __PACKAGE__ $self=shift(@_); |
366
|
|
|
|
|
|
|
|
367
|
67
|
|
|
|
|
87
|
for my $data (shift(@_)) { |
368
|
67
|
|
|
|
|
102
|
for my $buffer ($self->{buffer}) { |
369
|
67
|
|
|
|
|
71
|
my($len,$buflen,$pos,$methods); |
370
|
|
|
|
|
|
|
|
371
|
67
|
|
|
|
|
78
|
$methods=$self->{methods}; |
372
|
67
|
|
|
|
|
82
|
$len=length($data); |
373
|
67
|
|
|
|
|
64
|
$buflen=length($buffer); |
374
|
67
|
|
|
|
|
92
|
$buffer.=$data; |
375
|
67
|
100
|
|
|
|
361
|
if ($buffer =~ $self->{start_boundary}) { |
|
|
100
|
|
|
|
|
|
376
|
6
|
|
|
|
|
16
|
$len=$+[0]-$buflen; |
377
|
6
|
|
|
|
|
12
|
$buffer=''; |
378
|
6
|
|
|
|
|
10
|
$methods->[-1]='headers'; |
379
|
|
|
|
|
|
|
} elsif ($buffer =~ $self->{end_boundary}) { |
380
|
2
|
|
|
|
|
5
|
$len=$+[0]-$buflen; |
381
|
2
|
|
|
|
|
3
|
$buffer=''; |
382
|
2
|
|
|
|
|
4
|
pop(@{$methods}); |
|
2
|
|
|
|
|
3
|
|
383
|
|
|
|
|
|
|
} |
384
|
67
|
|
|
|
|
1303
|
return $len; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
sub init |
390
|
|
|
|
|
|
|
{ |
391
|
13
|
|
|
13
|
0
|
15
|
my __PACKAGE__ $self=shift(@_); |
392
|
13
|
|
|
|
|
19
|
my(undef,$resp)=@_; |
393
|
13
|
|
|
|
|
13
|
my($code,$fh,$delta,$methods,$content_type); |
394
|
|
|
|
|
|
|
|
395
|
13
|
|
|
|
|
17
|
$methods=$self->{methods}; |
396
|
13
|
|
|
|
|
24
|
$methods->[-1]='ignore'; |
397
|
13
|
|
|
|
|
16
|
$fh=$self->{fh}; |
398
|
13
|
|
|
|
|
16
|
$delta=$self->{delta}; |
399
|
13
|
|
|
|
|
34
|
$code=$resp->code(); |
400
|
13
|
|
|
|
|
134
|
$content_type=$resp->header('Content-Type'); |
401
|
13
|
100
|
66
|
|
|
523
|
if ($code==206) { |
|
|
100
|
|
|
|
|
|
402
|
6
|
100
|
|
|
|
17
|
if (defined(my $content_range=$resp->header('Content-Range'))) { |
|
|
50
|
|
|
|
|
|
403
|
3
|
|
|
|
|
105
|
$self->content_range($content_range); |
404
|
2
|
|
|
|
|
5
|
$self->{type}=$content_type; |
405
|
|
|
|
|
|
|
} elsif (defined($content_type)) { |
406
|
3
|
|
|
|
|
101
|
my($split,$ct,%params,$boundary); |
407
|
|
|
|
|
|
|
|
408
|
3
|
|
|
|
|
11
|
($split)=split_header_words($content_type); |
409
|
3
|
|
|
|
|
168
|
($ct,undef,%params)=@{$split}; |
|
3
|
|
|
|
|
11
|
|
410
|
3
|
50
|
33
|
|
|
21
|
unless ($ct eq 'multipart/byteranges' |
411
|
|
|
|
|
|
|
&& defined($boundary=$params{boundary})) { |
412
|
0
|
|
|
|
|
0
|
die "Unsupported Content-Type header"; |
413
|
|
|
|
|
|
|
} |
414
|
3
|
|
|
|
|
7
|
undef $self->{type}; |
415
|
3
|
|
|
|
|
4
|
push(@{$methods},'boundary'); |
|
3
|
|
|
|
|
6
|
|
416
|
3
|
|
|
|
|
7
|
$self->{buffer}=''; |
417
|
3
|
|
|
|
|
38
|
$self->{start_boundary}=qr/\x0D?\x0A--\Q$boundary\E\x0D?\x0A/; |
418
|
3
|
|
|
|
|
27
|
$self->{end_boundary}=qr/\x0D?\x0A--\Q$boundary\E--\x0D?\x0A/; |
419
|
3
|
|
|
|
|
14
|
$self->{partheaders}=[]; |
420
|
|
|
|
|
|
|
} else { |
421
|
0
|
|
|
|
|
0
|
die "Unsupported kind of partial content"; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
} elsif ($code==200 || $code==203) { |
424
|
5
|
100
|
|
|
|
14
|
if ($self->{require_partial}) { |
425
|
1
|
|
|
|
|
14
|
die "No partial content returned"; |
426
|
|
|
|
|
|
|
} |
427
|
4
|
100
|
|
|
|
11
|
if (defined(my $content_length=$resp->header('Content-Length'))) { |
428
|
2
|
|
|
|
|
66
|
$self->{length}=$content_length; |
429
|
2
|
50
|
|
|
|
8
|
if ($self->{truncate}) { |
430
|
0
|
0
|
|
|
|
0
|
truncate($fh,$content_length+$delta) |
431
|
|
|
|
|
|
|
or die "truncate error: $!"; |
432
|
|
|
|
|
|
|
} |
433
|
2
|
|
|
|
|
3
|
$self->{expected}=$content_length; |
434
|
2
|
50
|
|
|
|
6
|
if ($content_length>0) { |
435
|
2
|
|
|
|
|
2
|
push(@{$methods},'definite'); |
|
2
|
|
|
|
|
5
|
|
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
} else { |
438
|
2
|
100
|
|
|
|
69
|
if ($self->{require_length}) { |
439
|
1
|
|
|
|
|
9
|
die "No length returned"; |
440
|
|
|
|
|
|
|
} |
441
|
1
|
|
|
|
|
3
|
$methods->[-1]='indefinite'; |
442
|
|
|
|
|
|
|
} |
443
|
3
|
50
|
|
|
|
9
|
seek($fh,$delta,0) |
444
|
|
|
|
|
|
|
or die "seek error: $!"; |
445
|
3
|
50
|
|
|
|
9
|
if ($methods->[-1] ne 'ignore') { |
446
|
3
|
|
|
|
|
3
|
push(@{$self->{ranges}},[0,-1]); |
|
3
|
|
|
|
|
10
|
|
447
|
|
|
|
|
|
|
} |
448
|
3
|
|
|
|
|
7
|
$self->{type}=$content_type; |
449
|
|
|
|
|
|
|
} else { |
450
|
2
|
100
|
|
|
|
7
|
if ($self->{require_resource}) { |
451
|
1
|
|
|
|
|
8
|
die "No resource returned"; |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
} |
454
|
9
|
|
|
|
|
58
|
return 0; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
my $content_range_re=qr#^\s*bytes\s+(\d+)-(\d+)/(\d+|\*)#; |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
sub content_range |
460
|
|
|
|
|
|
|
{ |
461
|
9
|
|
|
9
|
0
|
13
|
my __PACKAGE__ $self=shift(@_); |
462
|
9
|
|
|
|
|
12
|
my($content_range)=@_; |
463
|
9
|
|
|
|
|
12
|
my($first,$last,$length,$fh,$delta); |
464
|
|
|
|
|
|
|
|
465
|
9
|
50
|
33
|
|
|
152
|
unless (($first,$last,$length)=($content_range =~ $content_range_re) |
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
466
|
|
|
|
|
|
|
and $last>=$first |
467
|
|
|
|
|
|
|
and $length eq '*' || $last<$length) { |
468
|
0
|
|
|
|
|
0
|
die "Malformed Content-Range header ($content_range)"; |
469
|
|
|
|
|
|
|
} |
470
|
9
|
|
|
|
|
22
|
$fh=$self->{fh}; |
471
|
9
|
|
|
|
|
16
|
$delta=$self->{delta}; |
472
|
9
|
100
|
|
|
|
23
|
if (!defined($self->{length})) { |
473
|
6
|
|
|
|
|
12
|
$self->{length}=$length; |
474
|
6
|
100
|
|
|
|
14
|
if ($length eq '*') { |
475
|
1
|
50
|
|
|
|
5
|
if ($self->{require_length}) { |
476
|
1
|
|
|
|
|
8
|
die "No length returned"; |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
} else { |
479
|
5
|
50
|
|
|
|
13
|
if ($self->{truncate}) { |
480
|
0
|
0
|
|
|
|
0
|
truncate($fh,$length+$delta) |
481
|
|
|
|
|
|
|
or die "truncate error: $!"; |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
} |
485
|
8
|
50
|
|
|
|
22
|
seek($fh,$first+$delta,0) |
486
|
|
|
|
|
|
|
or die "seek error: $!"; |
487
|
8
|
|
|
|
|
16
|
$self->{expected}=$last-$first+1; |
488
|
8
|
|
|
|
|
10
|
push(@{$self->{methods}},'definite'); |
|
8
|
|
|
|
|
17
|
|
489
|
8
|
|
|
|
|
11
|
push(@{$self->{ranges}},[$first,$first-1]); |
|
8
|
|
|
|
|
39
|
|
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
1; |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=head1 AUTHOR |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
Bo Lindbergh Eblgl@stacken.kth.seE |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
Copyright 2006 by Bo Lindbergh |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify it |
503
|
|
|
|
|
|
|
under the same terms as Perl itself, either Perl version 5.8.8 or, at |
504
|
|
|
|
|
|
|
your option, any later version of Perl 5 you may have available. |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=cut |
507
|
|
|
|
|
|
|
|