line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Leyland::Negotiator; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Performs HTTP negotiations for Leyland requests |
4
|
|
|
|
|
|
|
|
5
|
2
|
|
|
2
|
|
13
|
use strict; |
|
2
|
|
|
|
|
14
|
|
|
2
|
|
|
|
|
84
|
|
6
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
69
|
|
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
9
|
use Carp; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4424
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Leyland::Negotiator - Performs HTTP negotiations for Leyland requests |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 SYNOPSIS |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# used internally |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 DESCRIPTION |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
This module performs HTTP negotiations for L requests. When a request |
21
|
|
|
|
|
|
|
is handled by a Leyland application, it is first negotiated by this module |
22
|
|
|
|
|
|
|
to make sure it can be handled, and to decide on how to handle it. |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
The following negotiations are performed: |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=over |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=item 1. Character set negotiation - Leyland only supports UTF-8, so if |
29
|
|
|
|
|
|
|
the request defines a different character set, a 400 Bad Request error |
30
|
|
|
|
|
|
|
is thrown. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=item 2. Path negotiation - The request path is compared against the application's |
33
|
|
|
|
|
|
|
routes, and a list of routes is created. If none are found, a 404 Not Found |
34
|
|
|
|
|
|
|
error is thrown. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=item 3. Request method negotiation - The list of routes is filtered |
37
|
|
|
|
|
|
|
by the request method (GET, POST, etc.), so only routes of this method |
38
|
|
|
|
|
|
|
remain. If none remain, a 405 Method Not Allowed error is thrown. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=item 4. Received content type negotiation - The list of routes is filtered |
41
|
|
|
|
|
|
|
by the request content type (text/html for example), if it has any, so only |
42
|
|
|
|
|
|
|
routes that accept this media type remain. If none remain, a 415 Unsupported |
43
|
|
|
|
|
|
|
Media Type error is thrown. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=item 5. Returned content type negotiation - The list of routes is filtered |
46
|
|
|
|
|
|
|
by the request accepted media types (residing in the Accept HTTP header), |
47
|
|
|
|
|
|
|
if defined, so only routes that return a media type accepted by the client |
48
|
|
|
|
|
|
|
remain. If none remain, a 406 Not Acceptable error is thrown. |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=back |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
There's one thing this method doesn't perform, and that's language negotiation. |
53
|
|
|
|
|
|
|
Since proper HTTP language negotiation is rare (and difficult to implement), |
54
|
|
|
|
|
|
|
you are expect to perform that yourself (only if you wish, of course). |
55
|
|
|
|
|
|
|
For that, L is provided. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
This module also finds routes that match a path when an HTTP OPTIONS request |
58
|
|
|
|
|
|
|
is received. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=head1 CLASS METHODS |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head2 negotiate( $c, $app_routes, $path ) |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
Performs a series of HTTP negotiations on the request and returns matching |
65
|
|
|
|
|
|
|
routes. If none are found, an error is thrown. See L"DESCRIPTION"> for |
66
|
|
|
|
|
|
|
more information. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=cut |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub negotiate { |
71
|
0
|
|
|
0
|
1
|
|
my ($class, $c, $app_routes, $path) = @_; |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# 1. CHARACTER SET NEGOTIATION |
74
|
|
|
|
|
|
|
# -------------------------------------------------------------- |
75
|
|
|
|
|
|
|
# Leyland only supports UTF-8 character encodings, so let's check |
76
|
|
|
|
|
|
|
# the client supports that. If not, let's return an error |
77
|
0
|
|
|
|
|
|
$c->log->debug('Negotiating character set.'); |
78
|
0
|
0
|
|
|
|
|
Leyland::Negotiator->_negotiate_charset($c) |
79
|
|
|
|
|
|
|
|| $c->exception({ code => 400, error => "This server only supports the UTF-8 character set, unfortunately we are unable to fulfil your request." }); |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# 2. PATH NEGOTIATION |
82
|
|
|
|
|
|
|
# -------------------------------------------------------------- |
83
|
|
|
|
|
|
|
# let's find all possible prefix/route combinations |
84
|
|
|
|
|
|
|
# from the request path, and then find all routes matching |
85
|
|
|
|
|
|
|
# the request path |
86
|
0
|
|
|
|
|
|
my $routes = []; |
87
|
0
|
|
0
|
|
|
|
$path ||= $c->path; |
88
|
0
|
|
|
|
|
|
$routes = $class->_negotiate_path($c, { app_routes => $app_routes, path => $path }); |
89
|
0
|
0
|
|
|
|
|
$c->exception({ code => 404 }) unless scalar @$routes; |
90
|
|
|
|
|
|
|
|
91
|
0
|
|
|
|
|
|
$c->log->debug('Found '.scalar(@$routes).' routes matching '.$path); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# 3. REQUEST METHOD NEGOTIATION |
94
|
|
|
|
|
|
|
# -------------------------------------------------------------- |
95
|
|
|
|
|
|
|
# weed out routes that do not match request method |
96
|
0
|
|
|
|
|
|
$c->log->debug('Negotiating request method.'); |
97
|
0
|
|
|
|
|
|
$routes = $class->_negotiate_method($c->method, $routes); |
98
|
0
|
0
|
|
|
|
|
$c->exception({ code => 405 }) unless scalar @$routes; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# 4. RECEIVED CONTENT TYPE NEGOTIATION |
101
|
|
|
|
|
|
|
# -------------------------------------------------------------- |
102
|
|
|
|
|
|
|
# weed out all routes that do not accept the media type that the |
103
|
|
|
|
|
|
|
# client used for the request |
104
|
0
|
|
|
|
|
|
$c->log->debug('Negotiating media type received.'); |
105
|
0
|
|
|
|
|
|
$routes = $class->_negotiate_receive_media($c, $routes); |
106
|
0
|
0
|
|
|
|
|
$c->exception({ code => 415 }) unless scalar @$routes; |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# 5. RETURNED CONTENT TYPE NEGOTIATION |
109
|
|
|
|
|
|
|
# -------------------------------------------------------------- |
110
|
|
|
|
|
|
|
# weed out all routes that do not return any media type |
111
|
|
|
|
|
|
|
# the client accepts |
112
|
0
|
|
|
|
|
|
$c->log->debug('Negotiating media type returned.'); |
113
|
0
|
|
|
|
|
|
$routes = $class->_negotiate_return_media($c, $routes); |
114
|
0
|
0
|
|
|
|
|
$c->exception({ code => 406 }) unless scalar @$routes; |
115
|
|
|
|
|
|
|
|
116
|
0
|
|
|
|
|
|
return $routes; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=head2 find_options( $c, $app_routes ) |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Finds all routes that match a certain path when an HTTP OPTIONS request |
122
|
|
|
|
|
|
|
is received. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=cut |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
sub find_options { |
127
|
0
|
|
|
0
|
1
|
|
my ($class, $c, $app_routes) = @_; |
128
|
|
|
|
|
|
|
|
129
|
0
|
|
|
|
|
|
my $routes = $class->matching_routes($app_routes, $class->prefs_and_routes($c->path)); |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# have we found any matching routes? |
132
|
0
|
0
|
|
|
|
|
$c->exception({ code => 404 }) unless scalar @$routes; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# okay, we have, let's see which HTTP methods are supported by |
135
|
|
|
|
|
|
|
# these routes |
136
|
0
|
|
|
|
|
|
my %meths = ( 'OPTIONS' => 1 ); |
137
|
0
|
|
|
|
|
|
foreach (@$routes) { |
138
|
0
|
|
|
|
|
|
$meths{$class->method_name($_->{method})} = 1; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
0
|
|
|
|
|
|
return sort keys %meths; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head2 method_name( $meth ) |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
Receives the name of a Leyland-style HTTP method (like 'get', 'post', |
147
|
|
|
|
|
|
|
'put' or 'del') and returns the correct HTTP name of it (like 'GET', 'POST', |
148
|
|
|
|
|
|
|
'PUT' or 'DELETE'). |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=cut |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub method_name { |
153
|
0
|
|
|
0
|
1
|
|
my ($class, $meth) = @_; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# replace 'del' with 'delete' |
156
|
0
|
0
|
|
|
|
|
$meth = 'delete' if $meth eq 'del'; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# return this in uppercase |
159
|
0
|
|
|
|
|
|
return uc($meth); |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub _negotiate_path { |
163
|
0
|
|
|
0
|
|
|
my ($class, $c, $args) = @_; |
164
|
|
|
|
|
|
|
|
165
|
0
|
|
0
|
|
|
|
$args->{path} ||= $c->path; |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# let's find all possible prefix/route combinations |
168
|
|
|
|
|
|
|
# from the request path and then find all routes matching the request path |
169
|
0
|
|
|
|
|
|
my $routes = $class->_matching_routes($args->{app_routes}, $class->_prefs_and_routes($args->{path}), $args->{internal}); |
170
|
|
|
|
|
|
|
|
171
|
0
|
0
|
|
|
|
|
if ($args->{method}) { |
172
|
0
|
|
|
|
|
|
return $class->_negotiate_method($args->{method}, $routes); |
173
|
|
|
|
|
|
|
} else { |
174
|
0
|
|
|
|
|
|
return $routes; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub _prefs_and_routes { |
179
|
0
|
|
|
0
|
|
|
my ($class, $path) = @_; |
180
|
|
|
|
|
|
|
|
181
|
0
|
|
|
|
|
|
my $pref_routes = [{ prefix => '', route => $path }]; |
182
|
0
|
|
|
|
|
|
my ($prefix) = ($path =~ m!^(/[^/]+)!); |
183
|
0
|
|
0
|
|
|
|
my $route = $' || '/'; |
184
|
0
|
|
|
|
|
|
my $i = 0; # counter to prevent infinite loops, probably should be removed |
185
|
0
|
|
0
|
|
|
|
while ($prefix && $i < 1000) { |
186
|
0
|
|
|
|
|
|
push(@$pref_routes, { prefix => $prefix, route => $route }); |
187
|
|
|
|
|
|
|
|
188
|
0
|
|
|
|
|
|
my ($suffix) = ($route =~ m!^(/[^/]+)!); |
189
|
0
|
0
|
|
|
|
|
last unless $suffix; |
190
|
0
|
|
|
|
|
|
$prefix .= $suffix; |
191
|
0
|
|
0
|
|
|
|
$route = $' || '/'; |
192
|
0
|
|
|
|
|
|
$i++; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
|
return $pref_routes; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub _matching_routes { |
199
|
0
|
|
|
0
|
|
|
my ($class, $app_routes, $pref_routes, $internal) = @_; |
200
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
|
my $routes = []; |
202
|
0
|
|
|
|
|
|
foreach (@$pref_routes) { |
203
|
0
|
|
0
|
|
|
|
my $pref_name = $_->{prefix} || '_root_'; |
204
|
|
|
|
|
|
|
|
205
|
0
|
0
|
|
|
|
|
next unless $app_routes->EXISTS($pref_name); |
206
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
|
my $pref_routes = $app_routes->FETCH($pref_name); |
208
|
|
|
|
|
|
|
|
209
|
0
|
0
|
|
|
|
|
next unless $pref_routes; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# find matching routes in this prefix |
212
|
0
|
|
|
|
|
|
ROUTE: foreach my $r ($pref_routes->Keys) { |
213
|
|
|
|
|
|
|
# does the requested route match the current route? |
214
|
0
|
0
|
|
|
|
|
next unless my @captures = ($_->{route} =~ m/$r/); |
215
|
|
|
|
|
|
|
|
216
|
0
|
0
|
0
|
|
|
|
shift @captures if scalar @captures == 1 && $captures[0] eq '1'; |
217
|
|
|
|
|
|
|
|
218
|
0
|
|
|
|
|
|
my $route_meths = $pref_routes->FETCH($r); |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# find all routes that support the request method (i.e. GET, POST, etc.) |
221
|
0
|
0
|
|
|
|
|
METH: foreach my $m (sort { $a eq 'any' || $b eq 'any' } keys %$route_meths) { |
|
0
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
# do not match internal routes |
223
|
0
|
0
|
|
|
|
|
RULE: foreach my $rule (@{$route_meths->{$m}->{rules}->{is} || []}) { |
|
0
|
|
|
|
|
|
|
224
|
0
|
0
|
0
|
|
|
|
next METH if $rule eq 'internal' && !$internal; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# okay, add this route |
228
|
0
|
|
|
|
|
|
push(@$routes, { method => $m, class => $route_meths->{$m}->{class}, prefix => $_->{prefix}, route => $r, code => $route_meths->{$m}->{code}, rules => $route_meths->{$m}->{rules}, captures => \@captures }); |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
|
return $routes; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub _negotiate_method { |
237
|
0
|
|
|
0
|
|
|
my ($class, $method, $routes) = @_; |
238
|
|
|
|
|
|
|
|
239
|
0
|
0
|
|
|
|
|
return [grep { $class->method_name($_->{method}) eq $method || $_->{method} eq 'any' } @$routes]; |
|
0
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
sub _negotiate_receive_media { |
243
|
0
|
|
|
0
|
|
|
my ($class, $c, $all_routes) = @_; |
244
|
|
|
|
|
|
|
|
245
|
0
|
0
|
|
|
|
|
return $all_routes unless my $ct = $c->content_type; |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# will hold all routes with acceptable receive types |
248
|
0
|
|
|
|
|
|
my $routes = []; |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# remove charset from content-type |
251
|
0
|
0
|
|
|
|
|
if ($ct =~ m/^([^;]+)/) { |
252
|
0
|
|
|
|
|
|
$ct = $1; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
0
|
|
|
|
|
|
$c->log->debug("I have received $ct"); |
256
|
|
|
|
|
|
|
|
257
|
0
|
|
|
|
|
|
ROUTE: foreach (@$all_routes) { |
258
|
|
|
|
|
|
|
# does this route accept all media types? |
259
|
0
|
0
|
|
|
|
|
unless (exists $_->{rules}->{accepts}) { |
260
|
0
|
|
|
|
|
|
push(@$routes, $_); |
261
|
0
|
|
|
|
|
|
next ROUTE; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# okay, it has, what are we accepting? |
265
|
0
|
|
|
|
|
|
foreach my $accept (@{$_->{rules}->{accepts}}) { |
|
0
|
|
|
|
|
|
|
266
|
0
|
0
|
|
|
|
|
if ($accept eq $ct) { |
267
|
0
|
|
|
|
|
|
push(@$routes, $_); |
268
|
0
|
|
|
|
|
|
next ROUTE; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
0
|
|
|
|
|
|
return $routes; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub _negotiate_return_media { |
277
|
0
|
|
|
0
|
|
|
my ($class, $c, $all_routes) = @_; |
278
|
|
|
|
|
|
|
|
279
|
0
|
|
|
|
|
|
my @mimes; |
280
|
0
|
|
|
|
|
|
foreach (@{$c->wanted_mimes}) { |
|
0
|
|
|
|
|
|
|
281
|
0
|
|
|
|
|
|
push(@mimes, $_->{mime}); |
282
|
|
|
|
|
|
|
} |
283
|
0
|
|
|
|
|
|
$c->log->debug('Remote address wants '.join(', ', @mimes)); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# will hold all routes with acceptable return types |
286
|
0
|
|
|
|
|
|
my $routes = []; |
287
|
|
|
|
|
|
|
|
288
|
0
|
|
|
|
|
|
ROUTE: foreach (@$all_routes) { |
289
|
|
|
|
|
|
|
# does this route return any media type? |
290
|
0
|
0
|
|
|
|
|
if ($_->{rules}->{returns_all}) { |
291
|
0
|
|
|
|
|
|
$_->{media} = '*/*'; |
292
|
0
|
|
|
|
|
|
push(@$routes, $_); |
293
|
0
|
|
|
|
|
|
next ROUTE; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# what media types does this route return? |
297
|
0
|
|
|
|
|
|
my @have = exists $_->{rules}->{returns} ? |
298
|
0
|
0
|
|
|
|
|
@{$_->{rules}->{returns}} : |
299
|
|
|
|
|
|
|
('text/html'); |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# what routes do the client want? |
302
|
0
|
0
|
|
|
|
|
if (@{$c->wanted_mimes}) { |
|
0
|
|
|
|
|
|
|
303
|
0
|
|
|
|
|
|
foreach my $want (@{$c->wanted_mimes}) { |
|
0
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# does the client accept _everything_? |
305
|
|
|
|
|
|
|
# if so, just return the first type we support. |
306
|
|
|
|
|
|
|
# this will happen only in the end of the |
307
|
|
|
|
|
|
|
# wanted_mimes list, so if the client explicitely |
308
|
|
|
|
|
|
|
# accepts a type we support, it will have |
309
|
|
|
|
|
|
|
# preference over this |
310
|
0
|
0
|
0
|
|
|
|
if ($want->{mime} eq '*/*' && $want->{q} > 0) { |
311
|
0
|
|
|
|
|
|
$_->{media} = $have[0]; |
312
|
0
|
|
|
|
|
|
push(@$routes, $_); |
313
|
0
|
|
|
|
|
|
next ROUTE; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# okay, the client doesn't support */*, let's see what we have |
317
|
0
|
|
|
|
|
|
foreach my $have (@have) { |
318
|
0
|
0
|
|
|
|
|
if ($want->{mime} eq $have) { |
319
|
|
|
|
|
|
|
# we return a MIME type the client wants |
320
|
0
|
|
|
|
|
|
$_->{media} = $want->{mime}; |
321
|
0
|
|
|
|
|
|
push(@$routes, $_); |
322
|
0
|
|
|
|
|
|
next ROUTE; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} else { |
327
|
0
|
|
|
|
|
|
$_->{media} = $have[0]; |
328
|
0
|
|
|
|
|
|
push(@$routes, $_); |
329
|
0
|
|
|
|
|
|
next ROUTE; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
0
|
|
|
|
|
|
return $routes; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub _negotiate_charset { |
337
|
0
|
|
|
0
|
|
|
my ($class, $c) = @_; |
338
|
|
|
|
|
|
|
|
339
|
0
|
0
|
|
|
|
|
if ($c->header('Accept-Charset')) { |
340
|
0
|
|
|
|
|
|
my @chars = split(/,/, $c->header('Accept-Charset')); |
341
|
0
|
|
|
|
|
|
foreach (@chars) { |
342
|
0
|
|
|
|
|
|
my ($charset, $pref) = split(/;q=/, $_); |
343
|
0
|
0
|
|
|
|
|
next unless defined $pref; |
344
|
0
|
0
|
0
|
|
|
|
return if $charset =~ m/utf-?8/i && $pref == 0; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
0
|
|
|
|
|
|
return 1; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=head1 AUTHOR |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
Ido Perlmuter, C<< >> |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=head1 BUGS |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
358
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
359
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=head1 SUPPORT |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
perldoc Leyland::Negotiator |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
You can also look for information at: |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=over 4 |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
L |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
L |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=item * CPAN Ratings |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
L |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=item * Search CPAN |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
L |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=back |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
Copyright 2010-2014 Ido Perlmuter. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
394
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
395
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=cut |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
1; |