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) = @_;
74 my @pairs = split(/&/, $input);
77 my ($key, $value) = split(/=/, $_, 2);
78 _store_value($target, uri_decode($key), uri_decode($value)) if ($key);
81 $::lxdebug->leave_sub(2);
84 sub _parse_multipart_formdata {
85 my ($target, $temp_target, $input) = @_;
86 my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $p_attachment, $encoding, $transfer_encoding);
89 # teach substr and length to use good ol' bytes, not 'em fancy characters
92 # We SHOULD honor encodings and transfer-encodings here, but as hard as I
93 # looked I couldn't find a reasonably recent webbrowser that makes use of
94 # these. Transfer encoding just eats up bandwidth...
96 # so all I'm going to do is add a fail safe that if anyone ever encounters
97 # this, it's going to croak so that debugging is easier
98 $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
99 my $boundary = '--' . $1;
103 foreach my $line (split m/\n/, $input) {
104 $line_length = length $line;
106 if ($line =~ /^\Q$boundary\E(--)?\r?$/) {
107 my $last_boundary = $1;
108 my $data = substr $input, $data_start, $index - $data_start;
111 if ($previous && !$filename && $transfer_encoding && $transfer_encoding ne 'binary') {
112 ${ $previous } = Encode::decode($encoding, $data);
114 ${ $previous } = $data;
121 $content_type = "text/plain";
124 $encoding = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
125 $transfer_encoding = undef;
126 last if $last_boundary;
130 next unless $boundary_found;
132 if (!$headers_done) {
133 $line =~ s/[\r\n]*$//;
137 $data_start = $index + $line_length + 1;
141 if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
142 if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
144 substr $line, $-[0], $+[0] - $-[0], "";
147 if ($line =~ m|name\s*=\s*"(.*?)"|i) {
149 substr $line, $-[0], $+[0] - $-[0], "";
153 # legacy, some old upload routines expect this to be here
154 $temp_target->{FILENAME} = $filename if defined $filename;
156 # name can potentially be both a normal variable or a file upload
157 # a file upload can be identified by its "filename" attribute
158 # the thing is, if a [+] clause vivifies atructur in one of the
159 # branches it must be done in both, or subsequent "[]" will fail
160 my $temp_target_slot = _store_value($temp_target, $name);
161 my $target_slot = _store_value($target, $name);
163 # set the reference for appending of multiline data to the correct one
164 $previous = defined $filename ? $target_slot : $temp_target_slot;
166 # for multiple uploads: save the attachments in a SL/Mailer like structure
167 if (defined $filename) {
168 my $target_attachment = _store_value($target, "ATTACHMENTS.$name", {});
169 my $temp_target_attachment = _store_value($temp_target, "ATTACHMENTS.$name", {});
171 $$target_attachment->{data} = $previous;
172 $$temp_target_attachment->{filename} = $filename;
174 $p_attachment = $$temp_target_attachment;
181 if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
183 $p_attachment->{content_type} = $1;
185 if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
192 if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
193 $transfer_encoding = lc($1);
194 if ($transfer_encoding && $transfer_encoding !~ /^[78]bit|binary$/) {
195 die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
197 $p_attachment->{transfer_encoding} = $transfer_encoding;
205 next unless $previous;
208 $index += $line_length + 1;
211 $::lxdebug->leave_sub(2);
214 sub _recode_recursively {
215 $::lxdebug->enter_sub;
216 my ($iconv, $from, $to) = @_;
218 if (any { ref $from eq $_ } qw(Form HASH)) {
219 for my $key (keys %{ $from }) {
220 if (!ref $from->{$key}) {
221 # Workaround for a bug: converting $from->{$key} directly
222 # leads to 'undef'. I don't know why. Converting a copy works,
224 $to->{$key} = $iconv->convert("" . $from->{$key}) if defined $from->{$key} && !defined $to->{$key};
226 $to->{$key} ||= {} if 'HASH' eq ref $from->{$key};
227 $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
228 _recode_recursively($iconv, $from->{$key}, $to->{$key});
232 } elsif (ref $from eq 'ARRAY') {
233 foreach my $idx (0 .. scalar(@{ $from }) - 1) {
234 if (!ref $from->[$idx]) {
235 # Workaround for a bug: converting $from->[$idx] directly
236 # leads to 'undef'. I don't know why. Converting a copy works,
238 $to->[$idx] = $iconv->convert("" . $from->[$idx]);
240 $to->[$idx] ||= {} if 'HASH' eq ref $from->[$idx];
241 $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
242 _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
246 $main::lxdebug->leave_sub();
250 $::lxdebug->enter_sub;
253 my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
255 # yes i know, copying all those values around isn't terribly efficient, but
256 # the old version of dumping everything into form and then launching a
257 # tactical recode nuke at the data is still worse.
259 # this way the data can at least be recoded on the fly as soon as we get to
260 # know the source encoding and only in the cases where encoding may be hidden
261 # among the payload we take the hit of copying the request around
262 my $temp_target = { };
264 # since both of these can potentially bring their encoding in INPUT_ENCODING
265 # they get dumped into temp_target
266 _input_to_hash($temp_target, $ENV{QUERY_STRING}) if $ENV{QUERY_STRING};
267 _input_to_hash($temp_target, $ARGV[0]) if @ARGV && $ARGV[0];
269 if ($ENV{CONTENT_LENGTH}) {
271 read STDIN, $content, $ENV{CONTENT_LENGTH};
272 if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
273 # multipart formdata can bring it's own encoding, so give it both
274 # and let ti decide on it's own
275 _parse_multipart_formdata($target, $temp_target, $content);
277 # normal encoding must be recoded
278 _input_to_hash($temp_target, $content);
282 my $encoding = delete $temp_target->{INPUT_ENCODING} || $db_charset;
284 _recode_recursively(SL::Iconv->new($encoding, $db_charset), $temp_target => $target) if keys %$target;
286 if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
288 $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
289 _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
292 $::lxdebug->leave_sub;
298 my ($source, $target, $prefix, $in_array) = @_;
301 # there are two edge cases that need attention. first: more than one hash
302 # inside an array. only the first of each nested can have a [+]. second: if
303 # an array contains mixed values _store_value will rely on autovivification.
304 # so any type change must have a [+]
305 # this closure decides one recursion step AFTER an array has been found if a
306 # [+] needs to be generated
307 my $arr_prefix = sub {
308 return $_[0] ? '[+]' : '[]' if $in_array;
315 for my $key (keys %$source) {
316 flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
322 for my $i (0 .. $#$source) {
323 flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
328 die "can't flatten a pure scalar" unless defined $prefix;
329 push @$target, [ $prefix . $arr_prefix->(0) => $source ];
332 die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
340 my ($data, $target) = @_;
343 for my $pair (@$data) {
344 _store_value($target, @$pair) if defined $pair->[0];
356 SL::Request.pm - request parsing, data serialization, request information
360 This module handles unpacking of CGI parameters. It also gives
361 information about the request like whether or not it was done via AJAX
362 or the requested content type.
364 use SL::Request qw(read_cgi_input);
366 # read cgi input depending on request type, unflatten and recode
367 read_cgi_input($target_hash_ref);
369 # $hashref and $new_hashref should be identical
370 my $new_arrayref = flatten($hashref);
371 my $new_hashref = unflatten($new_arrayref);
373 # Handle AJAX requests differently than normal requests:
374 if ($::request->is_ajax) {
375 $controller->render('json-mask', { type => 'json' });
377 $controller->render('full-mask');
382 This module provides information about the request made by the
385 It also handles flattening and unflattening of data for request
386 roundtrip purposes. kivitendo uses the format as described below:
392 Hash entries will be connected with a dot (C<.>). A simple hash like this
399 will be serialized to
402 [ order.customer => 5 ],
406 Arrays will by trailing empty brackets (C<[]>). An hash like this
408 selected_id => [ 2, 6, 8, 9 ]
412 [ selected_id[] => 2 ],
413 [ selected_id[] => 6 ],
414 [ selected_id[] => 8 ],
415 [ selected_id[] => 9 ],
417 Since this will produce identical keys, the resulting flattened list can not be
418 used as a hash. It is however very easy to use this in a template to generate
421 [% FOREACH id = selected_ids %]
422 <input type="hidden" name="selected_id[]" value="[% id | html %]">
425 =item Nested structures
427 A special version of this are nested hashs in an array, which is very common.
428 The combined operator (C<[].>) will be used. As a special case, every time a new
429 array slice is started, the special convention (C<[+].>) will be used. Again this
430 is because it's easy to write a template with it.
449 [ order.orderitems[+].id => 1 ],
450 [ order.orderitems[].part => 15 ],
451 [ order.orderitems[+].id => 2 ],
452 [ order.orderitems[].part => 7 ],
456 The format currently does have certain limitations when compared to other
457 serialization formats.
463 The order of serialized values matters to reconstruct arrays properly. This
464 should rarely be a problem if you just flatten and dump into a url or a field
469 The current implementation of flatten does produce correct serialization of
470 empty keys, but unflatten is unable to resolve these. Do no use C<''> or
471 C<undef> as keys. C<0> is fine.
475 You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
479 It is not possible to serialize somehing like
481 sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
483 This is a feature, as perl doesn't do well with very large arrays.
487 There is currently no support nor prevention for flattening a circular structure.
489 =item Custom Delimiter
491 No support for other delimiters, sorry.
493 =item Other References
495 No support for globs, scalar refs, code refs, filehandles and the like. These will die.
505 =item C<flatten HASHREF [ ARRAYREF ]>
507 This function will flatten the provided hash ref into the provided array ref.
508 The array ref may be non empty, but will be changed in this case.
510 Return value is the flattened array ref.
512 =item C<unflatten ARRAYREF [ HASHREF ]>
514 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.
518 Returns trueish if the request is an XML HTTP request, also known as
523 Returns the requested content type (either C<html>, C<js> or C<json>).
527 Set and retrieve the layout object for the current request. Must be an instance
528 of L<SL::Layout::Base>. Defaults to an isntance of L<SL::Layout::None>.
530 For more information about layouts, see L<SL::Layout::Dispatcher>.
534 =head1 SPECIAL FUNCTIONS
536 =head2 C<_store_value()>
538 parses a complex var name, and stores it in the form.
541 _store_value($target, $key, $value);
543 keys must start with a string, and can contain various tokens.
544 supported key structures are:
547 simple key strings work as expected
552 separating two keys by a dot (.) will result in a hash lookup for the inner value
553 this is similar to the behaviour of java and templating mechanisms.
555 filter.description => $form->{filter}->{description}
557 3. array+hashref access
559 adding brackets ([]) before the dot will cause the next hash to be put into an array.
560 using [+] instead of [] will force a new array index. this is useful for recurring
561 data structures like part lists. put a [+] into the first varname, and use [] on the
564 repeating these names in your template:
567 invoice.items[].parts_id
571 $form->{invoice}->{items}->[
585 using brackets at the end of a name will result in a pure array to be created.
586 note that you mustn't use [+], which is reserved for array+hash access and will
587 result in undefined behaviour in array context.
589 filter.status[] => $form->{status}->[ val1, val2, ... ]