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   473 use Mojo::Base 'Mojo::EventEmitter';
  60         166  
  60         452  
3              
4 60     60   441 use Carp qw(croak);
  60         210  
  60         3422  
5 60     60   453 use Compress::Raw::Zlib qw(WANT_GZIP Z_STREAM_END);
  60         174  
  60         4255  
6 60     60   30983 use Mojo::Headers;
  60         193  
  60         593  
7 60     60   492 use Scalar::Util qw(looks_like_number);
  60         244  
  60         177534  
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 2812 sub body_contains { croak 'Method "body_contains" not implemented by subclass' }
17 1     1 1 794 sub body_size { croak 'Method "body_size" not implemented by subclass' }
18              
19 2896 100 100 2896 1 7148 sub boundary { (shift->headers->content_type // '') =~ $BOUNDARY_RE ? $1 // $2 : undef }
      66        
20              
21             sub charset {
22 1052   100 1052 1 3628 my $type = shift->headers->content_type // '';
23 1052 100       10555 return $type =~ /charset\s*=\s*"?([^"\s;]+)"?/i ? $1 : undef;
24             }
25              
26             sub clone {
27 19     19 1 41 my $self = shift;
28 19 100       48 return undef if $self->is_dynamic;
29 15         59 return $self->new(headers => $self->headers->clone);
30             }
31              
32             sub generate_body_chunk {
33 187     187 1 451 my ($self, $offset) = @_;
34              
35 187 100 100     1100 $self->emit(drain => $offset) unless length($self->{body_buffer} //= '');
36 186 100       793 return delete $self->{body_buffer} if length $self->{body_buffer};
37 64 100       359 return '' if $self->{eof};
38              
39 15         77 my $len = $self->headers->content_length;
40 15 100 100     162 return looks_like_number $len && $len == $offset ? '' : undef;
41             }
42              
43 1     1 1 712 sub get_body_chunk { croak 'Method "get_body_chunk" not implemented by subclass' }
44              
45 2018     2018 1 4728 sub get_header_chunk { substr shift->_headers->{header_buffer}, shift, 131072 }
46              
47 2106     2106 1 5324 sub header_size { length shift->_headers->{header_buffer} }
48              
49 62     62 1 205 sub headers_contain { index(shift->_headers->{header_buffer}, shift) >= 0 }
50              
51 6918     6918 1 13914 sub is_chunked { !!shift->headers->transfer_encoding }
52              
53 935   100 935 1 2356 sub is_compressed { lc(shift->headers->content_encoding // '') eq 'gzip' }
54              
55 6986     6986 1 20544 sub is_dynamic { !!$_[0]{dynamic} }
56              
57 5050   100 5050 1 19931 sub is_finished { (shift->{state} // '') eq 'finished' }
58              
59 2653     2653 1 7471 sub is_limit_exceeded { !!shift->{limit} }
60              
61 4318     4318 1 14662 sub is_multipart {undef}
62              
63 21   100 21 1 125 sub is_parsing_body { (shift->{state} // '') eq 'body' }
64              
65 985     985 1 4329 sub leftovers { shift->{buffer} }
66              
67             sub parse {
68 2835     2835 1 5345 my $self = shift;
69              
70             # Headers
71 2835         7348 $self->_parse_until_body(@_);
72 2835 100       7013 return $self if $self->{state} eq 'headers';
73              
74             # Chunked content
75 2536   100     9563 $self->{real_size} //= 0;
76 2536 100 66     5870 if ($self->is_chunked && $self->{state} ne 'headers') {
77 135         428 $self->_parse_chunked;
78 135 100 100     561 $self->{state} = 'finished' if ($self->{chunk_state} // '') eq 'finished';
79             }
80              
81             # Not chunked, pass through to second buffer
82             else {
83 2401         4637 $self->{real_size} += length $self->{pre_buffer};
84 2401   100     5395 my $limit = $self->is_finished && length($self->{buffer}) > $self->max_leftover_size;
85 2401 100       9163 $self->{buffer} .= $self->{pre_buffer} unless $limit;
86 2401         4565 $self->{pre_buffer} = '';
87             }
88              
89             # No content
90 2536 100       6642 if ($self->skip_body) {
91 99         429 $self->{state} = 'finished';
92 99         445 return $self;
93             }
94              
95             # Relaxed parsing
96 2437         5508 my $headers = $self->headers;
97 2437   100     5719 my $len = $headers->content_length // '';
98 2437 100 100     6259 if ($self->auto_relax && !length $len) {
99 80   100     232 my $connection = lc($headers->connection // '');
100 80 100 100     360 $self->relaxed(1) if $connection eq 'close' || !$connection;
101             }
102              
103             # Chunked or relaxed content
104 2437 100 100     4969 if ($self->is_chunked || $self->relaxed) {
105 257   100     1066 $self->_decompress($self->{buffer} //= '');
106 257         608 $self->{size} += length $self->{buffer};
107 257         459 $self->{buffer} = '';
108 257         1025 return $self;
109             }
110              
111             # Normal content
112 2180 100       9464 $len = 0 unless looks_like_number $len;
113 2180 100 100     11179 if ((my $need = $len - ($self->{size} ||= 0)) > 0) {
114 1189         2649 my $len = length $self->{buffer};
115 1189 100       6159 my $chunk = substr $self->{buffer}, 0, $need > $len ? $len : $need, '';
116 1189         3984 $self->_decompress($chunk);
117 1189         2752 $self->{size} += length $chunk;
118             }
119 2180 100       5174 $self->{state} = 'finished' if $len <= $self->progress;
120              
121 2180         9007 return $self;
122             }
123              
124             sub parse_body {
125 55     55 1 76 my $self = shift;
126 55         113 $self->{state} = 'body';
127 55         157 return $self->parse(@_);
128             }
129              
130             sub progress {
131 2222     2222 1 3506 my $self = shift;
132 2222 100       5237 return 0 unless my $state = $self->{state};
133 2215 100 100     5970 return 0 unless $state eq 'body' || $state eq 'finished';
134 2209   100     8065 return $self->{raw_size} - ($self->{header_size} || 0);
135             }
136              
137             sub write {
138 75     75 1 261 my ($self, $chunk, $cb) = @_;
139              
140 75         161 $self->{dynamic} = 1;
141 75 100       306 $self->{body_buffer} .= $chunk if defined $chunk;
142 75 100       209 $self->once(drain => $cb) if $cb;
143 75 100 100     357 $self->{eof} = 1 if defined $chunk && !length $chunk;
144              
145 75         257 return $self;
146             }
147              
148             sub write_chunk {
149 103     103 1 388 my ($self, $chunk, $cb) = @_;
150              
151 103 100       355 $self->headers->transfer_encoding('chunked') unless $self->{chunked};
152 103         205 @{$self}{qw(chunked dynamic)} = (1, 1);
  103         255  
153              
154 103 100       541 $self->{body_buffer} .= $self->_build_chunk($chunk) if defined $chunk;
155 103 100       500 $self->once(drain => $cb) if $cb;
156 103 100 100     565 $self->{eof} = 1 if defined $chunk && !length $chunk;
157              
158 103         268 return $self;
159             }
160              
161             sub _build_chunk {
162 102     102   210 my ($self, $chunk) = @_;
163              
164             # End
165 102 100       278 return "\x0d\x0a0\x0d\x0a\x0d\x0a" unless length $chunk;
166              
167             # First chunk has no leading CRLF
168 81 100       266 my $crlf = $self->{chunks}++ ? "\x0d\x0a" : '';
169 81         507 return $crlf . sprintf('%x', length $chunk) . "\x0d\x0a$chunk";
170             }
171              
172             sub _decompress {
173 1446     1446   3166 my ($self, $chunk) = @_;
174              
175             # No compression
176 1446 100 100     3773 return $self->emit(read => $chunk) unless $self->auto_decompress && $self->is_compressed;
177              
178             # Decompress
179 58         353 $self->{post_buffer} .= $chunk;
180 58   66     711 my $gz = $self->{gz} //= Compress::Raw::Zlib::Inflate->new(WindowBits => WANT_GZIP);
181 58         36944 my $status = $gz->inflate(\$self->{post_buffer}, my $out);
182 58 50       462 $self->emit(read => $out) if defined $out;
183              
184             # Replace Content-Encoding with Content-Length
185 58 100       413 $self->headers->content_length($gz->total_out)->remove('Content-Encoding') if $status == Z_STREAM_END;
186              
187             # Check buffer size
188 58 100 50     525 @$self{qw(state limit)} = ('finished', 1) if length($self->{post_buffer} // '') > $self->max_buffer_size;
189             }
190              
191             sub _headers {
192 4186     4186   5989 my $self = shift;
193 4186 100       14742 return $self if defined $self->{header_buffer};
194 1968         4636 my $headers = $self->headers->to_string;
195 1968 100       7358 $self->{header_buffer} = $headers ? "$headers\x0d\x0a\x0d\x0a" : "\x0d\x0a";
196 1968         9616 return $self;
197             }
198              
199             sub _parse_chunked {
200 135     135   216 my $self = shift;
201              
202             # Trailing headers
203 135 100 100     565 return $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
204              
205 133         363 while (my $len = length $self->{pre_buffer}) {
206              
207             # Start new chunk (ignore the chunk extension)
208 252 100       572 unless ($self->{chunk_len}) {
209 151 100       987 last unless $self->{pre_buffer} =~ s/^(?:\x0d?\x0a)?([0-9a-fA-F]+).*\x0a//;
210 131 100       644 next if $self->{chunk_len} = hex $1;
211              
212             # Last chunk
213 30         132 $self->{chunk_state} = 'trailing_headers';
214 30         69 last;
215             }
216              
217             # Remove as much as possible from payload
218 101 100       313 $len = $self->{chunk_len} if $self->{chunk_len} < $len;
219 101         321 $self->{buffer} .= substr $self->{pre_buffer}, 0, $len, '';
220 101         169 $self->{real_size} += $len;
221 101         294 $self->{chunk_len} -= $len;
222             }
223              
224             # Trailing headers
225 133 100 100     590 $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
226              
227             # Check buffer size
228 133 100 100     590 @$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   73 my $self = shift;
233              
234 32         109 my $headers = $self->headers->parse(delete $self->{pre_buffer});
235 32 100       164 return unless $headers->is_finished;
236 30         95 $self->{chunk_state} = 'finished';
237              
238             # Take care of leftover and replace Transfer-Encoding with Content-Length
239 30         105 $self->{buffer} .= $headers->leftovers;
240 30         141 $headers->remove('Transfer-Encoding');
241 30 100       99 $headers->content_length($self->{real_size}) unless $headers->content_length;
242             }
243              
244             sub _parse_headers {
245 2702     2702   4385 my $self = shift;
246              
247 2702         6524 my $headers = $self->headers->parse(delete $self->{pre_buffer});
248 2702 100       8289 return unless $headers->is_finished;
249 2104         4569 $self->{state} = 'body';
250              
251             # Take care of leftovers
252 2104         5823 my $leftovers = $self->{pre_buffer} = $headers->leftovers;
253 2104         5869 $self->{header_size} = $self->{raw_size} - length $leftovers;
254             }
255              
256             sub _parse_until_body {
257 5472     5472   10858 my ($self, $chunk) = @_;
258              
259 5472   100     16170 $self->{raw_size} += length($chunk //= '');
260 5472         14766 $self->{pre_buffer} .= $chunk;
261 5472 100 100     22023 $self->_parse_headers if ($self->{state} ||= 'headers') eq 'headers';
262 5472 100 100     26983 $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