line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTTP::AnyUA::Util; |
2
|
|
|
|
|
|
|
# ABSTRACT: Utility subroutines for HTTP::AnyUA backends and middleware |
3
|
|
|
|
|
|
|
|
4
|
17
|
|
|
17
|
|
124
|
use warnings; |
|
17
|
|
|
|
|
67
|
|
|
17
|
|
|
|
|
522
|
|
5
|
17
|
|
|
17
|
|
85
|
use strict; |
|
17
|
|
|
|
|
28
|
|
|
17
|
|
|
|
|
614
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.902'; # VERSION |
8
|
|
|
|
|
|
|
|
9
|
17
|
|
|
17
|
|
99
|
use Exporter qw(import); |
|
17
|
|
|
|
|
51
|
|
|
17
|
|
|
|
|
24939
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
13
|
|
|
|
|
|
|
http_headers_to_native |
14
|
|
|
|
|
|
|
native_to_http_request |
15
|
|
|
|
|
|
|
coderef_content_to_string |
16
|
|
|
|
|
|
|
normalize_headers |
17
|
|
|
|
|
|
|
internal_exception |
18
|
|
|
|
|
|
|
http_date |
19
|
|
|
|
|
|
|
parse_http_date |
20
|
|
|
|
|
|
|
uri_escape |
21
|
|
|
|
|
|
|
www_form_urlencode |
22
|
|
|
|
|
|
|
); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
25
|
0
|
|
|
0
|
|
0
|
sub _croak { require Carp; Carp::croak(@_) } |
|
0
|
|
|
|
|
0
|
|
26
|
0
|
|
|
0
|
|
0
|
sub _usage { _croak("Usage: @_\n") } |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
sub coderef_content_to_string { |
30
|
0
|
|
|
0
|
1
|
0
|
my $content = shift; |
31
|
|
|
|
|
|
|
|
32
|
0
|
0
|
|
|
|
0
|
return $content if !$content; |
33
|
|
|
|
|
|
|
|
34
|
0
|
0
|
|
|
|
0
|
if (ref($content) eq 'CODE') { |
35
|
|
|
|
|
|
|
# drain the request body |
36
|
0
|
|
|
|
|
0
|
my $body = ''; |
37
|
0
|
|
|
|
|
0
|
while (my $chunk = $content->()) { |
38
|
0
|
|
|
|
|
0
|
$body .= $chunk; |
39
|
|
|
|
|
|
|
} |
40
|
0
|
|
|
|
|
0
|
$content = $body; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
0
|
|
|
|
|
0
|
return $content; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub native_to_http_request { |
48
|
0
|
|
|
0
|
1
|
0
|
my $method = shift; |
49
|
0
|
|
|
|
|
0
|
my $url = shift; |
50
|
0
|
|
0
|
|
|
0
|
my $args = shift || {}; |
51
|
|
|
|
|
|
|
|
52
|
0
|
|
|
|
|
0
|
my $headers = []; |
53
|
0
|
|
|
|
|
0
|
my $content = $args->{content}; # works as either scalar or coderef |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# flatten headers |
56
|
0
|
0
|
|
|
|
0
|
for my $header (keys %{$args->{headers} || {}}) { |
|
0
|
|
|
|
|
0
|
|
57
|
0
|
|
|
|
|
0
|
my $value = $args->{headers}{$header}; |
58
|
0
|
0
|
|
|
|
0
|
my @values = ref($value) eq 'ARRAY' ? @$value : ($value); |
59
|
0
|
|
|
|
|
0
|
for my $v (@values) { |
60
|
0
|
|
|
|
|
0
|
push @$headers, ($header => $v); |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
0
|
|
|
|
|
0
|
require HTTP::Request; |
65
|
0
|
|
|
|
|
0
|
return HTTP::Request->new($method, $url, $headers, $content); |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub http_headers_to_native { |
70
|
0
|
|
|
0
|
1
|
0
|
my $http_headers = shift; |
71
|
|
|
|
|
|
|
|
72
|
0
|
|
|
|
|
0
|
my $native; |
73
|
|
|
|
|
|
|
|
74
|
0
|
|
|
|
|
0
|
for my $header ($http_headers->header_field_names) { |
75
|
0
|
|
|
|
|
0
|
my @values = $http_headers->header($header); |
76
|
0
|
0
|
|
|
|
0
|
$native->{lc($header)} = @values == 1 ? $values[0] : [@values]; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
0
|
|
|
|
|
0
|
return $native; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub normalize_headers { |
84
|
9
|
|
|
9
|
1
|
19
|
my $headers_in = shift; |
85
|
|
|
|
|
|
|
|
86
|
9
|
|
|
|
|
16
|
my $headers = {}; |
87
|
|
|
|
|
|
|
|
88
|
9
|
100
|
|
|
|
27
|
if (defined $headers_in) { |
89
|
5
|
50
|
|
|
|
9
|
while (my ($key, $value) = each %{$headers_in || {}}) { |
|
12
|
|
|
|
|
45
|
|
90
|
7
|
|
|
|
|
21
|
$headers->{lc($key)} = $value; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
9
|
|
|
|
|
28
|
return $headers; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
sub internal_exception { |
99
|
0
|
0
|
|
0
|
1
|
0
|
my $e = shift or _usage(q{internal_exception($exception)}); |
100
|
0
|
|
0
|
|
|
0
|
my $resp = shift || {}; |
101
|
|
|
|
|
|
|
|
102
|
0
|
|
|
|
|
0
|
$e = "$e"; |
103
|
|
|
|
|
|
|
|
104
|
0
|
0
|
|
|
|
0
|
$resp->{headers}{'client-original-status'} = $resp->{status} if $resp->{status}; |
105
|
0
|
0
|
|
|
|
0
|
$resp->{headers}{'client-original-reason'} = $resp->{reason} if $resp->{reason}; |
106
|
|
|
|
|
|
|
|
107
|
0
|
|
|
|
|
0
|
$resp->{success} = ''; |
108
|
0
|
|
|
|
|
0
|
$resp->{status} = 599; |
109
|
0
|
|
|
|
|
0
|
$resp->{reason} = 'Internal Exception'; |
110
|
0
|
|
|
|
|
0
|
$resp->{content} = $e; |
111
|
0
|
|
|
|
|
0
|
$resp->{headers}{'content-type'} = 'text/plain'; |
112
|
0
|
|
|
|
|
0
|
$resp->{headers}{'content-length'} = length $e; |
113
|
|
|
|
|
|
|
|
114
|
0
|
|
|
|
|
0
|
return $resp; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# adapted from HTTP/Tiny.pm |
119
|
|
|
|
|
|
|
sub split_url { |
120
|
0
|
0
|
|
0
|
1
|
0
|
my $url = shift or _usage(q{split_url($url)}); |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# URI regex adapted from the URI module |
123
|
0
|
0
|
|
|
|
0
|
my ($scheme, $host, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> |
124
|
|
|
|
|
|
|
or die(qq/Cannot parse URL: '$url'\n/); |
125
|
|
|
|
|
|
|
|
126
|
0
|
|
|
|
|
0
|
$scheme = lc $scheme; |
127
|
0
|
0
|
|
|
|
0
|
$path_query = "/$path_query" unless $path_query =~ m<\A/>; |
128
|
|
|
|
|
|
|
|
129
|
0
|
|
|
|
|
0
|
my $auth = ''; |
130
|
0
|
0
|
|
|
|
0
|
if ( (my $i = index $host, '@') != -1 ) { |
131
|
|
|
|
|
|
|
# user:pass@host |
132
|
0
|
|
|
|
|
0
|
$auth = substr $host, 0, $i, ''; # take up to the @ for auth |
133
|
0
|
|
|
|
|
0
|
substr $host, 0, 1, ''; # knock the @ off the host |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# userinfo might be percent escaped, so recover real auth info |
136
|
0
|
|
|
|
|
0
|
$auth =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; |
|
0
|
|
|
|
|
0
|
|
137
|
|
|
|
|
|
|
} |
138
|
0
|
0
|
0
|
|
|
0
|
my $port = $host =~ s/:(\d*)\z// && length $1 ? $1 |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
139
|
|
|
|
|
|
|
: $scheme eq 'http' ? 80 |
140
|
|
|
|
|
|
|
: $scheme eq 'https' ? 443 |
141
|
|
|
|
|
|
|
: undef; |
142
|
|
|
|
|
|
|
|
143
|
0
|
0
|
|
|
|
0
|
return ($scheme, (length $host ? lc $host : "localhost") , $port, $path_query, $auth); |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Date conversions adapted from HTTP::Date |
148
|
|
|
|
|
|
|
# adapted from HTTP/Tiny.pm |
149
|
|
|
|
|
|
|
my $DoW = 'Sun|Mon|Tue|Wed|Thu|Fri|Sat'; |
150
|
|
|
|
|
|
|
my $MoY = 'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec'; |
151
|
|
|
|
|
|
|
sub http_date { |
152
|
0
|
0
|
|
0
|
1
|
0
|
my $time = shift or _usage(q{http_date($time)}); |
153
|
0
|
|
|
|
|
0
|
my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($time); |
154
|
0
|
|
|
|
|
0
|
return sprintf('%s, %02d %s %04d %02d:%02d:%02d GMT', |
155
|
|
|
|
|
|
|
substr($DoW,$wday*4,3), |
156
|
|
|
|
|
|
|
$mday, substr($MoY,$mon*4,3), $year+1900, |
157
|
|
|
|
|
|
|
$hour, $min, $sec |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# adapted from HTTP/Tiny.pm |
163
|
|
|
|
|
|
|
sub parse_http_date { |
164
|
0
|
0
|
|
0
|
1
|
0
|
my $str = shift or _usage(q{parse_http_date($str)}); |
165
|
0
|
|
|
|
|
0
|
my @tl_parts; |
166
|
0
|
0
|
|
|
|
0
|
if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
167
|
0
|
|
|
|
|
0
|
@tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) { |
170
|
0
|
|
|
|
|
0
|
@tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) { |
173
|
0
|
|
|
|
|
0
|
@tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6); |
174
|
|
|
|
|
|
|
} |
175
|
0
|
|
|
|
|
0
|
require Time::Local; |
176
|
0
|
|
|
|
|
0
|
return eval { |
177
|
0
|
0
|
|
|
|
0
|
my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1; |
178
|
0
|
0
|
|
|
|
0
|
$t < 0 ? undef : $t; |
179
|
|
|
|
|
|
|
}; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# URI escaping adapted from URI::Escape |
184
|
|
|
|
|
|
|
# c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 |
185
|
|
|
|
|
|
|
# perl 5.6 ready UTF-8 encoding adapted from JSON::PP |
186
|
|
|
|
|
|
|
# adapted from HTTP/Tiny.pm |
187
|
|
|
|
|
|
|
my %escapes = map { chr($_) => sprintf('%%%02X', $_) } 0..255; |
188
|
|
|
|
|
|
|
$escapes{' '} = '+'; |
189
|
|
|
|
|
|
|
my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/; |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub uri_escape { |
192
|
4
|
50
|
|
4
|
1
|
10
|
my $str = shift or _usage(q{uri_escape($str)}); |
193
|
4
|
50
|
|
|
|
10
|
if ($] ge '5.008') { |
194
|
4
|
|
|
|
|
10
|
utf8::encode($str); |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
else { |
197
|
|
|
|
|
|
|
$str = pack('U*', unpack('C*', $str)) # UTF-8 encode a byte string |
198
|
17
|
0
|
|
17
|
|
10388
|
if (length $str == do { use bytes; length $str }); |
|
17
|
|
|
|
|
247
|
|
|
17
|
|
|
|
|
101
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
199
|
0
|
|
|
|
|
0
|
$str = pack('C*', unpack('C*', $str)); # clear UTF-8 flag |
200
|
|
|
|
|
|
|
} |
201
|
4
|
|
|
|
|
42
|
$str =~ s/($unsafe_char)/$escapes{$1}/ge; |
|
0
|
|
|
|
|
0
|
|
202
|
4
|
|
|
|
|
16
|
return $str; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# adapted from HTTP/Tiny.pm |
207
|
|
|
|
|
|
|
sub www_form_urlencode { |
208
|
1
|
|
|
1
|
1
|
2
|
my $data = shift; |
209
|
1
|
50
|
33
|
|
|
7
|
($data && ref $data) |
210
|
|
|
|
|
|
|
or _usage(q{www_form_urlencode($dataref)}); |
211
|
1
|
50
|
33
|
|
|
5
|
(ref $data eq 'HASH' || ref $data eq 'ARRAY') |
212
|
|
|
|
|
|
|
or _croak("form data must be a hash or array reference\n"); |
213
|
|
|
|
|
|
|
|
214
|
1
|
50
|
|
|
|
8
|
my @params = ref $data eq 'HASH' ? %$data : @$data; |
215
|
1
|
50
|
|
|
|
5
|
@params % 2 == 0 |
216
|
|
|
|
|
|
|
or _croak("form data reference must have an even number of terms\n"); |
217
|
|
|
|
|
|
|
|
218
|
1
|
|
|
|
|
2
|
my @terms; |
219
|
1
|
|
|
|
|
3
|
while (@params) { |
220
|
2
|
|
|
|
|
7
|
my ($key, $value) = splice(@params, 0, 2); |
221
|
2
|
50
|
|
|
|
6
|
if (ref $value eq 'ARRAY') { |
222
|
0
|
|
|
|
|
0
|
unshift @params, map { $key => $_ } @$value; |
|
0
|
|
|
|
|
0
|
|
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
else { |
225
|
2
|
|
|
|
|
5
|
push @terms, join('=', map { uri_escape($_) } $key, $value); |
|
4
|
|
|
|
|
11
|
|
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
1
|
50
|
|
|
|
19
|
return join('&', ref($data) eq 'ARRAY' ? @terms : sort @terms); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
1; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
__END__ |