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);
9 use Exporter qw(import);
11 our @EXPORT_OK = qw(flatten unflatten read_cgi_input);
14 my ($target, $key, $value) = @_;
15 my @tokens = split /((?:\[\+?\])?(?:\.)|(?:\[\+?\]))/, $key;
19 $curr = \ $target->{ shift @tokens };
23 my $sep = shift @tokens;
24 my $key = shift @tokens;
26 $curr = \ $$curr->[$#$$curr], next if $sep eq '[]' && @tokens;
27 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]' && !@tokens;
28 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[+]';
29 $curr = \ $$curr->[max 0, $#$$curr] if $sep eq '[].';
30 $curr = \ $$curr->[++$#$$curr] if $sep eq '[+].';
31 $curr = \ $$curr->{$key}
40 $::lxdebug->enter_sub(2);
42 my ($target, $input) = @_;
43 my @pairs = split(/&/, $input);
46 my ($key, $value) = split(/=/, $_, 2);
47 _store_value($target, uri_decode($key), uri_decode($value)) if ($key);
50 $::lxdebug->leave_sub(2);
53 sub _parse_multipart_formdata {
54 my ($target, $temp_target, $input) = @_;
55 my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $p_attachment, $encoding, $transfer_encoding);
58 # teach substr and length to use good ol' bytes, not 'em fancy characters
61 # We SHOULD honor encodings and transfer-encodings here, but as hard as I
62 # looked I couldn't find a reasonably recent webbrowser that makes use of
63 # these. Transfer encoding just eats up bandwidth...
65 # so all I'm going to do is add a fail safe that if anyone ever encounters
66 # this, it's going to croak so that debugging is easier
67 $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
68 my $boundary = '--' . $1;
72 foreach my $line (split m/\n/, $input) {
73 $line_length = length $line;
75 if ($line =~ /^\Q$boundary\E(--)?\r?$/) {
76 my $last_boundary = $1;
77 my $data = substr $input, $data_start, $index - $data_start;
80 if ($previous && !$filename && $transfer_encoding && $transfer_encoding ne 'binary') {
81 ${ $previous } = Encode::decode($encoding, $data);
83 ${ $previous } = $data;
90 $content_type = "text/plain";
93 $encoding = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
94 $transfer_encoding = undef;
95 last if $last_boundary;
99 next unless $boundary_found;
101 if (!$headers_done) {
102 $line =~ s/[\r\n]*$//;
106 $data_start = $index + $line_length + 1;
110 if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
111 if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
113 substr $line, $-[0], $+[0] - $-[0], "";
116 if ($line =~ m|name\s*=\s*"(.*?)"|i) {
118 substr $line, $-[0], $+[0] - $-[0], "";
122 # legacy, some old upload routines expect this to be here
123 $temp_target->{FILENAME} = $filename if defined $filename;
125 # name can potentially be both a normal variable or a file upload
126 # a file upload can be identified by its "filename" attribute
127 # the thing is, if a [+] clause vivifies atructur in one of the
128 # branches it must be done in both, or subsequent "[]" will fail
129 my $temp_target_slot = _store_value($temp_target, $name);
130 my $target_slot = _store_value($target, $name);
132 # set the reference for appending of multiline data to the correct one
133 $previous = defined $filename ? $target_slot : $temp_target_slot;
135 # for multiple uploads: save the attachments in a SL/Mailer like structure
136 if (defined $filename) {
137 my $target_attachment = _store_value($target, "ATTACHMENTS.$name", {});
138 my $temp_target_attachment = _store_value($temp_target, "ATTACHMENTS.$name", {});
140 $$target_attachment->{data} = $previous;
141 $$temp_target_attachment->{filename} = $filename;
143 $p_attachment = $$temp_target_attachment;
150 if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
152 $p_attachment->{content_type} = $1;
154 if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
161 if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
162 $transfer_encoding = lc($1);
163 if ($transfer_encoding && $transfer_encoding !~ /^[78]bit|binary$/) {
164 die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
166 $p_attachment->{transfer_encoding} = $transfer_encoding;
174 next unless $previous;
177 $index += $line_length + 1;
180 $::lxdebug->leave_sub(2);
183 sub _recode_recursively {
184 $::lxdebug->enter_sub;
185 my ($iconv, $from, $to) = @_;
187 if (any { ref $from eq $_ } qw(Form HASH)) {
188 for my $key (keys %{ $from }) {
189 if (!ref $from->{$key}) {
190 # Workaround for a bug: converting $from->{$key} directly
191 # leads to 'undef'. I don't know why. Converting a copy works,
193 $to->{$key} = $iconv->convert("" . $from->{$key}) if defined $from->{$key} && !defined $to->{$key};
195 $to->{$key} ||= {} if 'HASH' eq ref $from->{$key};
196 $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
197 _recode_recursively($iconv, $from->{$key}, $to->{$key});
201 } elsif (ref $from eq 'ARRAY') {
202 foreach my $idx (0 .. scalar(@{ $from }) - 1) {
203 if (!ref $from->[$idx]) {
204 # Workaround for a bug: converting $from->[$idx] directly
205 # leads to 'undef'. I don't know why. Converting a copy works,
207 $to->[$idx] = $iconv->convert("" . $from->[$idx]);
209 $to->[$idx] ||= {} if 'HASH' eq ref $from->[$idx];
210 $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
211 _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
215 $main::lxdebug->leave_sub();
219 $::lxdebug->enter_sub;
222 my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
224 # yes i know, copying all those values around isn't terribly efficient, but
225 # the old version of dumping everything into form and then launching a
226 # tactical recode nuke at the data is still worse.
228 # this way the data can at least be recoded on the fly as soon as we get to
229 # know the source encoding and only in the cases where encoding may be hidden
230 # among the payload we take the hit of copying the request around
231 my $temp_target = { };
233 # since both of these can potentially bring their encoding in INPUT_ENCODING
234 # they get dumped into temp_target
235 _input_to_hash($temp_target, $ENV{QUERY_STRING}) if $ENV{QUERY_STRING};
236 _input_to_hash($temp_target, $ARGV[0]) if @ARGV && $ARGV[0];
238 if ($ENV{CONTENT_LENGTH}) {
240 read STDIN, $content, $ENV{CONTENT_LENGTH};
241 if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
242 # multipart formdata can bring it's own encoding, so give it both
243 # and let ti decide on it's own
244 _parse_multipart_formdata($target, $temp_target, $content);
246 # normal encoding must be recoded
247 _input_to_hash($temp_target, $content);
251 my $encoding = delete $temp_target->{INPUT_ENCODING} || $db_charset;
253 _recode_recursively(SL::Iconv->new($encoding, $db_charset), $temp_target => $target) if keys %$target;
255 if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
257 $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
258 _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
261 $::lxdebug->leave_sub;
267 my ($source, $target, $prefix, $in_array) = @_;
270 # there are two edge cases that need attention. first: more than one hash
271 # inside an array. only the first of each nested can have a [+]. second: if
272 # an array contains mixed values _store_value will rely on autovivification.
273 # so any type change must have a [+]
274 # this closure decides one recursion step AFTER an array has been found if a
275 # [+] needs to be generated
276 my $arr_prefix = sub {
277 return $_[0] ? '[+]' : '[]' if $in_array;
284 for my $key (keys %$source) {
285 flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
291 for my $i (0 .. $#$source) {
292 flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
297 die "can't flatten a pure scalar" unless defined $prefix;
298 push @$target, [ $prefix . $arr_prefix->(0) => $source ];
301 die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
309 my ($data, $target) = @_;
312 for my $pair (@$data) {
313 _store_value($target, @$pair) if defined $pair->[0];
325 SL::Request.pm - request parsing and data serialization
329 This module handles unpacking of cgi parameters. usually you don't want to call
330 anything in here directly.
332 use SL::Request qw(read_cgi_input);
334 # read cgi input depending on request type, unflatten and recode
335 read_cgi_input($target_hash_ref);
337 # $hashref and $new_hashref should be identical
338 my $new_arrayref = flatten($hashref);
339 my $new_hashref = unflatten($new_arrayref);
344 This module handles flattening and unflattening of data for request
345 roundtrip purposes. kivitendo uses the format as described below:
351 Hash entries will be connected with a dot (C<.>). A simple hash like this
358 will be serialized to
361 [ order.customer => 5 ],
365 Arrays will by trailing empty brackets (C<[]>). An hash like this
367 selected_id => [ 2, 6, 8, 9 ]
371 [ selected_id[] => 2 ],
372 [ selected_id[] => 6 ],
373 [ selected_id[] => 8 ],
374 [ selected_id[] => 9 ],
376 Since this will produce identical keys, the resulting flattened list can not be
377 used as a hash. It is however very easy to use this in a template to generate
380 [% FOREACH id = selected_ids %]
381 <input type="hidden" name="selected_id[]" value="[% id | html %]">
384 =item Nested structures
386 A special version of this are nested hashs in an array, which is very common.
387 The combined operator (C<[].>) will be used. As a special case, every time a new
388 array slice is started, the special convention (C<[+].>) will be used. Again this
389 is because it's easy to write a template with it.
408 [ order.orderitems[+].id => 1 ],
409 [ order.orderitems[].part => 15 ],
410 [ order.orderitems[+].id => 2 ],
411 [ order.orderitems[].part => 7 ],
415 The format currently does have certain limitations when compared to other
416 serialization formats.
422 The order of serialized values matters to reconstruct arrays properly. This
423 should rarely be a problem if you just flatten and dump into a url or a field
428 The current implementation of flatten does produce correct serialization of
429 empty keys, but unflatten is unable to resolve these. Do no use C<''> or
430 C<undef> as keys. C<0> is fine.
434 You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
438 It is not possible to serialize somehing like
440 sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
442 This is a feature, as perl doesn't do well with very large arrays.
446 There is currently no support nor prevention for flattening a circular structure.
448 =item Custom Delimiter
450 No support for other delimiters, sorry.
452 =item Other References
454 No support for globs, scalar refs, code refs, filehandles and the like. These will die.
464 =item C<flatten HASHREF [ ARRAYREF ]>
466 This function will flatten the provided hash ref into the provided array ref.
467 The array ref may be non empty, but will be changed in this case.
469 Return value is the flattened array ref.
471 =item C<unflatten ARRAYREF [ HASHREF ]>
473 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.
477 =head1 SPECIAL FUNCTIONS
479 =head2 C<_store_value()>
481 parses a complex var name, and stores it in the form.
484 _store_value($target, $key, $value);
486 keys must start with a string, and can contain various tokens.
487 supported key structures are:
490 simple key strings work as expected
495 separating two keys by a dot (.) will result in a hash lookup for the inner value
496 this is similar to the behaviour of java and templating mechanisms.
498 filter.description => $form->{filter}->{description}
500 3. array+hashref access
502 adding brackets ([]) before the dot will cause the next hash to be put into an array.
503 using [+] instead of [] will force a new array index. this is useful for recurring
504 data structures like part lists. put a [+] into the first varname, and use [] on the
507 repeating these names in your template:
510 invoice.items[].parts_id
514 $form->{invoice}->{items}->[
528 using brackets at the end of a name will result in a pure array to be created.
529 note that you mustn't use [+], which is reserved for array+hash access and will
530 result in undefined behaviour in array context.
532 filter.status[] => $form->{status}->[ val1, val2, ... ]