6 use SL::MoreCommon qw(uri_encode uri_decode);
 
   7 use List::Util qw(first max min sum);
 
   8 use List::MoreUtils qw(all any apply);
 
  11   $::lxdebug->enter_sub(2);
 
  13   my ($target, $key, $value) = @_;
 
  14   my @tokens = split /((?:\[\+?\])?(?:\.|$))/, $key;
 
  18      $curr = \ $target->{ shift @tokens };
 
  22     my $sep = shift @tokens;
 
  23     my $key = shift @tokens;
 
  25     $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]';
 
  26     $curr = \ $$curr->[max 0, $#$$curr]  if $sep eq '[].';
 
  27     $curr = \ $$curr->[++$#$$curr]       if $sep eq '[+].';
 
  28     $curr = \ $$curr->{$key}
 
  33   $::lxdebug->leave_sub(2);
 
  39   $::lxdebug->enter_sub(2);
 
  41   my ($target, $input) = @_;
 
  42   my @pairs = split(/&/, $input);
 
  45     my ($key, $value) = split(/=/, $_, 2);
 
  46     _store_value($target, uri_decode($key), uri_decode($value)) if ($key);
 
  49   $::lxdebug->leave_sub(2);
 
  52 sub _parse_multipart_formdata {
 
  53   my ($target, $temp_target, $input) = @_;
 
  54   my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $encoding, $transfer_encoding);
 
  56   # We SHOULD honor encodings and transfer-encodings here, but as hard as I
 
  57   # looked I couldn't find a reasonably recent webbrowser that makes use of
 
  58   # these. Transfer encoding just eats up bandwidth...
 
  60   # so all I'm going to do is add a fail safe that if anyone ever encounters
 
  61   # this, it's going to croak so that debugging is easier
 
  62   $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
 
  63   my $boundary = '--' . $1;
 
  65   foreach my $line (split m/\n/, $input) {
 
  66     last if (($line eq "${boundary}--") || ($line eq "${boundary}--\r"));
 
  68     if (($line eq $boundary) || ($line eq "$boundary\r")) {
 
  69       ${ $previous } =~ s|\r?\n$|| if $previous;
 
  70       ${ $previous } =  Encode::decode($encoding, $$previous) if $previous && !$filename && !$transfer_encoding eq 'binary';
 
  76       $content_type   = "text/plain";
 
  79       $encoding       = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
 
  80       $transfer_encoding = undef;
 
  85     next unless $boundary_found;
 
  88       $line =~ s/[\r\n]*$//;
 
  95       if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
 
  96         if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
 
  98           substr $line, $-[0], $+[0] - $-[0], "";
 
 101         if ($line =~ m|name\s*=\s*"(.*?)"|i) {
 
 103           substr $line, $-[0], $+[0] - $-[0], "";
 
 106         $previous                = _store_value($filename ? $target : $temp_target, $name, '') if ($name);
 
 107         $temp_target->{FILENAME} = $filename if ($filename);
 
 112       if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
 
 115         if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
 
 122       if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
 
 123         $transfer_encoding = lc($1);
 
 124         if ($transfer_encoding  && $transfer_encoding !~ /^[78]bit|binary$/) {
 
 125           die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
 
 134     next unless $previous;
 
 136     ${ $previous } .= "${line}\n";
 
 139   ${ $previous } =~ s|\r?\n$|| if $previous;
 
 141   $::lxdebug->leave_sub(2);
 
 144 sub _recode_recursively {
 
 145   $::lxdebug->enter_sub;
 
 146   my ($iconv, $from, $to) = @_;
 
 148   if (any { ref $from eq $_ } qw(Form HASH)) {
 
 149     for my $key (keys %{ $from }) {
 
 150       if (!ref $from->{$key}) {
 
 151         # Workaround for a bug: converting $from->{$key} directly
 
 152         # leads to 'undef'. I don't know why. Converting a copy works,
 
 154         $to->{$key} = $iconv->convert("" . $from->{$key});
 
 156         $to->{$key} ||= {} if 'HASH'  eq ref $from->{$key};
 
 157         $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
 
 158         _recode_recursively($iconv, $from->{$key}, $to->{$key});
 
 162   } elsif (ref $from eq 'ARRAY') {
 
 163     foreach my $idx (0 .. scalar(@{ $from }) - 1) {
 
 164       if (!ref $from->[$idx]) {
 
 165         # Workaround for a bug: converting $from->[$idx] directly
 
 166         # leads to 'undef'. I don't know why. Converting a copy works,
 
 168         $from->[$idx] = $iconv->convert("" . $from->[$idx]);
 
 170         $to->[$idx] ||= {} if 'HASH'  eq ref $from->[$idx];
 
 171         $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
 
 172         _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
 
 176   $main::lxdebug->leave_sub();
 
 180   $::lxdebug->enter_sub;
 
 183   my $db_charset   = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
 
 185   # yes i know, copying all those values around isn't terribly efficient, but
 
 186   # the old version of dumping everything into form and then launching a
 
 187   # tactical recode nuke at the data is still worse.
 
 189   # this way the data can at least be recoded on the fly as soon as we get to
 
 190   # know the source encoding and only in the cases where encoding may be hidden
 
 191   # among the payload we take the hit of copying the request around
 
 192   my $temp_target = { };
 
 194   # since both of these can potentially bring their encoding in INPUT_ENCODING
 
 195   # they get dumped into temp_target
 
 196   _input_to_hash($temp_target, $ENV{QUERY_STRING}) if $ENV{QUERY_STRING};
 
 197   _input_to_hash($temp_target, $ARGV[0])           if @ARGV && $ARGV[0];
 
 199   if ($ENV{CONTENT_LENGTH}) {
 
 201     read STDIN, $content, $ENV{CONTENT_LENGTH};
 
 202     if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
 
 203       # multipart formdata can bring it's own encoding, so give it both
 
 204       # and let ti decide on it's own
 
 205       _parse_multipart_formdata($target, $temp_target, $content);
 
 207       # normal encoding must be recoded
 
 208       _input_to_hash($temp_target, $content);
 
 212   my $encoding     = delete $temp_target->{INPUT_ENCODING} || $db_charset;
 
 214   _recode_recursively(SL::Iconv->new($encoding, $db_charset), $temp_target => $target) if keys %$target;
 
 216   if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
 
 218     $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
 
 219     _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
 
 222   $::lxdebug->leave_sub;
 
 233 SL::Form.pm - main data object.
 
 237 This module handles unpacking of cgi parameters. usually you donĂ„t want to call
 
 238 anything in here directly,
 
 240   SL::Request::read_cgi_input($target_hash_ref);
 
 242 =head1 SPECIAL FUNCTIONS
 
 244 =head2 C<_store_value()>
 
 246 parses a complex var name, and stores it in the form.
 
 249   $form->_store_value($key, $value);
 
 251 keys must start with a string, and can contain various tokens.
 
 252 supported key structures are:
 
 255   simple key strings work as expected
 
 260   separating two keys by a dot (.) will result in a hash lookup for the inner value
 
 261   this is similar to the behaviour of java and templating mechanisms.
 
 263   filter.description => $form->{filter}->{description}
 
 265 3. array+hashref access
 
 267   adding brackets ([]) before the dot will cause the next hash to be put into an array.
 
 268   using [+] instead of [] will force a new array index. this is useful for recurring
 
 269   data structures like part lists. put a [+] into the first varname, and use [] on the
 
 272   repeating these names in your template:
 
 275     invoice.items[].parts_id
 
 279     $form->{invoice}->{items}->[
 
 293   using brackets at the end of a name will result in a pure array to be created.
 
 294   note that you mustn't use [+], which is reserved for array+hash access and will
 
 295   result in undefined behaviour in array context.
 
 297   filter.status[]  => $form->{status}->[ val1, val2, ... ]