File Coverage

blib/lib/Kelp/Request.pm
Criterion Covered Total %
statement 62 89 69.6
branch 17 26 65.3
condition 8 14 57.1
subroutine 24 33 72.7
pod 25 25 100.0
total 136 187 72.7


line stmt bran cond sub pod time code
1             package Kelp::Request;
2              
3 27     27   21234 use Kelp::Base 'Plack::Request';
  27         183  
  27         435  
4              
5 27     27   375 use Carp;
  27         170  
  27         2033  
6 27     27   187 use Try::Tiny;
  27         185  
  27         2020  
7 27     27   322 use Hash::MultiValue;
  27         74  
  27         846  
8 27     27   246 use Kelp::Util;
  27         50  
  27         85438  
9              
10             our @CARP_NOT = qw(Kelp);
11              
12             attr -app => sub { croak "app is required" };
13              
14             # The stash is used to pass values from one route to another
15             attr stash => sub { {} };
16              
17             # The named hash contains the values of the named placeholders
18             attr named => sub { {} };
19              
20             # charset which came with the request
21             attr -charset => sub {
22             my $self = shift;
23              
24             return undef unless $self->content_type;
25             return undef unless $self->content_type =~ m{
26             ^(?: # only on some content-types
27             text/ | application/
28             )
29             .+
30             ;\s*charset=([^;\$]+) # get the charset
31             }xi;
32             return $1;
33             };
34              
35             # The name of the matched route for this request
36             attr route_name => sub { undef };
37              
38             attr query_parameters => sub {
39             my $self = shift;
40              
41             $self->SUPER::query_parameters;
42             my $decoded = $self->_charset_decode_array($self->env->{'plack.request.query_parameters'}, 1);
43             return Hash::MultiValue->new(@{$decoded});
44             };
45              
46             attr body_parameters => sub {
47             my $self = shift;
48              
49             $self->SUPER::body_parameters;
50             my $decoded = $self->_charset_decode_array($self->env->{'plack.request.body_parameters'});
51             return Hash::MultiValue->new(@{$decoded});
52             };
53              
54             attr parameters => sub {
55             my $self = shift;
56              
57             $self->SUPER::parameters;
58             my $decoded_query = $self->_charset_decode_array($self->env->{'plack.request.query_parameters'}, 1);
59             my $decoded_body = $self->_charset_decode_array($self->env->{'plack.request.body_parameters'});
60             return Hash::MultiValue->new(@{$decoded_query}, @{$decoded_body});
61             };
62              
63             # Raw methods - methods in Plack::Request (without decoding)
64             # in Kelp::Request, they are replaced with decoding versions
65              
66             sub raw_path
67             {
68 0     0 1 0 my $self = shift;
69 0         0 return $self->SUPER::path(@_);
70             }
71              
72             sub raw_body
73             {
74 0     0 1 0 my $self = shift;
75 0         0 return $self->SUPER::content(@_);
76             }
77              
78             sub raw_body_parameters
79             {
80 0     0 1 0 my $self = shift;
81 0         0 return $self->SUPER::body_parameters(@_);
82             }
83              
84             sub raw_query_parameters
85             {
86 0     0 1 0 my $self = shift;
87 0         0 return $self->SUPER::query_parameters(@_);
88             }
89              
90             sub raw_parameters
91             {
92 0     0 1 0 my $self = shift;
93 0         0 return $self->SUPER::parameters(@_);
94             }
95              
96             # If you're running the web app as a proxy, use Plack::Middleware::ReverseProxy
97 1     1 1 5 sub address { $_[0]->env->{REMOTE_ADDR} }
98 0     0 1 0 sub remote_host { $_[0]->env->{REMOTE_HOST} }
99 0     0 1 0 sub user { $_[0]->env->{REMOTE_USER} }
100              
101             # Interface
102              
103             sub new
104             {
105 252     252 1 1080 my ($class, %args) = @_;
106 252         2322 my $self = $class->SUPER::new(delete $args{env});
107 252         3960 $self->{$_} = $args{$_} for keys %args;
108 252         2040 return $self;
109             }
110              
111             sub is_ajax
112             {
113 2     2 1 8 my $self = shift;
114 2 100       20 return 0 unless my $with = $self->headers->header('X-Requested-With');
115 1         500 return $with =~ m{XMLHttpRequest}i;
116             }
117              
118             sub content_type_is
119             {
120 107     107 1 265 my ($self, $type) = @_;
121 107 100       439 return 0 unless $self->content_type;
122 78         706 return $self->content_type =~ m{^\Q$type\E}i;
123             }
124              
125             sub is_text
126             {
127 2     2 1 11 return $_[0]->content_type_is('text/plain');
128             }
129              
130             sub is_html
131             {
132 2     2 1 15 return $_[0]->content_type_is('text/html');
133             }
134              
135             sub is_json
136             {
137 101     101 1 299 return $_[0]->content_type_is('application/json');
138             }
139              
140             sub is_xml
141             {
142 2     2 1 13 return $_[0]->content_type_is('application/xml');
143             }
144              
145             sub charset_decode
146             {
147 147     147 1 8050 my ($self, $string, $configured_only) = @_;
148 147         523 my $req_charset = $self->app->request_charset;
149              
150             # do not decode at all if the application is set no not decode
151 147 100       395 return $string unless $req_charset;
152 145   66     790 return Kelp::Util::charset_decode(
153             (!$configured_only && Kelp::Util::effective_charset($self->charset)) || $req_charset,
154             $string,
155             );
156             }
157              
158             sub _charset_decode_array
159             {
160 106     106   562 my ($self, $arr, $configured_only) = @_;
161              
162 106         267 return [map { $self->charset_decode($_, $configured_only) } @$arr];
  128         3543  
163             }
164              
165             sub path
166             {
167 252     252 1 546 my $self = shift;
168 252         902 return Kelp::Util::charset_decode($self->app->request_charset, $self->SUPER::path(@_));
169             }
170              
171             sub content
172             {
173 19     19 1 166 my $self = shift;
174 19         139 return $self->charset_decode($self->SUPER::content(@_));
175             }
176              
177             sub json_content
178             {
179 20     20 1 45 my $self = shift;
180 20 100       85 return undef unless $self->is_json;
181              
182             return try {
183 19     19   930 $self->app->get_encoder(json => 'internal')->decode($self->content);
184             }
185             catch {
186 1     1   101 undef;
187 19         458 };
188             }
189              
190             sub param
191             {
192 77     77 1 166 my $self = shift;
193              
194 77 100 66     242 if ($self->is_json && $self->app->can('json')) {
195 20         82 return $self->json_param(@_);
196             }
197              
198             # safe method without calling Plack::Request::param
199 57 100       874 return $self->parameters->get($_[0]) if @_;
200 7         17 return keys %{$self->parameters};
  7         29  
201             }
202              
203             sub cgi_param
204             {
205 0     0 1 0 shift->SUPER::param(@_);
206             }
207              
208             sub query_param
209             {
210 14     14 1 146 my $self = shift;
211              
212 14 50       118 return $self->query_parameters->get($_[0]) if @_;
213 0         0 return keys %{$self->query_parameters};
  0         0  
214             }
215              
216             sub body_param
217             {
218 2     2 1 108 my $self = shift;
219              
220 2 50       11 return $self->body_parameters->get($_[0]) if @_;
221 0         0 return keys %{$self->body_parameters};
  0         0  
222             }
223              
224             sub json_param
225             {
226 22     22 1 143 my $self = shift;
227              
228 22   66     91 my $hash = $self->{_param_json_content} //= do {
229 17   100     63 my $hash = $self->json_content // {};
230 17 50       1363 ref $hash eq 'HASH' ? $hash : {ref $hash, $hash};
231             };
232              
233 22 100       202 return $hash->{$_[0]} if @_;
234 4         36 return keys %$hash;
235             }
236              
237             sub session
238             {
239 0     0 1   my $self = shift;
240 0   0       my $session = $self->env->{'psgix.session'}
241             // croak "No Session middleware wrapped";
242              
243 0 0         return $session if !@_;
244              
245 0 0         if (@_ == 1) {
246 0           my $value = shift;
247 0 0         return $session->{$value} unless ref $value;
248 0           return $self->env->{'psgix.session'} = $value;
249             }
250              
251 0           my %hash = @_;
252 0           $session->{$_} = $hash{$_} for keys %hash;
253 0           return $session;
254             }
255              
256             1;
257              
258             __END__
259              
260             =pod
261              
262             =head1 NAME
263              
264             Kelp::Request - Request class for a Kelp application
265              
266             =head1 SYNOPSIS
267              
268             my $request = Kelp::Request( app => $app, env => $env );
269              
270             =head1 DESCRIPTION
271              
272             This module provides a convenience layer on top of L<Plack::Request>. It extends
273             it to add several convenience methods and support for application encoding.
274              
275             =head1 ENCODING
276              
277             Starting with version 2.10, Kelp::Request simplifies input handling and
278             improves correctness by automatically decoding path, query parameters and content.
279              
280             Headers (so cookies as well) are unaffected, as they aren't consistently
281             supported outside of ASCII range. JSON now decodes request data into the proper
282             charset instead of flat utf8 regardless of configuration. Sessions are
283             configured separately in middlewares, so they must themselves do the proper
284             decoding.
285              
286             Following methods will return values decoded with charset either from
287             C<Content-Type> header or the one specified in the app's configuration
288             (L<Kelp/request_charset>):
289              
290             =over
291              
292             =item * C<param>
293              
294             =item * C<cgi_param>
295              
296             =item * C<body_param>
297              
298             =item * C<json_param>
299              
300             =item * C<parameters>
301              
302             =item * C<body_parameters>
303              
304             =item * C<content>
305              
306             =item * C<json_content>
307              
308             =back
309              
310             Following methods will always decode to L<Kelp/request_charset> because they
311             are not the part of message's content (URIs should always be in ASCII-compilant
312             encoding, UTF-8 is preferable):
313              
314             =over
315              
316             =item * C<path>
317              
318             =item * C<param> (from query)
319              
320             =item * C<cgi_param> (from query)
321              
322             =item * C<parameters> (from query)
323              
324             =item * C<query_parameters>
325              
326             =back
327              
328             If you wish to get input in the original request encoding, use these instead
329             (note: there is no C<raw_param>):
330              
331             =over
332              
333             =item * C<raw_path>
334              
335             =item * C<raw_parameters>
336              
337             =item * C<raw_query_parameters>
338              
339             =item * C<raw_body_parameters>
340              
341             =item * C<raw_body> (instead of C<content>)
342              
343             =back
344              
345             Following methods will return decoded values if the other parts of the system
346             are configured to decode them:
347              
348             =over
349              
350             =item * C<session> - depends on session middleware
351              
352             =back
353              
354             B<Some caveats> about the automatic decoding and L<Kelp/request_charset>
355             configuration parameter:
356              
357             As always, UTF-8 (the default) works best - don't change to avoid issues. Other
358             ASCII-compilant encodings should work well. L</content> will always be decoded
359             properly, but C<application/x-www-form-urlencoded> and C<multipart/form-data>
360             will have issues with non-ASCII-compilant encodings. Especially the latter,
361             because the information about C<Content-Type> of a single part is lost on Plack
362             level and it is not properly decoded using that encoding. In such corner cases,
363             you should probably get the full undecoded body using L</raw_body> and parse it
364             yourself.
365              
366             If you wish to disable automatic decoding, you can set L<Kelp/request_charset>
367             to undef - it will then ignore any charset which came with the message and let
368             you do your own decoding.
369              
370             =head1 ATTRIBUTES
371              
372             =head2 app
373              
374             A reference to the Kelp application. This will always be the real application,
375             not the reblessed controller.
376              
377             =head2 stash
378              
379             Returns a hashref, which represents the stash of the current the request
380              
381             An all use, utility hash to use to pass information between routes. The stash
382             is a concept originally conceived by the developers of L<Catalyst>. It's a hash
383             that you can use to pass data from one route to another.
384              
385             # put value into stash
386             $self->req->stash->{username} = app->authenticate();
387             # more convenient way
388             $self->stash->{username} = app->authenticate();
389              
390             # get value from stash
391             return "Hello " . $self->req->stash->{username};
392             # more convenient way
393             return "Hello " . $self->stash('username');
394              
395             =head2 named
396              
397             This hash is initialized with the named placeholders of the path that the
398             current route is processing.
399              
400             =head2 route_name
401              
402             Contains a string name of the route matched for this request. Contains route pattern
403             if the route was not named.
404              
405             =head2 charset
406              
407             Returns the charset from the C<Content-Type> HTTP header or C<undef> if there
408             is none. Will ignore the charset unless C<Content-Type> is C<text/*> or
409             C<application/*>. Readonly.
410              
411             =head1 METHODS
412              
413             =head2 param
414              
415             Shortcut for returning the HTTP parameters of the request with heavy amount of
416             dwimmery. It has two modes of operation and behaves differently for JSON and
417             non-JSON requests.
418              
419             =over
420              
421             =item
422              
423             If passed with a parameter, returns the value value of a parameter with that
424             name from either request body or query (body is preferred). This always returns
425             a scalar value.
426              
427             =item
428              
429             If passed without parameters, returns the list containing the names of
430             available parameters. This always returns a list.
431              
432             =back
433              
434             The behavior is changed when the content type of the request is
435             C<application/json> and a JSON module is loaded. In that case, it will decode
436             the JSON body and return values from it instead. If the root contents of the
437             JSON document is not an C<HASH> (after decoding), then it will be wrapped into
438             a hash with its reftype as a key, for example:
439              
440             { ARRAY => [...] } # when JSON contains an array as root element
441             { '' => [...] } # when JSON contains something that's not a reference
442              
443             my $array_ref = $kelp->param('ARRAY');
444              
445             Since this method behaves differently based on the form of input, you're
446             encouraged to use other, more specific methods listed below.
447              
448             =head2 query_param
449              
450             Same as L</param>, but always returns parameters from query string.
451              
452             =head2 body_param
453              
454             Same as L</param>, but always returns parameters from body form.
455              
456             =head2 json_param
457              
458             Same as L</param>, but always returns parameters from JSON body.
459              
460             =head2 cgi_param
461              
462             CGI.pm compatible implementation of C<param> (but does not set parameters). It
463             is B<not recommended> to use this method, unless for some reason you have to
464             maintain CGI.pm compatibility. Misusing this method can lead to bugs and
465             security vulnerabilities.
466              
467             =head2 parameters
468              
469             Same as L<Plack::Request/parameters>, but the keys and values in the hash are decoded.
470              
471             =head2 raw_parameters
472              
473             Same as L<Plack::Request/parameters>. The hash keys and values are B<not> decoded.
474              
475             =head2 query_parameters
476              
477             Same as L<Plack::Request/query_parameters>, but the keys and values in the hash are decoded.
478              
479             =head2 raw_query_parameters
480              
481             Same as L<Plack::Request/query_parameters>, The hash keys and values are B<not> decoded.
482              
483             =head2 body_parameters
484              
485             Same as L<Plack::Request/body_parameters>, but the keys and values in the hash are decoded.
486              
487             =head2 raw_body_parameters
488              
489             Same as L<Plack::Request/body_parameters>, The hash keys and values are B<not> decoded.
490              
491             =head2 content
492              
493             Same as L<Plack::Request/content>, but the result is decoded.
494              
495             This is the go-to method for getting the request body for string manipulation
496             character by character. It can be useful when you, for example, want to run a
497             regex on the body. Use this instead of L</raw_body>.
498              
499             =head2 raw_body
500              
501             Same as L<Plack::Request/raw_body>. The result is B<not> decoded.
502              
503             This is the go-to method for getting the request body for string manipulation
504             byte by byte. An example would be deserializing the body with a custom
505             serializer. Use this instead of L</content>.
506              
507             =head2 json_content
508              
509             Returns the json-decoded body of the request or undef if the request is not
510             json, there is no json decoder or an error occured.
511              
512             =head2 path
513              
514             Same as L<Plack::Request/path>, but the result is decoded.
515              
516             =head2 raw_path
517              
518             Same as L<Plack::Request/path>. The result is B<not> decoded.
519              
520             =head2 address, remote_host, user
521              
522             These are shortcuts to the REMOTE_ADDR, REMOTE_HOST and REMOTE_USER environment
523             variables.
524              
525             if ( $self->req->address eq '127.0.0.1' ) {
526             ...
527             }
528              
529             Note: See L<Kelp::Cookbook/Deploying> for configuration required for these
530             fields when using a proxy.
531              
532             =head2 session
533              
534             Returns the Plack session hash or croaks if no C<Session> middleware was included.
535              
536             sub get_session_value {
537             my $self = shift;
538             $self->session->{user} = 45;
539             }
540              
541             If called with a single argument, returns that value from the session hash:
542              
543             sub set_session_value {
544             my $self = shift;
545             my $user = $self->req->session('user');
546             # Same as $self->req->session->{'user'};
547             }
548              
549             Set values in the session using key-value pairs:
550              
551             sub set_session_hash {
552             my $self = shift;
553             $self->req->session(
554             name => 'Jill Andrews',
555             age => 24,
556             email => 'jill@perlkelp.com'
557             );
558             }
559              
560             Replace all values with a hash:
561              
562             sub set_session_hashref {
563             my $self = shift;
564             $self->req->session( { bar => 'foo' } );
565             }
566              
567             Clear the session:
568              
569             sub clear_session {
570             my $self = shift;
571             $self->req->session( {} );
572             }
573              
574             Delete session value:
575              
576             delete $self->req->session->{'useless'};
577              
578             =head2 is_ajax
579              
580             Returns true if the request was called with C<XMLHttpRequest>.
581              
582             =head2 content_type_is
583              
584             Returns true if request has a C<Content-Type> header starting with a passed string.
585              
586             =head2 is_text
587              
588             Returns true if the request's content type was C<text/plain>.
589              
590             =head2 is_html
591              
592             Returns true if the request's content type was C<text/html>.
593              
594             =head2 is_json
595              
596             Returns true if the request's content type was C<application/json>.
597              
598             =head2 is_xml
599              
600             Returns true if the request's content type was C<application/xml>.
601              
602             =head2 charset_decode
603              
604             Shortcut method, which decodes a string using L</charset> or
605             L<Kelp/request_charset>. A second optional parameter can be passed, and if true
606             will cause the method to ignore charset passed in the C<Content-Type> header.
607              
608             It does noting if L<Kelp/request_charset> is undef or false.
609              
610             =cut
611