5 use parent qw(Rose::Object);
8 use List::Util qw(first max min sum);
9 use List::MoreUtils qw(all any apply);
10 use Exporter qw(import);
14 use SL::MoreCommon qw(uri_encode uri_decode);
18 our @EXPORT_OK = qw(flatten unflatten);
20 use Rose::Object::MakeMethods::Generic
22 scalar => [ qw(applying_database_upgrades post_data) ],
23 'scalar --get_set_init' => [ qw(cgi layout presenter is_ajax is_mobile type) ],
31 return SL::Layout::None->new;
35 return SL::Presenter->new;
39 return ($ENV{HTTP_X_REQUESTED_WITH} || '') eq 'XMLHttpRequest' ? 1 : 0;
43 # mobile clients will change their user agent when the user requests
44 # desktop version so user agent is the most reliable way to identify
45 return ($ENV{HTTP_USER_AGENT} || '') =~ /Mobi/ ? 1 : 0;
53 $ENV{HTTPS} && 'on' eq lc $ENV{HTTPS};
57 my ($self, $topic, $default) = @_;
59 $topic = '::' . (caller(0))[0] . "::$topic" unless $topic =~ m{^::};
61 $self->{_cache} //= {};
62 $self->{_cache}->{$topic} //= ($default // {});
64 return $self->{_cache}->{$topic};
68 my ($target, $key, $value) = @_;
69 my @tokens = split /((?:\[\+?\])?(?:\.)|(?:\[\+?\]))/, $key;
73 $curr = \ $target->{ shift @tokens };
77 my $sep = shift @tokens;
78 my $key = shift @tokens;
80 $curr = \ $$curr->[$#$$curr], next if $sep eq '[]' && @tokens;
81 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]' && !@tokens;
82 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[+]';
83 $curr = \ $$curr->[max 0, $#$$curr] if $sep eq '[].';
84 $curr = \ $$curr->[++$#$$curr] if $sep eq '[+].';
85 $curr = \ $$curr->{$key}
94 $::lxdebug->enter_sub(2);
96 my ($target, $input, $log) = @_;
97 my @pairs = split(/&/, $input);
100 my ($key, $value) = split(/=/, $_, 2);
102 _store_value($target, uri_decode($key), uri_decode($value));
105 $::lxdebug->add_request_params(uri_decode($key) => uri_decode($value)) if $log;
108 $::lxdebug->leave_sub(2);
111 sub _parse_multipart_formdata {
112 my ($target, $temp_target, $input, $log) = @_;
113 my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $p_attachment, $encoding, $transfer_encoding);
116 # teach substr and length to use good ol' bytes, not 'em fancy characters
119 # We SHOULD honor encodings and transfer-encodings here, but as hard as I
120 # looked I couldn't find a reasonably recent webbrowser that makes use of
121 # these. Transfer encoding just eats up bandwidth...
123 # so all I'm going to do is add a fail safe that if anyone ever encounters
124 # this, it's going to croak so that debugging is easier
125 $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
126 my $boundary = '--' . $1;
130 foreach my $line (split m/\n/, $input) {
131 $line_length = length $line;
133 if ($line =~ /^\Q$boundary\E(--)?\r?$/) {
134 my $last_boundary = $1;
135 my $data = substr $input, $data_start, $index - $data_start;
138 if ($previous && !$filename && $transfer_encoding && $transfer_encoding ne 'binary') {
139 ${ $previous } = Encode::decode($encoding, $data);
141 ${ $previous } = $data;
143 $::lxdebug->add_request_params($name, $$previous) if $log;
149 $content_type = "text/plain";
153 $transfer_encoding = undef;
154 last if $last_boundary;
158 next unless $boundary_found;
160 if (!$headers_done) {
161 $line =~ s/[\r\n]*$//;
165 $data_start = $index + $line_length + 1;
169 if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
170 if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
172 substr $line, $-[0], $+[0] - $-[0], "";
175 if ($line =~ m|name\s*=\s*"(.*?)"|i) {
177 substr $line, $-[0], $+[0] - $-[0], "";
181 # legacy, some old upload routines expect this to be here
182 $temp_target->{FILENAME} = $filename if defined $filename;
184 # Name can potentially be both a normal variable or a file upload.
185 # A file upload can be identified by its "filename" attribute.
186 # The thing is, if a [+] clause vivifies structure in one of the
187 # branches it must be done in both, or subsequent "[]" will fail
188 my $temp_target_slot = _store_value($temp_target, $name);
189 my $target_slot = _store_value($target, $name);
191 # set the reference for appending of multiline data to the correct one
192 $previous = defined $filename ? $target_slot : $temp_target_slot;
194 # for multiple uploads: save the attachments in a SL/Mailer like structure
195 if (defined $filename) {
196 my $target_attachment = _store_value($target, "ATTACHMENTS.$name", {});
197 my $temp_target_attachment = _store_value($temp_target, "ATTACHMENTS.$name", {});
199 $$target_attachment->{data} = $previous;
200 $$temp_target_attachment->{filename} = $filename;
202 $p_attachment = $$temp_target_attachment;
209 if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
211 $p_attachment->{content_type} = $1;
213 if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
220 if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
221 $transfer_encoding = lc($1);
222 if ($transfer_encoding && $transfer_encoding !~ /^[78]bit|binary$/) {
223 die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
225 $p_attachment->{transfer_encoding} = $transfer_encoding;
233 next unless $previous;
236 $index += $line_length + 1;
239 $::lxdebug->leave_sub(2);
242 sub _parse_json_formdata {
245 return $content ? SL::JSON::decode_json($content) : undef;
248 sub _recode_recursively {
249 $::lxdebug->enter_sub;
250 my ($iconv, $from, $to) = @_;
252 if (any { ref $from eq $_ } qw(Form HASH)) {
253 for my $key (keys %{ $from }) {
254 if (!ref $from->{$key}) {
255 # Workaround for a bug: converting $from->{$key} directly
256 # leads to 'undef'. I don't know why. Converting a copy works,
258 $to->{$key} = $iconv->convert("" . $from->{$key}) if defined $from->{$key} && !defined $to->{$key};
260 $to->{$key} ||= {} if 'HASH' eq ref $from->{$key};
261 $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
262 _recode_recursively($iconv, $from->{$key}, $to->{$key});
266 } elsif (ref $from eq 'ARRAY') {
267 foreach my $idx (0 .. scalar(@{ $from }) - 1) {
268 if (!ref $from->[$idx]) {
269 # Workaround for a bug: converting $from->[$idx] directly
270 # leads to 'undef'. I don't know why. Converting a copy works,
272 $to->[$idx] = $iconv->convert("" . $from->[$idx]) if defined $from->[$idx] && !defined $to->[$idx];
274 $to->[$idx] ||= {} if 'HASH' eq ref $from->[$idx];
275 $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
276 _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
280 $main::lxdebug->leave_sub();
284 $::lxdebug->enter_sub;
286 my ($self, $target) = @_;
288 # yes i know, copying all those values around isn't terribly efficient, but
289 # the old version of dumping everything into form and then launching a
290 # tactical recode nuke at the data is still worse.
292 # this way the data can at least be recoded on the fly as soon as we get to
293 # know the source encoding and only in the cases where encoding may be hidden
294 # among the payload we take the hit of copying the request around
295 $self->post_data(undef);
296 my $data_to_decode = { };
297 my $iconv = SL::Iconv->new('UTF-8', 'UTF-8');
299 _input_to_hash($data_to_decode, $ENV{QUERY_STRING}, 1) if $ENV{QUERY_STRING};
300 _input_to_hash($data_to_decode, $ARGV[0], 1) if @ARGV && $ARGV[0];
302 if ($ENV{CONTENT_LENGTH}) {
304 read STDIN, $content, $ENV{CONTENT_LENGTH};
306 if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
307 $self->post_data({});
308 my $post_data_to_decode = { };
310 # multipart formdata can bring it's own encoding, so give it both
311 # and let it decide on it's own
312 _parse_multipart_formdata($self->post_data, $post_data_to_decode, $content, 1);
313 _recode_recursively($iconv, $post_data_to_decode, $self->post_data) if keys %$post_data_to_decode;
315 $target->{$_} = $self->post_data->{$_} for keys %{ $self->post_data };
317 } elsif (($ENV{CONTENT_TYPE} // '') =~ m{^application/json}i) {
318 $self->post_data(_parse_json_formdata($content));
321 # normal encoding must be recoded
322 _input_to_hash($data_to_decode, $content, 1);
326 _recode_recursively($iconv, $data_to_decode, $target) if keys %$data_to_decode;
328 if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
330 $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
331 _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
334 $::lxdebug->leave_sub;
340 my ($source, $target, $prefix, $in_array) = @_;
343 # There are two edge cases that need attention. First: more than one hash
344 # inside an array. Only the first of each nested can have a [+]. Second: if
345 # an array contains mixed values _store_value will rely on autovivification.
346 # So any type change must have a [+]
347 # This closure decides one recursion step AFTER an array has been found if a
348 # [+] needs to be generated
349 my $arr_prefix = sub {
350 return $_[0] ? '[+]' : '[]' if $in_array;
357 for my $key (sort keys %$source) {
358 flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
364 for my $i (0 .. $#$source) {
365 flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
370 die "can't flatten a pure scalar" unless defined $prefix;
371 push @$target, [ $prefix . $arr_prefix->(0) => $source ];
374 die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
382 my ($data, $target) = @_;
385 for my $pair (@$data) {
386 _store_value($target, @$pair) if defined $pair->[0];
398 SL::Request.pm - request parsing, data serialization, request information
402 This module handles unpacking of CGI parameters. It also gives
403 information about the request, such as whether or not it was done via AJAX,
404 or the requested content type.
408 # read cgi input depending on request type, unflatten and recode
409 $::request->read_cgi_input($target_hash_ref);
411 # $hashref and $new_hashref should be identical
412 my $new_arrayref = flatten($hashref);
413 my $new_hashref = unflatten($new_arrayref);
415 # Handle AJAX requests differently than normal requests:
416 if ($::request->is_ajax) {
417 $controller->render('json-mask', { type => 'json' });
419 $controller->render('full-mask');
424 This module provides information about the request made by the
427 It also handles flattening and unflattening of data for request
428 roundtrip purposes. kivitendo uses the format as described below:
434 Hash entries will be connected with a dot (C<.>). A simple hash like this
441 will be serialized to
444 [ order.customer => 5 ],
448 Arrays will be marked by empty brackets (C<[]>). A hash like this
450 selected_id => [ 2, 6, 8, 9 ]
454 [ selected_id[] => 2 ],
455 [ selected_id[] => 6 ],
456 [ selected_id[] => 8 ],
457 [ selected_id[] => 9 ],
459 Since this will produce identical keys, the resulting flattened list can not be
460 used as a hash. It is however very easy to use this in a template to generate
463 [% FOREACH id = selected_ids %]
464 <input type="hidden" name="selected_id[]" value="[% id | html %]">
467 =item Nested structures
469 A special version of this are nested hashes in an array, which is very common.
470 The combined operator (C<[].>) will be used. As a special case, every time a new
471 array slice is started, the special convention (C<[+].>) will be used. Again this
472 is because it's easy to write a template with it.
491 [ order.orderitems[+].id => 1 ],
492 [ order.orderitems[].part => 15 ],
493 [ order.orderitems[+].id => 2 ],
494 [ order.orderitems[].part => 7 ],
498 The format currently does have certain limitations when compared to other
499 serialization formats.
505 The order of serialized values matters to reconstruct arrays properly. This
506 should rarely be a problem if you just flatten and dump into a url or a field
511 The current implementation of flatten does produce correct serialization of
512 empty keys, but unflatten is unable to resolve these. Do no use C<''> or
513 C<undef> as keys. C<0> is fine.
517 You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
521 It is not possible to serialize something like
523 sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
525 This is a feature, as perl doesn't do well with very large arrays.
529 There is currently no support nor prevention for flattening a circular structure.
531 =item Custom Delimiter
533 No support for other delimiters, sorry.
535 =item Other References
537 No support for globs, scalar refs, code refs, filehandles and the like. These will die.
547 =item C<flatten HASHREF [ ARRAYREF ]>
549 This function will flatten the provided hash ref into the provided array ref.
550 The array ref may be non empty, but will be changed in this case.
552 The return value is the flattened array ref.
554 =item C<unflatten ARRAYREF [ HASHREF ]>
556 This function will parse the array ref, and will store the contents into the hash ref. The hash ref may be non empty, in this case any new keys will override the old ones only on leafs with same type. Type changes on a node will die.
560 Returns trueish if the request is an XML HTTP request, also known as
565 Returns the requested content type (either C<html>, C<js> or C<json>).
569 Set and retrieve the layout object for the current request. Must be an instance
570 of L<SL::Layout::Base>. Defaults to an instance of L<SL::Layout::None>.
572 For more information about layouts, see L<SL::Layout::Dispatcher>.
574 =item C<cache $topic[, $default ]>
576 Caches an item for the duration of the request. C<$topic> must be an
577 index name referring to the thing to cache. It is used for retrieving
578 it later on. If C<$topic> doesn't start with C<::> then the caller's
579 package name is prepended to the topic. For example, if the a from
580 package C<SL::StuffedStuff> calls with topic = C<get_stuff> then the
581 actual key will be C<::SL::StuffedStuff::get_stuff>.
583 If no item exists in the cache for C<$topic> then it is created and
584 its initial value is set to C<$default>. If C<$default> is not given
585 (undefined) then a new, empty hash reference is created.
587 Returns the cached item.
591 If the client sends data in the request body with the content type of
592 either C<application/json> or C<multipart/form-data>, the content will
593 be stored in the global request object, too. It can be retrieved via
594 the C<post_data> function.
596 For content type C<multipart/form-data> the same data is additionally
597 stored in the global C<$::form> instance, potentially overwriting
598 parameters given in the URL. This is done primarily for compatibility
599 purposes with existing code that expects all parameters to be present
602 For content type C<application/json> the data is only available in
603 C<$::request>. The reason is that the top-level data in a JSON
604 documents doesn't have to be an object which could be mapped to the
605 hash C<$::form>. Instead, the top-level data can also be an
606 array. Additionally keeping the namespaces of URL and POST parameters
607 separate is cleaner and allows for fewer accidental conflicts.
611 =head1 SPECIAL FUNCTIONS
613 =head2 C<_store_value()>
615 Parses a complex var name, and stores it in the form.
618 _store_value($target, $key, $value);
620 Keys must start with a string, and can contain various tokens.
621 Supported key structures are:
624 Simple key strings work as expected
629 Separating two keys by a dot (.) will result in a hash lookup for the inner value
630 This is similar to the behaviour of java and templating mechanisms.
632 filter.description => $form->{filter}->{description}
634 3. array+hashref access
636 Adding brackets ([]) before the dot will cause the next hash to be put into an array.
637 Using [+] instead of [] will force a new array index. This is useful for recurring
638 data structures like part lists. Put a [+] into the first varname, and use [] on the
641 Repeating these names in your template:
644 invoice.items[].parts_id
648 $form->{invoice}->{items}->[
662 Using brackets at the end of a name will result in the creation of a pure array.
663 Note that you mustn't use [+], which is reserved for array+hash access and will
664 result in undefined behaviour in array context.
666 filter.status[] => $form->{status}->[ val1, val2, ... ]