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 ($target, $key, $value) = @_;
46 my @tokens = split /((?:\[\+?\])?(?:\.)|(?:\[\+?\]))/, $key;
50 $curr = \ $target->{ shift @tokens };
54 my $sep = shift @tokens;
55 my $key = shift @tokens;
57 $curr = \ $$curr->[$#$$curr], next if $sep eq '[]' && @tokens;
58 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]' && !@tokens;
59 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[+]';
60 $curr = \ $$curr->[max 0, $#$$curr] if $sep eq '[].';
61 $curr = \ $$curr->[++$#$$curr] if $sep eq '[+].';
62 $curr = \ $$curr->{$key}
71 $::lxdebug->enter_sub(2);
73 my ($target, $input, $log) = @_;
74 my @pairs = split(/&/, $input);
77 my ($key, $value) = split(/=/, $_, 2);
79 _store_value($target, uri_decode($key), uri_decode($value));
82 $::lxdebug->add_request_params(uri_decode($key) => uri_decode($value)) if $log;
85 $::lxdebug->leave_sub(2);
88 sub _parse_multipart_formdata {
89 my ($target, $temp_target, $input, $log) = @_;
90 my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $p_attachment, $encoding, $transfer_encoding);
93 # teach substr and length to use good ol' bytes, not 'em fancy characters
96 # We SHOULD honor encodings and transfer-encodings here, but as hard as I
97 # looked I couldn't find a reasonably recent webbrowser that makes use of
98 # these. Transfer encoding just eats up bandwidth...
100 # so all I'm going to do is add a fail safe that if anyone ever encounters
101 # this, it's going to croak so that debugging is easier
102 $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
103 my $boundary = '--' . $1;
107 foreach my $line (split m/\n/, $input) {
108 $line_length = length $line;
110 if ($line =~ /^\Q$boundary\E(--)?\r?$/) {
111 my $last_boundary = $1;
112 my $data = substr $input, $data_start, $index - $data_start;
115 if ($previous && !$filename && $transfer_encoding && $transfer_encoding ne 'binary') {
116 ${ $previous } = Encode::decode($encoding, $data);
118 ${ $previous } = $data;
120 $::lxdebug->add_request_params($name, $$previous) if $log;
126 $content_type = "text/plain";
129 $encoding = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
130 $transfer_encoding = undef;
131 last if $last_boundary;
135 next unless $boundary_found;
137 if (!$headers_done) {
138 $line =~ s/[\r\n]*$//;
142 $data_start = $index + $line_length + 1;
146 if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
147 if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
149 substr $line, $-[0], $+[0] - $-[0], "";
152 if ($line =~ m|name\s*=\s*"(.*?)"|i) {
154 substr $line, $-[0], $+[0] - $-[0], "";
158 # legacy, some old upload routines expect this to be here
159 $temp_target->{FILENAME} = $filename if defined $filename;
161 # name can potentially be both a normal variable or a file upload
162 # a file upload can be identified by its "filename" attribute
163 # the thing is, if a [+] clause vivifies atructur in one of the
164 # branches it must be done in both, or subsequent "[]" will fail
165 my $temp_target_slot = _store_value($temp_target, $name);
166 my $target_slot = _store_value($target, $name);
168 # set the reference for appending of multiline data to the correct one
169 $previous = defined $filename ? $target_slot : $temp_target_slot;
171 # for multiple uploads: save the attachments in a SL/Mailer like structure
172 if (defined $filename) {
173 my $target_attachment = _store_value($target, "ATTACHMENTS.$name", {});
174 my $temp_target_attachment = _store_value($temp_target, "ATTACHMENTS.$name", {});
176 $$target_attachment->{data} = $previous;
177 $$temp_target_attachment->{filename} = $filename;
179 $p_attachment = $$temp_target_attachment;
186 if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
188 $p_attachment->{content_type} = $1;
190 if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
197 if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
198 $transfer_encoding = lc($1);
199 if ($transfer_encoding && $transfer_encoding !~ /^[78]bit|binary$/) {
200 die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
202 $p_attachment->{transfer_encoding} = $transfer_encoding;
210 next unless $previous;
213 $index += $line_length + 1;
216 $::lxdebug->leave_sub(2);
219 sub _recode_recursively {
220 $::lxdebug->enter_sub;
221 my ($iconv, $from, $to) = @_;
223 if (any { ref $from eq $_ } qw(Form HASH)) {
224 for my $key (keys %{ $from }) {
225 if (!ref $from->{$key}) {
226 # Workaround for a bug: converting $from->{$key} directly
227 # leads to 'undef'. I don't know why. Converting a copy works,
229 $to->{$key} = $iconv->convert("" . $from->{$key}) if defined $from->{$key} && !defined $to->{$key};
231 $to->{$key} ||= {} if 'HASH' eq ref $from->{$key};
232 $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
233 _recode_recursively($iconv, $from->{$key}, $to->{$key});
237 } elsif (ref $from eq 'ARRAY') {
238 foreach my $idx (0 .. scalar(@{ $from }) - 1) {
239 if (!ref $from->[$idx]) {
240 # Workaround for a bug: converting $from->[$idx] directly
241 # leads to 'undef'. I don't know why. Converting a copy works,
243 $to->[$idx] = $iconv->convert("" . $from->[$idx]);
245 $to->[$idx] ||= {} if 'HASH' eq ref $from->[$idx];
246 $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
247 _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
251 $main::lxdebug->leave_sub();
255 $::lxdebug->enter_sub;
258 my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
260 # yes i know, copying all those values around isn't terribly efficient, but
261 # the old version of dumping everything into form and then launching a
262 # tactical recode nuke at the data is still worse.
264 # this way the data can at least be recoded on the fly as soon as we get to
265 # know the source encoding and only in the cases where encoding may be hidden
266 # among the payload we take the hit of copying the request around
267 my $temp_target = { };
269 # since both of these can potentially bring their encoding in INPUT_ENCODING
270 # they get dumped into temp_target
271 _input_to_hash($temp_target, $ENV{QUERY_STRING}, 1) if $ENV{QUERY_STRING};
272 _input_to_hash($temp_target, $ARGV[0], 1) if @ARGV && $ARGV[0];
274 if ($ENV{CONTENT_LENGTH}) {
276 read STDIN, $content, $ENV{CONTENT_LENGTH};
277 if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
278 # multipart formdata can bring it's own encoding, so give it both
279 # and let ti decide on it's own
280 _parse_multipart_formdata($target, $temp_target, $content, 1);
282 # normal encoding must be recoded
283 _input_to_hash($temp_target, $content, 1);
287 my $encoding = delete $temp_target->{INPUT_ENCODING} || $db_charset;
289 _recode_recursively(SL::Iconv->new($encoding, $db_charset), $temp_target => $target) if keys %$target;
291 if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
293 $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
294 _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
297 $::lxdebug->leave_sub;
303 my ($source, $target, $prefix, $in_array) = @_;
306 # there are two edge cases that need attention. first: more than one hash
307 # inside an array. only the first of each nested can have a [+]. second: if
308 # an array contains mixed values _store_value will rely on autovivification.
309 # so any type change must have a [+]
310 # this closure decides one recursion step AFTER an array has been found if a
311 # [+] needs to be generated
312 my $arr_prefix = sub {
313 return $_[0] ? '[+]' : '[]' if $in_array;
320 for my $key (sort keys %$source) {
321 flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
327 for my $i (0 .. $#$source) {
328 flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
333 die "can't flatten a pure scalar" unless defined $prefix;
334 push @$target, [ $prefix . $arr_prefix->(0) => $source ];
337 die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
345 my ($data, $target) = @_;
348 for my $pair (@$data) {
349 _store_value($target, @$pair) if defined $pair->[0];
361 SL::Request.pm - request parsing, data serialization, request information
365 This module handles unpacking of CGI parameters. It also gives
366 information about the request like whether or not it was done via AJAX
367 or the requested content type.
369 use SL::Request qw(read_cgi_input);
371 # read cgi input depending on request type, unflatten and recode
372 read_cgi_input($target_hash_ref);
374 # $hashref and $new_hashref should be identical
375 my $new_arrayref = flatten($hashref);
376 my $new_hashref = unflatten($new_arrayref);
378 # Handle AJAX requests differently than normal requests:
379 if ($::request->is_ajax) {
380 $controller->render('json-mask', { type => 'json' });
382 $controller->render('full-mask');
387 This module provides information about the request made by the
390 It also handles flattening and unflattening of data for request
391 roundtrip purposes. kivitendo uses the format as described below:
397 Hash entries will be connected with a dot (C<.>). A simple hash like this
404 will be serialized to
407 [ order.customer => 5 ],
411 Arrays will by trailing empty brackets (C<[]>). An hash like this
413 selected_id => [ 2, 6, 8, 9 ]
417 [ selected_id[] => 2 ],
418 [ selected_id[] => 6 ],
419 [ selected_id[] => 8 ],
420 [ selected_id[] => 9 ],
422 Since this will produce identical keys, the resulting flattened list can not be
423 used as a hash. It is however very easy to use this in a template to generate
426 [% FOREACH id = selected_ids %]
427 <input type="hidden" name="selected_id[]" value="[% id | html %]">
430 =item Nested structures
432 A special version of this are nested hashs in an array, which is very common.
433 The combined operator (C<[].>) will be used. As a special case, every time a new
434 array slice is started, the special convention (C<[+].>) will be used. Again this
435 is because it's easy to write a template with it.
454 [ order.orderitems[+].id => 1 ],
455 [ order.orderitems[].part => 15 ],
456 [ order.orderitems[+].id => 2 ],
457 [ order.orderitems[].part => 7 ],
461 The format currently does have certain limitations when compared to other
462 serialization formats.
468 The order of serialized values matters to reconstruct arrays properly. This
469 should rarely be a problem if you just flatten and dump into a url or a field
474 The current implementation of flatten does produce correct serialization of
475 empty keys, but unflatten is unable to resolve these. Do no use C<''> or
476 C<undef> as keys. C<0> is fine.
480 You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
484 It is not possible to serialize somehing like
486 sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
488 This is a feature, as perl doesn't do well with very large arrays.
492 There is currently no support nor prevention for flattening a circular structure.
494 =item Custom Delimiter
496 No support for other delimiters, sorry.
498 =item Other References
500 No support for globs, scalar refs, code refs, filehandles and the like. These will die.
510 =item C<flatten HASHREF [ ARRAYREF ]>
512 This function will flatten the provided hash ref into the provided array ref.
513 The array ref may be non empty, but will be changed in this case.
515 Return value is the flattened array ref.
517 =item C<unflatten ARRAYREF [ HASHREF ]>
519 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.
523 Returns trueish if the request is an XML HTTP request, also known as
528 Returns the requested content type (either C<html>, C<js> or C<json>).
532 Set and retrieve the layout object for the current request. Must be an instance
533 of L<SL::Layout::Base>. Defaults to an isntance of L<SL::Layout::None>.
535 For more information about layouts, see L<SL::Layout::Dispatcher>.
539 =head1 SPECIAL FUNCTIONS
541 =head2 C<_store_value()>
543 parses a complex var name, and stores it in the form.
546 _store_value($target, $key, $value);
548 keys must start with a string, and can contain various tokens.
549 supported key structures are:
552 simple key strings work as expected
557 separating two keys by a dot (.) will result in a hash lookup for the inner value
558 this is similar to the behaviour of java and templating mechanisms.
560 filter.description => $form->{filter}->{description}
562 3. array+hashref access
564 adding brackets ([]) before the dot will cause the next hash to be put into an array.
565 using [+] instead of [] will force a new array index. this is useful for recurring
566 data structures like part lists. put a [+] into the first varname, and use [] on the
569 repeating these names in your template:
572 invoice.items[].parts_id
576 $form->{invoice}->{items}->[
590 using brackets at the end of a name will result in a pure array to be created.
591 note that you mustn't use [+], which is reserved for array+hash access and will
592 result in undefined behaviour in array context.
594 filter.status[] => $form->{status}->[ val1, val2, ... ]