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);
 
  13 use SL::MoreCommon qw(uri_encode uri_decode);
 
  17 our @EXPORT_OK = qw(flatten unflatten read_cgi_input);
 
  19 use Rose::Object::MakeMethods::Generic
 
  21   'scalar --get_set_init' => [ qw(cgi layout presenter is_ajax type) ],
 
  29   return SL::Layout::None->new;
 
  33   return SL::Presenter->new;
 
  37   return ($ENV{HTTP_X_REQUESTED_WITH} || '') eq 'XMLHttpRequest' ? 1 : 0;
 
  45   my ($self, $topic, $default) = @_;
 
  47   $topic = '::' . (caller(0))[0] . "::$topic" unless $topic =~ m{^::};
 
  49   $self->{_cache}           //= {};
 
  50   $self->{_cache}->{$topic} //= ($default // {});
 
  52   return $self->{_cache}->{$topic};
 
  56   my ($target, $key, $value) = @_;
 
  57   my @tokens = split /((?:\[\+?\])?(?:\.)|(?:\[\+?\]))/, $key;
 
  61      $curr = \ $target->{ shift @tokens };
 
  65     my $sep = shift @tokens;
 
  66     my $key = shift @tokens;
 
  68     $curr = \ $$curr->[$#$$curr], next   if $sep eq '[]' && @tokens;
 
  69     $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]' && !@tokens;
 
  70     $curr = \ $$curr->[++$#$$curr], next if $sep eq '[+]';
 
  71     $curr = \ $$curr->[max 0, $#$$curr]  if $sep eq '[].';
 
  72     $curr = \ $$curr->[++$#$$curr]       if $sep eq '[+].';
 
  73     $curr = \ $$curr->{$key}
 
  82   $::lxdebug->enter_sub(2);
 
  84   my ($target, $input, $log) = @_;
 
  85   my @pairs = split(/&/, $input);
 
  88     my ($key, $value) = split(/=/, $_, 2);
 
  90     _store_value($target, uri_decode($key), uri_decode($value));
 
  93     $::lxdebug->add_request_params(uri_decode($key) => uri_decode($value)) if $log;
 
  96   $::lxdebug->leave_sub(2);
 
  99 sub _parse_multipart_formdata {
 
 100   my ($target, $temp_target, $input, $log) = @_;
 
 101   my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $p_attachment, $encoding, $transfer_encoding);
 
 104   # teach substr and length to use good ol' bytes, not 'em fancy characters
 
 107   # We SHOULD honor encodings and transfer-encodings here, but as hard as I
 
 108   # looked I couldn't find a reasonably recent webbrowser that makes use of
 
 109   # these. Transfer encoding just eats up bandwidth...
 
 111   # so all I'm going to do is add a fail safe that if anyone ever encounters
 
 112   # this, it's going to croak so that debugging is easier
 
 113   $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
 
 114   my $boundary = '--' . $1;
 
 118   foreach my $line (split m/\n/, $input) {
 
 119     $line_length = length $line;
 
 121     if ($line =~ /^\Q$boundary\E(--)?\r?$/) {
 
 122       my $last_boundary = $1;
 
 123       my $data       =  substr $input, $data_start, $index - $data_start;
 
 126       if ($previous && !$filename && $transfer_encoding && $transfer_encoding ne 'binary') {
 
 127         ${ $previous } = Encode::decode($encoding, $data);
 
 129         ${ $previous } = $data;
 
 131       $::lxdebug->add_request_params($name, $$previous) if $log;
 
 137       $content_type   = "text/plain";
 
 141       $transfer_encoding = undef;
 
 142       last if $last_boundary;
 
 146     next unless $boundary_found;
 
 148     if (!$headers_done) {
 
 149       $line =~ s/[\r\n]*$//;
 
 153         $data_start = $index + $line_length + 1;
 
 157       if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
 
 158         if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
 
 160           substr $line, $-[0], $+[0] - $-[0], "";
 
 163         if ($line =~ m|name\s*=\s*"(.*?)"|i) {
 
 165           substr $line, $-[0], $+[0] - $-[0], "";
 
 169           # legacy, some old upload routines expect this to be here
 
 170           $temp_target->{FILENAME} = $filename if defined $filename;
 
 172           # Name can potentially be both a normal variable or a file upload.
 
 173           # A file upload can be identified by its "filename" attribute.
 
 174           # The thing is, if a [+] clause vivifies structure in one of the
 
 175           # branches it must be done in both, or subsequent "[]" will fail
 
 176           my $temp_target_slot = _store_value($temp_target, $name);
 
 177           my $target_slot      = _store_value($target,      $name);
 
 179           # set the reference for appending of multiline data to the correct one
 
 180           $previous            = defined $filename ? $target_slot : $temp_target_slot;
 
 182           # for multiple uploads: save the attachments in a SL/Mailer like structure
 
 183           if (defined $filename) {
 
 184             my $target_attachment      = _store_value($target,      "ATTACHMENTS.$name", {});
 
 185             my $temp_target_attachment = _store_value($temp_target, "ATTACHMENTS.$name", {});
 
 187             $$target_attachment->{data}          = $previous;
 
 188             $$temp_target_attachment->{filename} = $filename;
 
 190             $p_attachment = $$temp_target_attachment;
 
 197       if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
 
 199         $p_attachment->{content_type} = $1;
 
 201         if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
 
 208       if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
 
 209         $transfer_encoding = lc($1);
 
 210         if ($transfer_encoding  && $transfer_encoding !~ /^[78]bit|binary$/) {
 
 211           die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
 
 213         $p_attachment->{transfer_encoding} = $transfer_encoding;
 
 221     next unless $previous;
 
 224     $index += $line_length + 1;
 
 227   $::lxdebug->leave_sub(2);
 
 230 sub _recode_recursively {
 
 231   $::lxdebug->enter_sub;
 
 232   my ($iconv, $from, $to) = @_;
 
 234   if (any { ref $from eq $_ } qw(Form HASH)) {
 
 235     for my $key (keys %{ $from }) {
 
 236       if (!ref $from->{$key}) {
 
 237         # Workaround for a bug: converting $from->{$key} directly
 
 238         # leads to 'undef'. I don't know why. Converting a copy works,
 
 240         $to->{$key} = $iconv->convert("" . $from->{$key}) if defined $from->{$key} && !defined $to->{$key};
 
 242         $to->{$key} ||= {} if 'HASH'  eq ref $from->{$key};
 
 243         $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
 
 244         _recode_recursively($iconv, $from->{$key}, $to->{$key});
 
 248   } elsif (ref $from eq 'ARRAY') {
 
 249     foreach my $idx (0 .. scalar(@{ $from }) - 1) {
 
 250       if (!ref $from->[$idx]) {
 
 251         # Workaround for a bug: converting $from->[$idx] directly
 
 252         # leads to 'undef'. I don't know why. Converting a copy works,
 
 254         $to->[$idx] = $iconv->convert("" . $from->[$idx]);
 
 256         $to->[$idx] ||= {} if 'HASH'  eq ref $from->[$idx];
 
 257         $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
 
 258         _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
 
 262   $main::lxdebug->leave_sub();
 
 266   $::lxdebug->enter_sub;
 
 270   # yes i know, copying all those values around isn't terribly efficient, but
 
 271   # the old version of dumping everything into form and then launching a
 
 272   # tactical recode nuke at the data is still worse.
 
 274   # this way the data can at least be recoded on the fly as soon as we get to
 
 275   # know the source encoding and only in the cases where encoding may be hidden
 
 276   # among the payload we take the hit of copying the request around
 
 277   my $temp_target = { };
 
 279   # since both of these can potentially bring their encoding in INPUT_ENCODING
 
 280   # they get dumped into temp_target
 
 281   _input_to_hash($temp_target, $ENV{QUERY_STRING}, 1) if $ENV{QUERY_STRING};
 
 282   _input_to_hash($temp_target, $ARGV[0],           1) if @ARGV && $ARGV[0];
 
 284   if ($ENV{CONTENT_LENGTH}) {
 
 286     read STDIN, $content, $ENV{CONTENT_LENGTH};
 
 287     if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
 
 288       # multipart formdata can bring it's own encoding, so give it both
 
 289       # and let it decide on it's own
 
 290       _parse_multipart_formdata($target, $temp_target, $content, 1);
 
 292       # normal encoding must be recoded
 
 293       _input_to_hash($temp_target, $content, 1);
 
 297   my $encoding     = delete $temp_target->{INPUT_ENCODING} || 'UTF-8';
 
 299   _recode_recursively(SL::Iconv->new($encoding, 'UTF-8'), $temp_target => $target) if keys %$target;
 
 301   if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
 
 303     $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
 
 304     _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
 
 307   $::lxdebug->leave_sub;
 
 313   my ($source, $target, $prefix, $in_array) = @_;
 
 316   # There are two edge cases that need attention. First: more than one hash
 
 317   # inside an array.  Only the first of each nested can have a [+].  Second: if
 
 318   # an array contains mixed values _store_value will rely on autovivification.
 
 319   # So any type change must have a [+]
 
 320   # This closure decides one recursion step AFTER an array has been found if a
 
 321   # [+] needs to be generated
 
 322   my $arr_prefix = sub {
 
 323     return $_[0] ? '[+]' : '[]' if $in_array;
 
 330       for my $key (sort keys %$source) {
 
 331         flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
 
 337       for my $i (0 .. $#$source) {
 
 338         flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
 
 343       die "can't flatten a pure scalar" unless defined $prefix;
 
 344       push @$target, [ $prefix . $arr_prefix->(0) => $source ];
 
 347     die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
 
 355   my ($data, $target) = @_;
 
 358   for my $pair (@$data) {
 
 359     _store_value($target, @$pair) if defined $pair->[0];
 
 371 SL::Request.pm - request parsing, data serialization, request information
 
 375 This module handles unpacking of CGI parameters. It also gives
 
 376 information about the request, such as whether or not it was done via AJAX,
 
 377 or the requested content type.
 
 379   use SL::Request qw(read_cgi_input);
 
 381   # read cgi input depending on request type, unflatten and recode
 
 382   read_cgi_input($target_hash_ref);
 
 384   # $hashref and $new_hashref should be identical
 
 385   my $new_arrayref = flatten($hashref);
 
 386   my $new_hashref  = unflatten($new_arrayref);
 
 388   # Handle AJAX requests differently than normal requests:
 
 389   if ($::request->is_ajax) {
 
 390     $controller->render('json-mask', { type => 'json' });
 
 392     $controller->render('full-mask');
 
 397 This module provides information about the request made by the
 
 400 It also handles flattening and unflattening of data for request
 
 401 roundtrip purposes. kivitendo uses the format as described below:
 
 407 Hash entries will be connected with a dot (C<.>). A simple hash like this
 
 414 will be serialized to
 
 417   [ order.customer => 5 ],
 
 421 Arrays will be marked by empty brackets (C<[]>). A hash like this
 
 423   selected_id => [ 2, 6, 8, 9 ]
 
 427   [ selected_id[] => 2 ],
 
 428   [ selected_id[] => 6 ],
 
 429   [ selected_id[] => 8 ],
 
 430   [ selected_id[] => 9 ],
 
 432 Since this will produce identical keys, the resulting flattened list can not be
 
 433 used as a hash. It is however very easy to use this in a template to generate
 
 436   [% FOREACH id = selected_ids %]
 
 437     <input type="hidden" name="selected_id[]" value="[% id | html %]">
 
 440 =item Nested structures
 
 442 A special version of this are nested hashes in an array, which is very common.
 
 443 The combined operator (C<[].>) will be used. As a special case, every time a new
 
 444 array slice is started, the special convention (C<[+].>) will be used. Again this
 
 445 is because it's easy to write a template with it.
 
 464   [ order.orderitems[+].id  => 1  ],
 
 465   [ order.orderitems[].part => 15 ],
 
 466   [ order.orderitems[+].id  => 2  ],
 
 467   [ order.orderitems[].part => 7  ],
 
 471   The format currently does have certain limitations when compared to other
 
 472   serialization formats.
 
 478 The order of serialized values matters to reconstruct arrays properly. This
 
 479 should rarely be a problem if you just flatten and dump into a url or a field
 
 484 The current implementation of flatten does produce correct serialization of
 
 485 empty keys, but unflatten is unable to resolve these. Do no use C<''> or
 
 486 C<undef> as keys. C<0> is fine.
 
 490 You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
 
 494 It is not possible to serialize something like
 
 496   sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
 
 498 This is a feature, as perl doesn't do well with very large arrays.
 
 502 There is currently no support nor prevention for flattening a circular structure.
 
 504 =item Custom Delimiter
 
 506 No support for other delimiters, sorry.
 
 508 =item Other References
 
 510 No support for globs, scalar refs, code refs, filehandles and the like. These will die.
 
 520 =item C<flatten HASHREF [ ARRAYREF ]>
 
 522 This function will flatten the provided hash ref into the provided array ref.
 
 523 The array ref may be non empty, but will be changed in this case.
 
 525 The return value is the flattened array ref.
 
 527 =item C<unflatten ARRAYREF [ HASHREF ]>
 
 529 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.
 
 533 Returns trueish if the request is an XML HTTP request, also known as
 
 538 Returns the requested content type (either C<html>, C<js> or C<json>).
 
 542 Set and retrieve the layout object for the current request. Must be an instance
 
 543 of L<SL::Layout::Base>. Defaults to an instance of L<SL::Layout::None>.
 
 545 For more information about layouts, see L<SL::Layout::Dispatcher>.
 
 547 =item C<cache $topic[, $default ]>
 
 549 Caches an item for the duration of the request. C<$topic> must be an
 
 550 index name referring to the thing to cache. It is used for retrieving
 
 551 it later on. If C<$topic> doesn't start with C<::> then the caller's
 
 552 package name is prepended to the topic. For example, if the a from
 
 553 package C<SL::StuffedStuff> calls with topic = C<get_stuff> then the
 
 554 actual key will be C<::SL::StuffedStuff::get_stuff>.
 
 556 If no item exists in the cache for C<$topic> then it is created and
 
 557 its initial value is set to C<$default>. If C<$default> is not given
 
 558 (undefined) then a new, empty hash reference is created.
 
 560 Returns the cached item.
 
 564 =head1 SPECIAL FUNCTIONS
 
 566 =head2 C<_store_value()>
 
 568 Parses a complex var name, and stores it in the form.
 
 571   _store_value($target, $key, $value);
 
 573 Keys must start with a string, and can contain various tokens.
 
 574 Supported key structures are:
 
 577   Simple key strings work as expected
 
 582   Separating two keys by a dot (.) will result in a hash lookup for the inner value
 
 583   This is similar to the behaviour of java and templating mechanisms.
 
 585   filter.description => $form->{filter}->{description}
 
 587 3. array+hashref access
 
 589   Adding brackets ([]) before the dot will cause the next hash to be put into an array.
 
 590   Using [+] instead of [] will force a new array index. This is useful for recurring
 
 591   data structures like part lists. Put a [+] into the first varname, and use [] on the
 
 594   Repeating these names in your template:
 
 597     invoice.items[].parts_id
 
 601     $form->{invoice}->{items}->[
 
 615   Using brackets at the end of a name will result in the creation of a pure array.
 
 616   Note that you mustn't use [+], which is reserved for array+hash access and will
 
 617   result in undefined behaviour in array context.
 
 619   filter.status[]  => $form->{status}->[ val1, val2, ... ]