File Coverage

blib/lib/HTTP/XSHeaders.pm
Criterion Covered Total %
statement 135 148 91.2
branch 49 64 76.5
condition 13 21 61.9
subroutine 31 35 88.5
pod 13 15 86.6
total 241 283 85.1


line stmt bran cond sub pod time code
1             package HTTP::XSHeaders;
2 12     12   1317137 use strict;
  12         22  
  12         409  
3 12     12   54 use warnings;
  12         64  
  12         653  
4 12     12   60 use XSLoader;
  12         22  
  12         655  
5              
6             our $VERSION = '0.500000';
7              
8             eval {
9             require HTTP::Headers::Fast;
10              
11 12     12   69 no warnings qw;
  12         35  
  12         1378  
12             # HTTP::Headers::Fast
13             *HTTP::Headers::Fast::new = *HTTP::XSHeaders::new;
14              
15             my %fast_orig;
16             my @fast_methods = qw(
17             clone header _header clear push_header init_header remove_header
18             remove_content_headers as_string as_string_without_sort header_field_names
19             psgi_flatten psgi_flatten_without_sort scan
20             );
21              
22             for my $meth (@fast_methods) {
23             $fast_orig{$meth} = HTTP::Headers::Fast->can($meth);
24 12     12   62 no strict 'refs'; ## no critic
  12         24  
  12         2639  
25             *{ "HTTP::Headers::Fast::$meth" } = sub {
26             HTTP::XSHeaders::_dispatch_xs_or_orig(
27             shift,
28 4         21 \&{ "HTTP::XSHeaders::$meth" },
29 4     4   177527 $fast_orig{$meth},
30             @_,
31             );
32             };
33             }
34              
35             # Implemented in Pure-Perl
36             # (candidates to move to XS)
37             *HTTP::Headers::Fast::_date_header = *HTTP::XSHeaders::_date_header;
38             *HTTP::Headers::Fast::content_type = *HTTP::XSHeaders::content_type;
39             *HTTP::Headers::Fast::content_type_charset = *HTTP::XSHeaders::content_type_charset;
40             *HTTP::Headers::Fast::referer = *HTTP::XSHeaders::referer;
41             *HTTP::Headers::Fast::referrer = *HTTP::XSHeaders::referer;
42             *HTTP::Headers::Fast::_basic_auth = *HTTP::XSHeaders::_basic_auth;
43             };
44              
45             eval {
46             require HTTP::Headers;
47              
48 12     12   69 no warnings qw;
  12         35  
  12         1092  
49             # HTTP::Headers
50             *HTTP::Headers::new = *HTTP::XSHeaders::new;
51              
52             my %headers_orig;
53             my @headers_methods = qw(
54             clone header _header clear push_header init_header remove_header
55             remove_content_headers as_string header_field_names psgi_flatten
56             psgi_flatten_without_sort scan
57             );
58              
59             for my $meth (@headers_methods) {
60             $headers_orig{$meth} = HTTP::Headers->can($meth);
61 12     12   77 no strict 'refs'; ## no critic
  12         37  
  12         2045  
62             *{ "HTTP::Headers::$meth" } = sub {
63             HTTP::XSHeaders::_dispatch_xs_or_orig(
64             shift,
65 4         14 \&{ "HTTP::XSHeaders::$meth" },
66 4     4   19 $headers_orig{$meth},
67             @_,
68             );
69             };
70             }
71              
72             # Implemented in Pure-Perl
73             *HTTP::Headers::_date_header = *HTTP::XSHeaders::_date_header;
74             *HTTP::Headers::content_type = *HTTP::XSHeaders::content_type;
75             *HTTP::Headers::content_type_charset = *HTTP::XSHeaders::content_type_charset;
76             *HTTP::Headers::referer = *HTTP::XSHeaders::referer;
77             *HTTP::Headers::referrer = *HTTP::XSHeaders::referer;
78             *HTTP::Headers::_basic_auth = *HTTP::XSHeaders::_basic_auth;
79             };
80              
81             XSLoader::load( 'HTTP::XSHeaders', $VERSION );
82              
83             {
84 12     12   80 no warnings qw;
  12         46  
  12         1353  
85             for my $key (qw/content-length content-language content-encoding title user-agent server from warnings www-authenticate authorization proxy-authenticate proxy-authorization/) {
86             (my $meth = $key) =~ s/-/_/g;
87 12     12   86 no strict 'refs'; ## no critic
  12         30  
  12         1880  
88 24     24   564 *{$meth} = sub { (shift->header($key, @_))[0] };
89              
90             *{ "HTTP::Headers::$meth" } = sub {
91 0     0   0 (shift->header($key, @_))[0];
92             };
93              
94             *{ "HTTP::Headers::Fast::$meth" } = sub {
95 0     0   0 (shift->header($key, @_))[0];
96             };
97             }
98             }
99              
100 12     12   244 use 5.00800;
  12         45  
101 12     12   57 use Carp ();
  12         18  
  12         25899  
102              
103             sub _dispatch_xs_or_orig {
104 8     8   13 my ( $self, $xs, $orig, @args ) = @_;
105 8 50 33     20 if ( HTTP::XSHeaders::_is_xsheaders($self) || !$orig ) {
106 8         12 @_ = ( $self, @args );
107 8         62 goto &$xs;
108             }
109 0         0 @_ = ( $self, @args );
110 0         0 goto &$orig;
111             }
112              
113             sub _date_header {
114 16     16   831 require HTTP::Date;
115 16         6946 my ( $self, $header, $time ) = @_;
116 16         27 my $old;
117 16 100       42 if ( defined $time ) {
118 5         19 ($old) = $self->header($header, HTTP::Date::time2str($time));
119             } else {
120 11         64 ($old) = $self->header($header);
121             }
122 16 100       189 $old =~ s/;.*// if defined($old);
123 16         47 HTTP::Date::str2time($old);
124             }
125              
126             sub content_type {
127 20     20 1 626 my $self = shift;
128 20         104 my $ct = $self->header('content-type');
129 20 100       138 $self->header('content-type', shift) if @_;
130 20 50       49 $ct = $ct->[0] if ref($ct) eq 'ARRAY';
131 20 100 66     108 return '' unless defined($ct) && length($ct);
132 16         62 my @ct = split( /;\s*/, $ct, 2 );
133 16         34 for ( $ct[0] ) {
134 16         53 s/\s+//g;
135 16         42 $_ = lc($_);
136             }
137 16 100       80 wantarray ? @ct : $ct[0];
138             }
139              
140             # This is copied here because it is not a method
141             sub _split_header_words
142             {
143 2     2   5 my(@val) = @_;
144 2         3 my @res;
145 2         3 for (@val) {
146 2         3 my @cur;
147 2         12 while (length) {
148 3 100 33     33 if (s/^\s*(=*[^\s=;,]+)//) { # 'token' or parameter 'attribute'
    50          
    50          
149 2         6 push(@cur, $1);
150             # a quoted value
151 2 100       11 if (s/^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"//) {
    50          
152 1         2 my $val = $1;
153 1         2 $val =~ s/\\(.)/$1/g;
154 1         3 push(@cur, $val);
155             # some unquoted value
156             }
157             elsif (s/^\s*=\s*([^;,\s]*)//) {
158 0         0 my $val = $1;
159 0         0 $val =~ s/\s+$//;
160 0         0 push(@cur, $val);
161             # no value, a lone token
162             }
163             else {
164 1         11 push(@cur, undef);
165             }
166             }
167             elsif (s/^\s*,//) {
168 0 0       0 push(@res, [@cur]) if @cur;
169 0         0 @cur = ();
170             }
171             elsif (s/^\s*;// || s/^\s+//) {
172             # continue
173             }
174             else {
175 0         0 die "This should not happen: '$_'";
176             }
177             }
178 2 100       6 push(@res, \@cur) if @cur;
179             }
180              
181 2         2 for my $arr (@res) {
182 1         3 for (my $i = @$arr - 2; $i >= 0; $i -= 2) {
183 2         7 $arr->[$i] = lc($arr->[$i]);
184             }
185             }
186 2         4 return @res;
187             }
188              
189             sub content_type_charset {
190 2     2 1 141420 my $self = shift;
191 2         8 my $h = $self->header('content-type');
192 2 50       6 $h = $h->[0] if ref($h);
193 2 100       5 $h = "" unless defined $h;
194 2         5 my @v = _split_header_words($h);
195 2 100       4 if (@v) {
196 1         2 my($ct, undef, %ct_param) = @{$v[0]};
  1         3  
197 1         2 my $charset = $ct_param{charset};
198 1 50       3 if ($ct) {
199 1         8 $ct = lc($ct);
200 1         2 $ct =~ s/\s+//;
201             }
202 1 50       2 if ($charset) {
203 1         2 $charset = uc($charset);
204 1         3 $charset =~ s/^\s+//; $charset =~ s/\s+\z//;
  1         2  
205 1 50       2 undef($charset) if $charset eq "";
206             }
207 1 50       2 return $ct, $charset if wantarray;
208 1         5 return $charset;
209             }
210 1 50       2 return undef, undef if wantarray; ## no critic
211 1         4 return undef; ## no critic
212             }
213              
214             sub referer {
215 8     8 1 20287 my $self = shift;
216 8 100 100     117 if ( @_ && $_[0] =~ /#/ ) {
217              
218             # Strip fragment per RFC 2616, section 14.36.
219 2         16 my $uri = shift;
220 2 100       22 if ( ref($uri) ) {
221 1         11 require URI;
222 1         9 $uri = $uri->clone;
223 1         14 $uri->fragment(undef);
224             }
225             else {
226 1         8 $uri =~ s/\#.*//;
227             }
228 2         28 unshift @_, $uri;
229             }
230 8         95 ( $self->header( 'Referer', @_ ) )[0];
231             }
232              
233             *referrer = \&referer;
234              
235 7     7 1 27 sub authorization_basic { shift->_basic_auth( "Authorization", @_ ) }
236             sub proxy_authorization_basic {
237 2     2 1 109 shift->_basic_auth( "Proxy-Authorization", @_ );
238             }
239              
240             sub _basic_auth {
241 9     9   723 require MIME::Base64;
242 9         1009 my ( $self, $h, $user, $passwd ) = @_;
243 9         54 my ($old) = $self->header($h);
244 9 100       44 if ( defined $user ) {
245 4 100       289 Carp::croak("Basic authorization user name can't contain ':'")
246             if $user =~ /:/;
247 3 100       9 $passwd = '' unless defined $passwd;
248 3         43 $self->header(
249             $h => 'Basic ' . MIME::Base64::encode( "$user:$passwd", '' ) );
250             }
251 8 100 66     63 if ( defined $old && $old =~ s/^\s*Basic\s+// ) {
252 5         16 my $val = MIME::Base64::decode($old);
253 5 100       36 return $val unless wantarray;
254 3         23 return split( /:/, $val, 2 );
255             }
256 3         18 return;
257             }
258              
259 3     3 1 222857 sub date { shift->_date_header( 'date', @_ ); }
260 3     3 1 475 sub expires { shift->_date_header( 'expires', @_ ); }
261 4     4 1 3760 sub if_modified_since { shift->_date_header( 'if-modified-since', @_ ); }
262 3     3 1 459 sub if_unmodified_since { shift->_date_header( 'if-unmodified-since', @_ ); }
263 3     3 1 456 sub last_modified { shift->_date_header( 'last-modified', @_ ); }
264              
265             # This is used as a private LWP extension. The Client-Date header is
266             # added as a timestamp to a response when it has been received.
267 0     0 0 0 sub client_date { shift->_date_header( 'client-date', @_ ); }
268              
269             sub content_is_text {
270 0     0 0 0 my $self = shift;
271 0         0 return $self->content_type =~ m{^text/};
272             }
273              
274             sub content_is_html {
275 2     2 1 110 my $self = shift;
276 2   66     6 return $self->content_type eq 'text/html' || $self->content_is_xhtml;
277             }
278              
279             sub content_is_xhtml {
280 3     3 1 9 my $ct = shift->content_type;
281 3   66     24 return $ct eq "application/xhtml+xml"
282             || $ct eq "application/vnd.wap.xhtml+xml";
283             }
284              
285             sub content_is_xml {
286 2     2 1 7 my $ct = shift->content_type;
287 2 50       6 return 1 if $ct eq "text/xml";
288 2 50       5 return 1 if $ct eq "application/xml";
289 2 100       26 return 1 if $ct =~ /\+xml$/;
290 1         5 return 0;
291             }
292              
293             1;
294              
295             __END__