File Coverage

blib/lib/Mojo/Content.pm
Criterion Covered Total %
statement 143 143 100.0
branch 95 96 98.9
condition 73 77 94.8
subroutine 35 35 100.0
pod 23 23 100.0
total 369 374 98.6


line stmt bran cond sub pod time code
1             package Mojo::Content;
2 61     61   474 use Mojo::Base 'Mojo::EventEmitter';
  61         145  
  61         450  
3              
4 61     61   489 use Carp qw(croak);
  61         196  
  61         3817  
5 61     61   579 use Compress::Raw::Zlib qw(WANT_GZIP Z_STREAM_END);
  61         160  
  61         4442  
6 61     61   33292 use Mojo::Headers;
  61         310  
  61         657  
7 61     61   529 use Scalar::Util qw(looks_like_number);
  61         161  
  61         182178  
8              
9             has [qw(auto_decompress auto_relax relaxed skip_body)];
10             has headers => sub { Mojo::Headers->new };
11             has max_buffer_size => sub { $ENV{MOJO_MAX_BUFFER_SIZE} || 262144 };
12             has max_leftover_size => sub { $ENV{MOJO_MAX_LEFTOVER_SIZE} || 262144 };
13              
14             my $BOUNDARY_RE = qr!multipart.*boundary\s*=\s*(?:"([^"]+)"|([\w'(),.:?\-+/]+))!i;
15              
16 1     1 1 2830 sub body_contains { croak 'Method "body_contains" not implemented by subclass' }
17 1     1 1 855 sub body_size { croak 'Method "body_size" not implemented by subclass' }
18              
19 2926 100 100 2926 1 7590 sub boundary { (shift->headers->content_type // '') =~ $BOUNDARY_RE ? $1 // $2 : undef }
      66        
20              
21             sub charset {
22 1065   100 1065 1 3311 my $type = shift->headers->content_type // '';
23 1065 100       10169 return $type =~ /charset\s*=\s*"?([^"\s;]+)"?/i ? $1 : undef;
24             }
25              
26             sub clone {
27 19     19 1 42 my $self = shift;
28 19 100       78 return undef if $self->is_dynamic;
29 15         53 return $self->new(headers => $self->headers->clone);
30             }
31              
32             sub generate_body_chunk {
33 187     187 1 407 my ($self, $offset) = @_;
34              
35 187 100 100     1120 $self->emit(drain => $offset) unless length($self->{body_buffer} //= '');
36 186 100       843 return delete $self->{body_buffer} if length $self->{body_buffer};
37 64 100       333 return '' if $self->{eof};
38              
39 15         80 my $len = $self->headers->content_length;
40 15 100 100     279 return looks_like_number $len && $len == $offset ? '' : undef;
41             }
42              
43 1     1 1 739 sub get_body_chunk { croak 'Method "get_body_chunk" not implemented by subclass' }
44              
45 2042     2042 1 4332 sub get_header_chunk { substr shift->_headers->{header_buffer}, shift, 131072 }
46              
47 2130     2130 1 5309 sub header_size { length shift->_headers->{header_buffer} }
48              
49 62     62 1 165 sub headers_contain { index(shift->_headers->{header_buffer}, shift) >= 0 }
50              
51 6998     6998 1 14547 sub is_chunked { !!shift->headers->transfer_encoding }
52              
53 949   100 949 1 2405 sub is_compressed { lc(shift->headers->content_encoding // '') eq 'gzip' }
54              
55 7068     7068 1 21225 sub is_dynamic { !!$_[0]{dynamic} }
56              
57 5105   100 5105 1 20486 sub is_finished { (shift->{state} // '') eq 'finished' }
58              
59 2680     2680 1 8040 sub is_limit_exceeded { !!shift->{limit} }
60              
61 4368     4368 1 14445 sub is_multipart {undef}
62              
63 21   100 21 1 128 sub is_parsing_body { (shift->{state} // '') eq 'body' }
64              
65 997     997 1 3984 sub leftovers { shift->{buffer} }
66              
67             sub parse {
68 2865     2865 1 5533 my $self = shift;
69              
70             # Headers
71 2865         7496 $self->_parse_until_body(@_);
72 2865 100       7375 return $self if $self->{state} eq 'headers';
73              
74             # Chunked content
75 2564   100     9746 $self->{real_size} //= 0;
76 2564 100 66     5869 if ($self->is_chunked && $self->{state} ne 'headers') {
77 135         492 $self->_parse_chunked;
78 135 100 100     621 $self->{state} = 'finished' if ($self->{chunk_state} // '') eq 'finished';
79             }
80              
81             # Not chunked, pass through to second buffer
82             else {
83 2429         4774 $self->{real_size} += length $self->{pre_buffer};
84 2429   100     5323 my $limit = $self->is_finished && length($self->{buffer}) > $self->max_leftover_size;
85 2429 100       9131 $self->{buffer} .= $self->{pre_buffer} unless $limit;
86 2429         4657 $self->{pre_buffer} = '';
87             }
88              
89             # No content
90 2564 100       7094 if ($self->skip_body) {
91 99         267 $self->{state} = 'finished';
92 99         462 return $self;
93             }
94              
95             # Relaxed parsing
96 2465         5437 my $headers = $self->headers;
97 2465   100     5822 my $len = $headers->content_length // '';
98 2465 100 100     6385 if ($self->auto_relax && !length $len) {
99 81   100     266 my $connection = lc($headers->connection // '');
100 81 100 100     450 $self->relaxed(1) if $connection eq 'close' || !$connection;
101             }
102              
103             # Chunked or relaxed content
104 2465 100 100     5120 if ($self->is_chunked || $self->relaxed) {
105 259   100     1275 $self->_decompress($self->{buffer} //= '');
106 259         686 $self->{size} += length $self->{buffer};
107 259         477 $self->{buffer} = '';
108 259         1106 return $self;
109             }
110              
111             # Normal content
112 2206 100       9711 $len = 0 unless looks_like_number $len;
113 2206 100 100     11139 if ((my $need = $len - ($self->{size} ||= 0)) > 0) {
114 1203         2627 my $len = length $self->{buffer};
115 1203 100       6721 my $chunk = substr $self->{buffer}, 0, $need > $len ? $len : $need, '';
116 1203         4308 $self->_decompress($chunk);
117 1203         2838 $self->{size} += length $chunk;
118             }
119 2206 100       5206 $self->{state} = 'finished' if $len <= $self->progress;
120              
121 2206         9131 return $self;
122             }
123              
124             sub parse_body {
125 55     55 1 108 my $self = shift;
126 55         123 $self->{state} = 'body';
127 55         168 return $self->parse(@_);
128             }
129              
130             sub progress {
131 2248     2248 1 3467 my $self = shift;
132 2248 100       5548 return 0 unless my $state = $self->{state};
133 2241 100 100     5929 return 0 unless $state eq 'body' || $state eq 'finished';
134 2235   100     8094 return $self->{raw_size} - ($self->{header_size} || 0);
135             }
136              
137             sub write {
138 75     75 1 263 my ($self, $chunk, $cb) = @_;
139              
140 75         168 $self->{dynamic} = 1;
141 75 100       256 $self->{body_buffer} .= $chunk if defined $chunk;
142 75 100       242 $self->once(drain => $cb) if $cb;
143 75 100 100     354 $self->{eof} = 1 if defined $chunk && !length $chunk;
144              
145 75         256 return $self;
146             }
147              
148             sub write_chunk {
149 103     103 1 382 my ($self, $chunk, $cb) = @_;
150              
151 103 100       347 $self->headers->transfer_encoding('chunked') unless $self->{chunked};
152 103         212 @{$self}{qw(chunked dynamic)} = (1, 1);
  103         244  
153              
154 103 100       444 $self->{body_buffer} .= $self->_build_chunk($chunk) if defined $chunk;
155 103 100       489 $self->once(drain => $cb) if $cb;
156 103 100 100     541 $self->{eof} = 1 if defined $chunk && !length $chunk;
157              
158 103         283 return $self;
159             }
160              
161             sub _build_chunk {
162 102     102   218 my ($self, $chunk) = @_;
163              
164             # End
165 102 100       297 return "\x0d\x0a0\x0d\x0a\x0d\x0a" unless length $chunk;
166              
167             # First chunk has no leading CRLF
168 81 100       262 my $crlf = $self->{chunks}++ ? "\x0d\x0a" : '';
169 81         504 return $crlf . sprintf('%x', length $chunk) . "\x0d\x0a$chunk";
170             }
171              
172             sub _decompress {
173 1462     1462   3217 my ($self, $chunk) = @_;
174              
175             # No compression
176 1462 100 100     3574 return $self->emit(read => $chunk) unless $self->auto_decompress && $self->is_compressed;
177              
178             # Decompress
179 62         433 $self->{post_buffer} .= $chunk;
180 62   66     743 my $gz = $self->{gz} //= Compress::Raw::Zlib::Inflate->new(WindowBits => WANT_GZIP);
181 62         39397 my $status = $gz->inflate(\$self->{post_buffer}, my $out);
182 62 50       531 $self->emit(read => $out) if defined $out;
183              
184             # Replace Content-Encoding with Content-Length
185 62 100       443 $self->headers->content_length($gz->total_out)->remove('Content-Encoding') if $status == Z_STREAM_END;
186              
187             # Check buffer size
188 62 100 50     684 @$self{qw(state limit)} = ('finished', 1) if length($self->{post_buffer} // '') > $self->max_buffer_size;
189             }
190              
191             sub _headers {
192 4234     4234   6580 my $self = shift;
193 4234 100       14945 return $self if defined $self->{header_buffer};
194 1992         4485 my $headers = $self->headers->to_string;
195 1992 100       7793 $self->{header_buffer} = $headers ? "$headers\x0d\x0a\x0d\x0a" : "\x0d\x0a";
196 1992         9427 return $self;
197             }
198              
199             sub _parse_chunked {
200 135     135   238 my $self = shift;
201              
202             # Trailing headers
203 135 100 100     612 return $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
204              
205 133         412 while (my $len = length $self->{pre_buffer}) {
206              
207             # Start new chunk (ignore the chunk extension)
208 252 100       537 unless ($self->{chunk_len}) {
209 151 100       1050 last unless $self->{pre_buffer} =~ s/^(?:\x0d?\x0a)?([0-9a-fA-F]+).*\x0a//;
210 131 100       664 next if $self->{chunk_len} = hex $1;
211              
212             # Last chunk
213 30         130 $self->{chunk_state} = 'trailing_headers';
214 30         79 last;
215             }
216              
217             # Remove as much as possible from payload
218 101 100       303 $len = $self->{chunk_len} if $self->{chunk_len} < $len;
219 101         350 $self->{buffer} .= substr $self->{pre_buffer}, 0, $len, '';
220 101         175 $self->{real_size} += $len;
221 101         255 $self->{chunk_len} -= $len;
222             }
223              
224             # Trailing headers
225 133 100 100     696 $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
226              
227             # Check buffer size
228 133 100 100     643 @$self{qw(state limit)} = ('finished', 1) if length($self->{pre_buffer} // '') > $self->max_buffer_size;
229             }
230              
231             sub _parse_chunked_trailing_headers {
232 32     32   100 my $self = shift;
233              
234 32         136 my $headers = $self->headers->parse(delete $self->{pre_buffer});
235 32 100       167 return unless $headers->is_finished;
236 30         130 $self->{chunk_state} = 'finished';
237              
238             # Take care of leftover and replace Transfer-Encoding with Content-Length
239 30         107 $self->{buffer} .= $headers->leftovers;
240 30         152 $headers->remove('Transfer-Encoding');
241 30 100       128 $headers->content_length($self->{real_size}) unless $headers->content_length;
242             }
243              
244             sub _parse_headers {
245 2730     2730   4192 my $self = shift;
246              
247 2730         6556 my $headers = $self->headers->parse(delete $self->{pre_buffer});
248 2730 100       8313 return unless $headers->is_finished;
249 2128         4411 $self->{state} = 'body';
250              
251             # Take care of leftovers
252 2128         5082 my $leftovers = $self->{pre_buffer} = $headers->leftovers;
253 2128         5751 $self->{header_size} = $self->{raw_size} - length $leftovers;
254             }
255              
256             sub _parse_until_body {
257 5531     5531   10666 my ($self, $chunk) = @_;
258              
259 5531   100     16777 $self->{raw_size} += length($chunk //= '');
260 5531         15323 $self->{pre_buffer} .= $chunk;
261 5531 100 100     22312 $self->_parse_headers if ($self->{state} ||= 'headers') eq 'headers';
262 5531 100 100     27979 $self->emit('body') if $self->{state} ne 'headers' && !$self->{body}++;
263             }
264              
265             1;
266              
267             =encoding utf8
268              
269             =head1 NAME
270              
271             Mojo::Content - HTTP content base class
272              
273             =head1 SYNOPSIS
274              
275             package Mojo::Content::MyContent;
276             use Mojo::Base 'Mojo::Content';
277              
278             sub body_contains {...}
279             sub body_size {...}
280             sub get_body_chunk {...}
281              
282             =head1 DESCRIPTION
283              
284             L is an abstract base class for HTTP content containers, based on L
285             7230|https://tools.ietf.org/html/rfc7230> and L, like
286             L and L.
287              
288             =head1 EVENTS
289              
290             L inherits all events from L and can emit the following new ones.
291              
292             =head2 body
293              
294             $content->on(body => sub ($content) {...});
295              
296             Emitted once all headers have been parsed and the body starts.
297              
298             $content->on(body => sub ($content) {
299             $content->auto_upgrade(0) if $content->headers->header('X-No-MultiPart');
300             });
301              
302             =head2 drain
303              
304             $content->on(drain => sub ($content, $offset) {...});
305              
306             Emitted once all data has been written.
307              
308             $content->on(drain => sub ($content) {
309             $content->write_chunk(time);
310             });
311              
312             =head2 read
313              
314             $content->on(read => sub ($content, $bytes) {...});
315              
316             Emitted when a new chunk of content arrives.
317              
318             $content->on(read => sub ($content, $bytes) {
319             say "Streaming: $bytes";
320             });
321              
322             =head1 ATTRIBUTES
323              
324             L implements the following attributes.
325              
326             =head2 auto_decompress
327              
328             my $bool = $content->auto_decompress;
329             $content = $content->auto_decompress($bool);
330              
331             Decompress content automatically if L is true.
332              
333             =head2 auto_relax
334              
335             my $bool = $content->auto_relax;
336             $content = $content->auto_relax($bool);
337              
338             Try to detect when relaxed parsing is necessary.
339              
340             =head2 headers
341              
342             my $headers = $content->headers;
343             $content = $content->headers(Mojo::Headers->new);
344              
345             Content headers, defaults to a L object.
346              
347             =head2 max_buffer_size
348              
349             my $size = $content->max_buffer_size;
350             $content = $content->max_buffer_size(1024);
351              
352             Maximum size in bytes of buffer for content parser, defaults to the value of the C environment
353             variable or C<262144> (256KiB).
354              
355             =head2 max_leftover_size
356              
357             my $size = $content->max_leftover_size;
358             $content = $content->max_leftover_size(1024);
359              
360             Maximum size in bytes of buffer for pipelined HTTP requests, defaults to the value of the C
361             environment variable or C<262144> (256KiB).
362              
363             =head2 relaxed
364              
365             my $bool = $content->relaxed;
366             $content = $content->relaxed($bool);
367              
368             Activate relaxed parsing for responses that are terminated with a connection close.
369              
370             =head2 skip_body
371              
372             my $bool = $content->skip_body;
373             $content = $content->skip_body($bool);
374              
375             Skip body parsing and finish after headers.
376              
377             =head1 METHODS
378              
379             L inherits all methods from L and implements the following new ones.
380              
381             =head2 body_contains
382              
383             my $bool = $content->body_contains('foo bar baz');
384              
385             Check if content contains a specific string. Meant to be overloaded in a subclass.
386              
387             =head2 body_size
388              
389             my $size = $content->body_size;
390              
391             Content size in bytes. Meant to be overloaded in a subclass.
392              
393             =head2 boundary
394              
395             my $boundary = $content->boundary;
396              
397             Extract multipart boundary from C header.
398              
399             =head2 charset
400              
401             my $charset = $content->charset;
402              
403             Extract charset from C header.
404              
405             =head2 clone
406              
407             my $clone = $content->clone;
408              
409             Return a new L object cloned from this content if possible, otherwise return C.
410              
411             =head2 generate_body_chunk
412              
413             my $bytes = $content->generate_body_chunk(0);
414              
415             Generate dynamic content.
416              
417             =head2 get_body_chunk
418              
419             my $bytes = $content->get_body_chunk(0);
420              
421             Get a chunk of content starting from a specific position. Meant to be overloaded in a subclass.
422              
423             =head2 get_header_chunk
424              
425             my $bytes = $content->get_header_chunk(13);
426              
427             Get a chunk of the headers starting from a specific position. Note that this method finalizes the content.
428              
429             =head2 header_size
430              
431             my $size = $content->header_size;
432              
433             Size of headers in bytes. Note that this method finalizes the content.
434              
435             =head2 headers_contain
436              
437             my $bool = $content->headers_contain('foo bar baz');
438              
439             Check if headers contain a specific string. Note that this method finalizes the content.
440              
441             =head2 is_chunked
442              
443             my $bool = $content->is_chunked;
444              
445             Check if C header indicates chunked transfer encoding.
446              
447             =head2 is_compressed
448              
449             my $bool = $content->is_compressed;
450              
451             Check C header for C value.
452              
453             =head2 is_dynamic
454              
455             my $bool = $content->is_dynamic;
456              
457             Check if content will be dynamically generated, which prevents L from working.
458              
459             =head2 is_finished
460              
461             my $bool = $content->is_finished;
462              
463             Check if parser is finished.
464              
465             =head2 is_limit_exceeded
466              
467             my $bool = $content->is_limit_exceeded;
468              
469             Check if buffer has exceeded L.
470              
471             =head2 is_multipart
472              
473             my $bool = $content->is_multipart;
474              
475             False, this is not a L object.
476              
477             =head2 is_parsing_body
478              
479             my $bool = $content->is_parsing_body;
480              
481             Check if body parsing started yet.
482              
483             =head2 leftovers
484              
485             my $bytes = $content->leftovers;
486              
487             Get leftover data from content parser.
488              
489             =head2 parse
490              
491             $content
492             = $content->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
493              
494             Parse content chunk.
495              
496             =head2 parse_body
497              
498             $content = $content->parse_body('Hi!');
499              
500             Parse body chunk and skip headers.
501              
502             =head2 progress
503              
504             my $size = $content->progress;
505              
506             Size of content already received from message in bytes.
507              
508             =head2 write
509              
510             $content = $content->write;
511             $content = $content->write('');
512             $content = $content->write($bytes);
513             $content = $content->write($bytes => sub {...});
514              
515             Write dynamic content non-blocking, the optional drain callback will be executed once all data has been written.
516             Calling this method without a chunk of data will finalize the L and allow for dynamic content to be written
517             later. You can write an empty chunk of data at any time to end the stream.
518              
519             # Make sure previous chunk of data has been written before continuing
520             $content->write('He' => sub ($content) {
521             $content->write('llo!' => sub ($content) {
522             $content->write('');
523             });
524             });
525              
526             =head2 write_chunk
527              
528             $content = $content->write_chunk;
529             $content = $content->write_chunk('');
530             $content = $content->write_chunk($bytes);
531             $content = $content->write_chunk($bytes => sub {...});
532              
533             Write dynamic content non-blocking with chunked transfer encoding, the optional drain callback will be executed once
534             all data has been written. Calling this method without a chunk of data will finalize the L and allow for
535             dynamic content to be written later. You can write an empty chunk of data at any time to end the stream.
536              
537             # Make sure previous chunk of data has been written before continuing
538             $content->write_chunk('He' => sub ($content) {
539             $content->write_chunk('llo!' => sub ($content) {
540             $content->write_chunk('');
541             });
542             });
543              
544             =head1 SEE ALSO
545              
546             L, L, L.
547              
548             =cut