File Coverage

blib/lib/Perlbal/HTTPHeaders.pm
Criterion Covered Total %
statement 170 205 82.9
branch 56 106 52.8
condition 34 86 39.5
subroutine 26 27 96.3
pod 0 20 0.0
total 286 444 64.4


line stmt bran cond sub pod time code
1             ######################################################################
2             # HTTP header class (both request and response)
3             #
4             # Copyright 2004, Danga Interactive, Inc.
5             # Copyright 2005-2007, Six Apart, Ltd.
6             #
7              
8             package Perlbal::HTTPHeaders;
9 22     22   177 use strict;
  22         63  
  22         868  
10 22     22   132 use warnings;
  22         53  
  22         1559  
11 22     22   119 no warnings qw(deprecated);
  22         205  
  22         1350  
12              
13 22     22   117 use Perlbal;
  22         55  
  22         1326  
14              
15             use fields (
16 22         164 'headers', # href; lowercase header -> comma-sep list of values
17             'origcase', # href; lowercase header -> provided case
18             'hdorder', # aref; order headers were received (canonical order)
19             'method', # scalar; request method (if GET request)
20             'uri', # scalar; request URI (if GET request)
21             'type', # 'res' or 'req'
22             'code', # HTTP response status code
23             'codetext', # status text that for response code
24             'ver', # version (string) "1.1"
25             'vernum', # version (number: major*1000+minor): "1.1" => 1001
26             'responseLine', # first line of HTTP response (if response)
27             'requestLine', # first line of HTTP request (if request)
28 22     22   25120 );
  22         352757  
29              
30             our $HTTPCode = {
31             200 => 'OK',
32             204 => 'No Content',
33             206 => 'Partial Content',
34             301 => 'Permanent Redirect',
35             302 => 'Found',
36             304 => 'Not Modified',
37             400 => 'Bad request',
38             403 => 'Forbidden',
39             404 => 'Not Found',
40             416 => 'Request range not satisfiable',
41             500 => 'Internal Server Error',
42             501 => 'Not Implemented',
43             503 => 'Service Unavailable',
44             };
45              
46             sub fail {
47 1     1 0 5 return undef unless Perlbal::DEBUG >= 1;
48              
49 0         0 my $reason = shift;
50 0         0 print "HTTP parse failure: $reason\n" if Perlbal::DEBUG >= 1;
51 0         0 return undef;
52             }
53              
54             sub http_code_english {
55 355     355 0 1457 my Perlbal::HTTPHeaders $self = shift;
56 355 100       962 if (@_) {
57 80   100     552 return $HTTPCode->{shift()} || "";
58             } else {
59 275 50       1124 return "" unless $self->response_code;
60 275   100     799 return $HTTPCode->{$self->response_code} || "";
61             }
62             }
63              
64             sub new_response {
65 80     80 0 163 my Perlbal::HTTPHeaders $self = shift;
66 80 50       399 $self = fields::new($self) unless ref $self;
67              
68 80         8185 my $code = shift;
69 80         218 $self->{headers} = {};
70 80         263 $self->{origcase} = {};
71 80         218 $self->{hdorder} = [];
72 80         247 $self->{method} = undef;
73 80         154 $self->{uri} = undef;
74              
75 80         683 $self->{responseLine} = "HTTP/1.0 $code " . $self->http_code_english($code);
76 80         293 $self->{code} = $code;
77 80         240 $self->{type} = "httpres";
78              
79 80         338 Perlbal::objctor($self, $self->{type});
80 80         391 return $self;
81             }
82             *new_response_PERL = \&new_response;
83              
84             sub new {
85 380     380 0 2696 my Perlbal::HTTPHeaders $self = shift;
86 380 50       2386 $self = fields::new($self) unless ref $self;
87              
88 380         56067 my ($hstr_ref, $is_response) = @_;
89             # hstr: headers as a string ref
90             # is_response: bool; is HTTP response (as opposed to request). defaults to request.
91              
92 380         924 my $absoluteURIHost = undef;
93              
94 380         5502 my @lines = split(/\r?\n/, $$hstr_ref);
95              
96 380         1448 $self->{headers} = {};
97 380         1064 $self->{origcase} = {};
98 380         1405 $self->{hdorder} = [];
99 380         972 $self->{method} = undef;
100 380         983 $self->{uri} = undef;
101 380 100       1474 $self->{type} = ($is_response ? "res" : "req");
102 380         2269 Perlbal::objctor($self, $self->{type});
103              
104             # check request line
105 380 100       1124 if ($is_response) {
106 152   50     792 $self->{responseLine} = (shift @lines) || "";
107              
108             # check for valid response line
109 152 50       1641 return fail("Bogus response line") unless
110             $self->{responseLine} =~ m!^HTTP\/(\d+)\.(\d+)\s+(\d+)(?:\s+(.*))$!;
111              
112 152         1114 my ($ver_ma, $ver_mi, $code) = ($1, $2, $3);
113 152         851 $self->code($code, $4);
114              
115             # version work so we know what version the backend spoke
116 152 50       627 unless (defined $ver_ma) {
117 0         0 ($ver_ma, $ver_mi) = (0, 9);
118             }
119 152         633 $self->{ver} = "$ver_ma.$ver_mi";
120 152         552 $self->{vernum} = $ver_ma*1000 + $ver_mi;
121             } else {
122 228   50     1297 $self->{requestLine} = (shift @lines) || "";
123              
124             # check for valid request line
125 228 100       2768 return fail("Bogus request line") unless
126             $self->{requestLine} =~ m!^(\w+) ((?:\*|(?:\S*?)))(?: HTTP/(\d+)\.(\d+))$!;
127              
128 227         1131 $self->{method} = $1;
129 227         1155 $self->{uri} = $2;
130              
131 227         2680 my ($ver_ma, $ver_mi) = ($3, $4);
132              
133             # now check uri for not being a uri
134 227 50       946 if ($self->{uri} =~ m!^http://([^/:]+?)(?::\d+)?(/.*)?$!) {
135 0         0 $absoluteURIHost = lc($1);
136 0   0     0 $self->{uri} = $2 || "/"; # "http://www.foo.com" yields no path, so default to "/"
137             }
138              
139             # default to HTTP/0.9
140 227 50       623 unless (defined $ver_ma) {
141 0         0 ($ver_ma, $ver_mi) = (0, 9);
142             }
143              
144 227         1414 $self->{ver} = "$ver_ma.$ver_mi";
145 227         1076 $self->{vernum} = $ver_ma*1000 + $ver_mi;
146             }
147              
148 379         832 my $last_header = undef;
149 379         1003 foreach my $line (@lines) {
150 1003 50       9042 if ($line =~ /^\s/) {
    50          
151 0 0       0 next unless defined $last_header;
152 0         0 $self->{headers}{$last_header} .= $line;
153             } elsif ($line =~ /^([^\x00-\x20\x7f()<>@,;:\\\"\/\[\]?={}]+):\s*(.*)$/) {
154             # RFC 2616:
155             # sec 4.2:
156             # message-header = field-name ":" [ field-value ]
157             # field-name = token
158             # sec 2.2:
159             # token = 1*
160              
161 1003         2500 $last_header = lc($1);
162 1003 100       3366 if (defined $self->{headers}{$last_header}) {
163 2 50       9 if ($last_header eq "set-cookie") {
164             # cookie spec doesn't allow merged headers for set-cookie,
165             # so instead we do this hack so to_string below does the right
166             # thing without needing to be arrayref-aware or such. also
167             # this lets client code still modify/delete this data
168             # (but retrieving the value of "set-cookie" will be broken)
169 0         0 $self->{headers}{$last_header} .= "\r\nSet-Cookie: $2";
170             } else {
171             # normal merged header case (according to spec)
172 2         9 $self->{headers}{$last_header} .= ", $2";
173             }
174             } else {
175 1001         4343 $self->{headers}{$last_header} = $2;
176 1001         3238 $self->{origcase}{$last_header} = $1;
177 1001         1725 push @{$self->{hdorder}}, $last_header;
  1001         4527  
178             }
179             } else {
180 0         0 return fail("unknown header line");
181             }
182             }
183              
184             # override the host header if an absolute URI was provided
185 379 50       1472 $self->header('Host', $absoluteURIHost)
186             if defined $absoluteURIHost;
187              
188             # now error if no host
189 379 50 100     2928 return fail("HTTP 1.1 requires host header")
      66        
190             if !$is_response && $self->{vernum} >= 1001 && !$self->header('Host');
191              
192 379         4049 return $self;
193             }
194             *new_PERL = \&new;
195              
196             sub _codetext {
197 208     208   455 my Perlbal::HTTPHeaders $self = shift;
198 208 100       1310 return $self->{codetext} if $self->{codetext};
199 82         211 return $self->http_code_english;
200             }
201              
202             sub code {
203 160     160 0 378 my Perlbal::HTTPHeaders $self = shift;
204 160         458 my ($code, $text) = @_;
205 160         655 $self->{codetext} = $text;
206 160 50 66     881 if (! defined $self->{code} || $code != $self->{code}) {
207 160         549 $self->{code} = $code+0;
208 160 50       1946 if ($self->{responseLine}) {
209 160         797 $self->{responseLine} = "HTTP/1.0 $code " . $self->http_code_english;
210             }
211             }
212             }
213              
214             sub response_code {
215 754     754 0 1077 my Perlbal::HTTPHeaders $self = $_[0];
216 754         4966 return $self->{code};
217             }
218              
219             sub request_method {
220 547     547 0 1213 my Perlbal::HTTPHeaders $self = shift;
221 547         4698 return $self->{method};
222             }
223              
224             sub request_uri {
225 182     182 0 366 my Perlbal::HTTPHeaders $self = shift;
226 182         1821 return $self->{uri};
227             }
228              
229             sub set_request_uri {
230 0     0 0 0 my Perlbal::HTTPHeaders $self = shift;
231 0 0       0 return unless $self->{requestLine};
232              
233 0         0 my $uri = shift;
234              
235 0 0 0     0 return unless defined $uri and length $uri;
236              
237 0         0 my $ver = $self->{ver};
238              
239 0 0       0 if ($ver == 0.9) {
240 0         0 $self->{requestLine} = sprintf("%s %s", $self->{method}, $uri);
241             } else {
242 0         0 $self->{requestLine} = sprintf("%s %s HTTP/%s", $self->{method}, $uri, $ver);
243             }
244              
245 0         0 return $self->{uri} = $uri;
246             }
247              
248             sub version_number {
249 488     488 0 984 my Perlbal::HTTPHeaders $self = $_[0];
250 488 50       2776 return $self->{vernum} unless $_[1];
251 0         0 return $self->{vernum} = $_[1];
252             }
253              
254             sub header {
255 4098     4098 0 6714 my Perlbal::HTTPHeaders $self = shift;
256 4098         7283 my $key = shift;
257 4098 100       28622 return $self->{headers}{lc($key)} unless @_;
258              
259             # adding a new header
260 1403         2117 my $origcase = $key;
261 1403         3318 $key = lc($key);
262 1403 100       6750 unless (exists $self->{headers}{$key}) {
263 1105         2799 push @{$self->{hdorder}}, $key;
  1105         4007  
264 1105         3771 $self->{origcase}{$key} = $origcase;
265             }
266              
267 1403         29148 return $self->{headers}{$key} = shift;
268             }
269              
270             sub headers_list {
271 1     1 0 3 my Perlbal::HTTPHeaders $self = shift;
272 1 50       4 return [] unless $self->{headers};
273 1         4 return [ map { $self->{origcase}{$_} } keys %{$self->{headers}} ];
  3         10  
  1         5  
274             }
275              
276             sub to_string_ref {
277 359     359 0 689 my Perlbal::HTTPHeaders $self = shift;
278 1336         6459 my $st = join("\r\n",
279             $self->{requestLine} || $self->{responseLine},
280 1734         5670 (map { "$self->{origcase}{$_}: $self->{headers}{$_}" }
281 359         1063 grep { defined $self->{headers}{$_} }
282 359   66     2373 @{$self->{hdorder}}),
283             '', ''); # final \r\n\r\n
284 359         2420 return \$st;
285             }
286              
287             sub clone {
288 265     265 0 579 my Perlbal::HTTPHeaders $self = shift;
289 265         1064 my $new = fields::new($self);
290 265         43140 foreach (qw(method uri type code codetext ver vernum responseLine requestLine)) {
291 2385         7573 $new->{$_} = $self->{$_};
292             }
293              
294             # mark this object as constructed
295 265         1402 Perlbal::objctor($new, $new->{type});
296              
297 265         507 $new->{headers} = { %{$self->{headers}} };
  265         2353  
298 265         610 $new->{origcase} = { %{$self->{origcase}} };
  265         2803  
299 265         952 $new->{hdorder} = [ @{$self->{hdorder}} ];
  265         1988  
300 265         1406 return $new;
301             }
302              
303             sub set_version {
304 343     343 0 862 my Perlbal::HTTPHeaders $self = shift;
305 343         598 my $ver = shift;
306              
307 343 50       5028 die "Bogus version" unless $ver =~ /^(\d+)\.(\d+)$/;
308 343         1648 my ($ver_ma, $ver_mi) = ($1, $2);
309              
310             # check for req, as the other can be res or httpres
311 343 100       4452 if ($self->{type} eq 'req') {
312 135         799 $self->{requestLine} = "$self->{method} $self->{uri} HTTP/$ver";
313             } else {
314 208         1546 $self->{responseLine} = "HTTP/$ver $self->{code} " . $self->_codetext;
315             }
316 343         1219 $self->{ver} = "$ver_ma.$ver_mi";
317 343         1470 $self->{vernum} = $ver_ma*1000 + $ver_mi;
318 343         980 return $self;
319             }
320              
321             # using all available information, attempt to determine the content length of
322             # the message body being sent to us.
323             sub content_length {
324 344     344 0 819 my Perlbal::HTTPHeaders $self = shift;
325              
326             # shortcuts depending on our method/code, depending on what we are
327 344 100 33     2143 if ($self->{type} eq 'req') {
    50          
328             # no content length for head requests
329 197 100       794 return 0 if $self->{method} eq 'HEAD';
330             } elsif ($self->{type} eq 'res' || $self->{type} eq 'httpres') {
331             # no content length in any of these
332 147 100 66     2324 if ($self->{code} == 304 || $self->{code} == 204 ||
      33        
      66        
333             ($self->{code} >= 100 && $self->{code} <= 199)) {
334 7         30 return 0;
335             }
336             }
337              
338             # the normal case for a GET/POST, etc. real data coming back
339             # also, an OPTIONS requests generally has a defined but 0 content-length
340 336 100       1014 if (defined(my $clen = $self->header("Content-Length"))) {
341 282         5302 return $clen;
342             }
343              
344             # if we get here, nothing matched, so we don't definitively know what the
345             # content length is. this is usually an error, but we try to work around it.
346 54         238 return undef;
347             }
348              
349             # answers the question: "should a response to this person specify keep-alive,
350             # given the request (self) and the backend response?" this is used in proxy
351             # mode to determine based on the client's request and the backend's response
352             # whether or not the response from the proxy (us) should do keep-alive.
353             #
354             # FIXME: this is called too often (especially with service selector),
355             # and should be redesigned to be simpler, and/or cached on the
356             # connection. there's too much duplication with res_keep_alive.
357             sub req_keep_alive {
358 203     203 0 389 my Perlbal::HTTPHeaders $self = $_[0];
359 203 50       711 my Perlbal::HTTPHeaders $res = $_[1] or Carp::confess("ASSERT: No response headers given");
360              
361             # get the connection header now (saves warnings later)
362 203   100     616 my $conn = lc ($self->header('Connection') || '');
363              
364             # check the client
365 203 100       819 if ($self->version_number < 1001) {
366             # they must specify a keep-alive header
367 168 100       1349 return 0 unless $conn =~ /\bkeep-alive\b/i;
368             }
369              
370             # so it must be 1.1 which means keep-alive is on, unless they say not to
371 183 100       1064 return 0 if $conn =~ /\bclose\b/i;
372              
373             # if we get here, the user wants keep-alive and seems to support it,
374             # so we make sure that the response is in a form that we can understand
375             # well enough to do keep-alive. FIXME: support chunked encoding in the
376             # future, which means this check changes.
377 149 0 66     948 return 1 if defined $res->header('Content-length') ||
      33        
      33        
378             $res->response_code == 304 || # not modified
379             $res->response_code == 204 || # no content
380             $self->request_method eq 'HEAD';
381              
382             # fail-safe, no keep-alive
383 0         0 return 0;
384             }
385              
386             # if an options response from a backend looks like it can do keep-alive.
387             sub res_keep_alive_options {
388 1     1 0 4 my Perlbal::HTTPHeaders $self = $_[0];
389 1         5 return $self->res_keep_alive(undef, 1);
390             }
391              
392             # answers the question: "is the backend expected to stay open?" this
393             # is a combination of the request we sent to it and the response they
394             # sent...
395              
396             # FIXME: this is called too often (especially with service selector),
397             # and should be redesigned to be simpler, and/or cached on the
398             # connection. there's too much duplication with req_keep_alive.
399             sub res_keep_alive {
400 359     359 0 643 my Perlbal::HTTPHeaders $self = $_[0];
401 359         614 my Perlbal::HTTPHeaders $req = $_[1];
402 359         525 my $is_options = $_[2];
403 359 50 66     1318 Carp::confess("ASSERT: No request headers given") unless $req || $is_options;
404              
405             # get the connection header now (saves warnings later)
406 359   50     1243 my $conn = lc ($self->header('Connection') || '');
407              
408             # if they said Connection: close, it's always not keep-alive
409 359 100       2792 return 0 if $conn =~ /\bclose\b/i;
410              
411             # handle the http 1.0/0.9 case which requires keep-alive specified
412 285 50       773 if ($self->version_number < 1001) {
413             # must specify keep-alive, and must have a content length OR
414             # the request must be a head request
415 285 50 33     4397 return 1 if
      33        
416             $conn =~ /\bkeep-alive\b/i &&
417             ($is_options ||
418             defined $self->header('Content-length') ||
419             $req->request_method eq 'HEAD' ||
420             $self->response_code == 304 || # not modified
421             $self->response_code == 204
422             ); # no content
423              
424 0         0 return 0;
425             }
426              
427             # HTTP/1.1 case. defaults to keep-alive, per spec, unless
428             # asked for otherwise (checked above)
429             # FIXME: make sure we handle a HTTP/1.1 response from backend
430             # with connection: close, no content-length, going to a
431             # HTTP/1.1 persistent client. we'll have to add chunk markers.
432             # (not here, obviously)
433 0         0 return 1;
434             }
435              
436             # returns (status, range_start, range_end) when given a size
437             # status = 200 - invalid or non-existent range header. serve normally.
438             # status = 206 - parseable range is good. serve partial content.
439             # status = 416 - Range is unsatisfiable
440             sub range {
441 40     40 0 90 my Perlbal::HTTPHeaders $self = $_[0];
442 40         78 my $size = $_[1];
443              
444 40         74 my $not_satisfiable;
445 40         101 my $range = $self->header("Range");
446              
447 40 0 33     281 return 200 unless
      33        
448             $range &&
449             defined $size &&
450             $range =~ /^bytes=(\d*)-(\d*)$/;
451              
452 0         0 my ($range_start, $range_end) = ($1, $2);
453              
454 0 0       0 undef $range_start if $range_start eq '';
455 0 0       0 undef $range_end if $range_end eq '';
456 0 0 0     0 return 200 unless defined($range_start) or defined($range_end);
457              
458 0 0 0     0 if (defined($range_start) and defined($range_end) and $range_start > $range_end) {
    0 0        
    0 0        
      0        
      0        
459 0         0 return 416;
460             } elsif (not defined($range_start) and defined($range_end) and $range_end == 0) {
461 0         0 return 416;
462             } elsif (defined($range_start) and $size <= $range_start) {
463 0         0 return 416;
464             }
465              
466 0 0       0 $range_start = 0 unless defined($range_start);
467 0 0 0     0 $range_end = $size - 1 unless defined($range_end) and $range_end < $size;
468              
469 0         0 return (206, $range_start, $range_end);
470             }
471              
472              
473             sub DESTROY {
474 688     688   2146 my Perlbal::HTTPHeaders $self = shift;
475 688         4400 Perlbal::objdtor($self, $self->{type});
476             }
477              
478             1;
479              
480             # Local Variables:
481             # mode: perl
482             # c-basic-indent: 4
483             # indent-tabs-mode: nil
484             # End: