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 60     60   466 use Mojo::Base 'Mojo::EventEmitter';
  60         152  
  60         453  
3              
4 60     60   434 use Carp qw(croak);
  60         134  
  60         3453  
5 60     60   493 use Compress::Raw::Zlib qw(WANT_GZIP Z_STREAM_END);
  60         161  
  60         4074  
6 60     60   30657 use Mojo::Headers;
  60         196  
  60         589  
7 60     60   537 use Scalar::Util qw(looks_like_number);
  60         251  
  60         169201  
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 2843 sub body_contains { croak 'Method "body_contains" not implemented by subclass' }
17 1     1 1 870 sub body_size { croak 'Method "body_size" not implemented by subclass' }
18              
19 2949 100 100 2949 1 7130 sub boundary { (shift->headers->content_type // '') =~ $BOUNDARY_RE ? $1 // $2 : undef }
      66        
20              
21             sub charset {
22 1052   100 1052 1 3308 my $type = shift->headers->content_type // '';
23 1052 100       10514 return $type =~ /charset\s*=\s*"?([^"\s;]+)"?/i ? $1 : undef;
24             }
25              
26             sub clone {
27 19     19 1 37 my $self = shift;
28 19 100       45 return undef if $self->is_dynamic;
29 15         64 return $self->new(headers => $self->headers->clone);
30             }
31              
32             sub generate_body_chunk {
33 187     187 1 402 my ($self, $offset) = @_;
34              
35 187 100 100     1104 $self->emit(drain => $offset) unless length($self->{body_buffer} //= '');
36 186 100       762 return delete $self->{body_buffer} if length $self->{body_buffer};
37 64 100       274 return '' if $self->{eof};
38              
39 15         65 my $len = $self->headers->content_length;
40 15 100 100     165 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 2018     2018 1 4289 sub get_header_chunk { substr shift->_headers->{header_buffer}, shift, 131072 }
46              
47 2106     2106 1 5388 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 6986     6986 1 14223 sub is_chunked { !!shift->headers->transfer_encoding }
52              
53 939   100 939 1 2356 sub is_compressed { lc(shift->headers->content_encoding // '') eq 'gzip' }
54              
55 6986     6986 1 20079 sub is_dynamic { !!$_[0]{dynamic} }
56              
57 5113   100 5113 1 19870 sub is_finished { (shift->{state} // '') eq 'finished' }
58              
59 2686     2686 1 7646 sub is_limit_exceeded { !!shift->{limit} }
60              
61 4318     4318 1 14135 sub is_multipart {undef}
62              
63 21   100 21 1 131 sub is_parsing_body { (shift->{state} // '') eq 'body' }
64              
65 985     985 1 4115 sub leftovers { shift->{buffer} }
66              
67             sub parse {
68 2888     2888 1 5405 my $self = shift;
69              
70             # Headers
71 2888         7393 $self->_parse_until_body(@_);
72 2888 100       7098 return $self if $self->{state} eq 'headers';
73              
74             # Chunked content
75 2570   100     9478 $self->{real_size} //= 0;
76 2570 100 66     5576 if ($self->is_chunked && $self->{state} ne 'headers') {
77 139         439 $self->_parse_chunked;
78 139 100 100     550 $self->{state} = 'finished' if ($self->{chunk_state} // '') eq 'finished';
79             }
80              
81             # Not chunked, pass through to second buffer
82             else {
83 2431         4860 $self->{real_size} += length $self->{pre_buffer};
84 2431   100     5231 my $limit = $self->is_finished && length($self->{buffer}) > $self->max_leftover_size;
85 2431 100       8889 $self->{buffer} .= $self->{pre_buffer} unless $limit;
86 2431         4618 $self->{pre_buffer} = '';
87             }
88              
89             # No content
90 2570 100       6661 if ($self->skip_body) {
91 99         277 $self->{state} = 'finished';
92 99         451 return $self;
93             }
94              
95             # Relaxed parsing
96 2471         5216 my $headers = $self->headers;
97 2471   100     5675 my $len = $headers->content_length // '';
98 2471 100 100     6541 if ($self->auto_relax && !length $len) {
99 84   100     229 my $connection = lc($headers->connection // '');
100 84 100 100     441 $self->relaxed(1) if $connection eq 'close' || !$connection;
101             }
102              
103             # Chunked or relaxed content
104 2471 100 100     5094 if ($self->is_chunked || $self->relaxed) {
105 262   100     1094 $self->_decompress($self->{buffer} //= '');
106 262         649 $self->{size} += length $self->{buffer};
107 262         491 $self->{buffer} = '';
108 262         1022 return $self;
109             }
110              
111             # Normal content
112 2209 100       9285 $len = 0 unless looks_like_number $len;
113 2209 100 100     10977 if ((my $need = $len - ($self->{size} ||= 0)) > 0) {
114 1218         2611 my $len = length $self->{buffer};
115 1218 100       6390 my $chunk = substr $self->{buffer}, 0, $need > $len ? $len : $need, '';
116 1218         3939 $self->_decompress($chunk);
117 1218         2745 $self->{size} += length $chunk;
118             }
119 2209 100       5082 $self->{state} = 'finished' if $len <= $self->progress;
120              
121 2209         8897 return $self;
122             }
123              
124             sub parse_body {
125 55     55 1 103 my $self = shift;
126 55         123 $self->{state} = 'body';
127 55         169 return $self->parse(@_);
128             }
129              
130             sub progress {
131 2251     2251 1 3442 my $self = shift;
132 2251 100       5510 return 0 unless my $state = $self->{state};
133 2244 100 100     6068 return 0 unless $state eq 'body' || $state eq 'finished';
134 2238   100     7995 return $self->{raw_size} - ($self->{header_size} || 0);
135             }
136              
137             sub write {
138 75     75 1 262 my ($self, $chunk, $cb) = @_;
139              
140 75         156 $self->{dynamic} = 1;
141 75 100       241 $self->{body_buffer} .= $chunk if defined $chunk;
142 75 100       251 $self->once(drain => $cb) if $cb;
143 75 100 100     374 $self->{eof} = 1 if defined $chunk && !length $chunk;
144              
145 75         248 return $self;
146             }
147              
148             sub write_chunk {
149 103     103 1 381 my ($self, $chunk, $cb) = @_;
150              
151 103 100       333 $self->headers->transfer_encoding('chunked') unless $self->{chunked};
152 103         195 @{$self}{qw(chunked dynamic)} = (1, 1);
  103         245  
153              
154 103 100       430 $self->{body_buffer} .= $self->_build_chunk($chunk) if defined $chunk;
155 103 100       468 $self->once(drain => $cb) if $cb;
156 103 100 100     525 $self->{eof} = 1 if defined $chunk && !length $chunk;
157              
158 103         283 return $self;
159             }
160              
161             sub _build_chunk {
162 102     102   206 my ($self, $chunk) = @_;
163              
164             # End
165 102 100       269 return "\x0d\x0a0\x0d\x0a\x0d\x0a" unless length $chunk;
166              
167             # First chunk has no leading CRLF
168 81 100       250 my $crlf = $self->{chunks}++ ? "\x0d\x0a" : '';
169 81         477 return $crlf . sprintf('%x', length $chunk) . "\x0d\x0a$chunk";
170             }
171              
172             sub _decompress {
173 1480     1480   3112 my ($self, $chunk) = @_;
174              
175             # No compression
176 1480 100 100     3582 return $self->emit(read => $chunk) unless $self->auto_decompress && $self->is_compressed;
177              
178             # Decompress
179 58         303 $self->{post_buffer} .= $chunk;
180 58   66     648 my $gz = $self->{gz} //= Compress::Raw::Zlib::Inflate->new(WindowBits => WANT_GZIP);
181 58         35573 my $status = $gz->inflate(\$self->{post_buffer}, my $out);
182 58 50       456 $self->emit(read => $out) if defined $out;
183              
184             # Replace Content-Encoding with Content-Length
185 58 100       370 $self->headers->content_length($gz->total_out)->remove('Content-Encoding') if $status == Z_STREAM_END;
186              
187             # Check buffer size
188 58 100 50     476 @$self{qw(state limit)} = ('finished', 1) if length($self->{post_buffer} // '') > $self->max_buffer_size;
189             }
190              
191             sub _headers {
192 4186     4186   6041 my $self = shift;
193 4186 100       14441 return $self if defined $self->{header_buffer};
194 1968         4464 my $headers = $self->headers->to_string;
195 1968 100       7700 $self->{header_buffer} = $headers ? "$headers\x0d\x0a\x0d\x0a" : "\x0d\x0a";
196 1968         9099 return $self;
197             }
198              
199             sub _parse_chunked {
200 139     139   228 my $self = shift;
201              
202             # Trailing headers
203 139 100 100     554 return $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
204              
205 137         372 while (my $len = length $self->{pre_buffer}) {
206              
207             # Start new chunk (ignore the chunk extension)
208 252 100       547 unless ($self->{chunk_len}) {
209 151 100       1059 last unless $self->{pre_buffer} =~ s/^(?:\x0d?\x0a)?([0-9a-fA-F]+).*\x0a//;
210 131 100       658 next if $self->{chunk_len} = hex $1;
211              
212             # Last chunk
213 30         152 $self->{chunk_state} = 'trailing_headers';
214 30         65 last;
215             }
216              
217             # Remove as much as possible from payload
218 101 100       283 $len = $self->{chunk_len} if $self->{chunk_len} < $len;
219 101         320 $self->{buffer} .= substr $self->{pre_buffer}, 0, $len, '';
220 101         163 $self->{real_size} += $len;
221 101         254 $self->{chunk_len} -= $len;
222             }
223              
224             # Trailing headers
225 137 100 100     647 $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
226              
227             # Check buffer size
228 137 100 100     582 @$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   63 my $self = shift;
233              
234 32         109 my $headers = $self->headers->parse(delete $self->{pre_buffer});
235 32 100       149 return unless $headers->is_finished;
236 30         94 $self->{chunk_state} = 'finished';
237              
238             # Take care of leftover and replace Transfer-Encoding with Content-Length
239 30         112 $self->{buffer} .= $headers->leftovers;
240 30         129 $headers->remove('Transfer-Encoding');
241 30 100       96 $headers->content_length($self->{real_size}) unless $headers->content_length;
242             }
243              
244             sub _parse_headers {
245 2740     2740   4115 my $self = shift;
246              
247 2740         6167 my $headers = $self->headers->parse(delete $self->{pre_buffer});
248 2740 100       8313 return unless $headers->is_finished;
249 2104         4289 $self->{state} = 'body';
250              
251             # Take care of leftovers
252 2104         5524 my $leftovers = $self->{pre_buffer} = $headers->leftovers;
253 2104         5665 $self->{header_size} = $self->{raw_size} - length $leftovers;
254             }
255              
256             sub _parse_until_body {
257 5549     5549   10635 my ($self, $chunk) = @_;
258              
259 5549   100     16705 $self->{raw_size} += length($chunk //= '');
260 5549         15129 $self->{pre_buffer} .= $chunk;
261 5549 100 100     22063 $self->_parse_headers if ($self->{state} ||= 'headers') eq 'headers';
262 5549 100 100     26823 $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