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);
14 use SL::MoreCommon qw(uri_encode uri_decode);
18 our @EXPORT_OK = qw(flatten unflatten);
20 use Rose::Object::MakeMethods::Generic
22 scalar => [ qw(applying_database_upgrades post_data) ],
23 'scalar --get_set_init' => [ qw(cgi layout presenter is_ajax type) ],
31 return SL::Layout::None->new;
35 return SL::Presenter->new;
39 return ($ENV{HTTP_X_REQUESTED_WITH} || '') eq 'XMLHttpRequest' ? 1 : 0;
47 $ENV{HTTPS} && 'on' eq lc $ENV{HTTPS};
51 my ($self, $topic, $default) = @_;
53 $topic = '::' . (caller(0))[0] . "::$topic" unless $topic =~ m{^::};
55 $self->{_cache} //= {};
56 $self->{_cache}->{$topic} //= ($default // {});
58 return $self->{_cache}->{$topic};
62 my ($target, $key, $value) = @_;
63 my @tokens = split /((?:\[\+?\])?(?:\.)|(?:\[\+?\]))/, $key;
67 $curr = \ $target->{ shift @tokens };
71 my $sep = shift @tokens;
72 my $key = shift @tokens;
74 $curr = \ $$curr->[$#$$curr], next if $sep eq '[]' && @tokens;
75 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]' && !@tokens;
76 $curr = \ $$curr->[++$#$$curr], next if $sep eq '[+]';
77 $curr = \ $$curr->[max 0, $#$$curr] if $sep eq '[].';
78 $curr = \ $$curr->[++$#$$curr] if $sep eq '[+].';
79 $curr = \ $$curr->{$key}
88 $::lxdebug->enter_sub(2);
90 my ($target, $input, $log) = @_;
91 my @pairs = split(/&/, $input);
94 my ($key, $value) = split(/=/, $_, 2);
96 _store_value($target, uri_decode($key), uri_decode($value));
99 $::lxdebug->add_request_params(uri_decode($key) => uri_decode($value)) if $log;
102 $::lxdebug->leave_sub(2);
105 sub _parse_multipart_formdata {
106 my ($target, $temp_target, $input, $log) = @_;
107 my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous, $p_attachment, $encoding, $transfer_encoding);
110 # teach substr and length to use good ol' bytes, not 'em fancy characters
113 # We SHOULD honor encodings and transfer-encodings here, but as hard as I
114 # looked I couldn't find a reasonably recent webbrowser that makes use of
115 # these. Transfer encoding just eats up bandwidth...
117 # so all I'm going to do is add a fail safe that if anyone ever encounters
118 # this, it's going to croak so that debugging is easier
119 $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/;
120 my $boundary = '--' . $1;
124 foreach my $line (split m/\n/, $input) {
125 $line_length = length $line;
127 if ($line =~ /^\Q$boundary\E(--)?\r?$/) {
128 my $last_boundary = $1;
129 my $data = substr $input, $data_start, $index - $data_start;
132 if ($previous && !$filename && $transfer_encoding && $transfer_encoding ne 'binary') {
133 ${ $previous } = Encode::decode($encoding, $data);
135 ${ $previous } = $data;
137 $::lxdebug->add_request_params($name, $$previous) if $log;
143 $content_type = "text/plain";
147 $transfer_encoding = undef;
148 last if $last_boundary;
152 next unless $boundary_found;
154 if (!$headers_done) {
155 $line =~ s/[\r\n]*$//;
159 $data_start = $index + $line_length + 1;
163 if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) {
164 if ($line =~ m|filename\s*=\s*"(.*?)"|i) {
166 substr $line, $-[0], $+[0] - $-[0], "";
169 if ($line =~ m|name\s*=\s*"(.*?)"|i) {
171 substr $line, $-[0], $+[0] - $-[0], "";
175 # legacy, some old upload routines expect this to be here
176 $temp_target->{FILENAME} = $filename if defined $filename;
178 # Name can potentially be both a normal variable or a file upload.
179 # A file upload can be identified by its "filename" attribute.
180 # The thing is, if a [+] clause vivifies structure in one of the
181 # branches it must be done in both, or subsequent "[]" will fail
182 my $temp_target_slot = _store_value($temp_target, $name);
183 my $target_slot = _store_value($target, $name);
185 # set the reference for appending of multiline data to the correct one
186 $previous = defined $filename ? $target_slot : $temp_target_slot;
188 # for multiple uploads: save the attachments in a SL/Mailer like structure
189 if (defined $filename) {
190 my $target_attachment = _store_value($target, "ATTACHMENTS.$name", {});
191 my $temp_target_attachment = _store_value($temp_target, "ATTACHMENTS.$name", {});
193 $$target_attachment->{data} = $previous;
194 $$temp_target_attachment->{filename} = $filename;
196 $p_attachment = $$temp_target_attachment;
203 if ($line =~ m|^content-type\s*:\s*(.*?)[;\$]|i) {
205 $p_attachment->{content_type} = $1;
207 if ($content_type =~ /^text/ && $line =~ m|;\s*charset\s*:\s*("?)(.*?)\1$|i) {
214 if ($line =~ m|^content-transfer-encoding\s*=\s*(.*?)$|i) {
215 $transfer_encoding = lc($1);
216 if ($transfer_encoding && $transfer_encoding !~ /^[78]bit|binary$/) {
217 die 'Transfer encodings beyond 7bit/8bit and binary are not implemented.';
219 $p_attachment->{transfer_encoding} = $transfer_encoding;
227 next unless $previous;
230 $index += $line_length + 1;
233 $::lxdebug->leave_sub(2);
236 sub _parse_json_formdata {
239 return $content ? SL::JSON::decode_json($content) : undef;
242 sub _recode_recursively {
243 $::lxdebug->enter_sub;
244 my ($iconv, $from, $to) = @_;
246 if (any { ref $from eq $_ } qw(Form HASH)) {
247 for my $key (keys %{ $from }) {
248 if (!ref $from->{$key}) {
249 # Workaround for a bug: converting $from->{$key} directly
250 # leads to 'undef'. I don't know why. Converting a copy works,
252 $to->{$key} = $iconv->convert("" . $from->{$key}) if defined $from->{$key} && !defined $to->{$key};
254 $to->{$key} ||= {} if 'HASH' eq ref $from->{$key};
255 $to->{$key} ||= [] if 'ARRAY' eq ref $from->{$key};
256 _recode_recursively($iconv, $from->{$key}, $to->{$key});
260 } elsif (ref $from eq 'ARRAY') {
261 foreach my $idx (0 .. scalar(@{ $from }) - 1) {
262 if (!ref $from->[$idx]) {
263 # Workaround for a bug: converting $from->[$idx] directly
264 # leads to 'undef'. I don't know why. Converting a copy works,
266 $to->[$idx] = $iconv->convert("" . $from->[$idx]) if defined $from->[$idx] && !defined $to->[$idx];
268 $to->[$idx] ||= {} if 'HASH' eq ref $from->[$idx];
269 $to->[$idx] ||= [] if 'ARRAY' eq ref $from->[$idx];
270 _recode_recursively($iconv, $from->[$idx], $to->[$idx]);
274 $main::lxdebug->leave_sub();
278 $::lxdebug->enter_sub;
280 my ($self, $target) = @_;
282 # yes i know, copying all those values around isn't terribly efficient, but
283 # the old version of dumping everything into form and then launching a
284 # tactical recode nuke at the data is still worse.
286 # this way the data can at least be recoded on the fly as soon as we get to
287 # know the source encoding and only in the cases where encoding may be hidden
288 # among the payload we take the hit of copying the request around
289 $self->post_data(undef);
290 my $data_to_decode = { };
291 my $iconv = SL::Iconv->new('UTF-8', 'UTF-8');
293 _input_to_hash($data_to_decode, $ENV{QUERY_STRING}, 1) if $ENV{QUERY_STRING};
294 _input_to_hash($data_to_decode, $ARGV[0], 1) if @ARGV && $ARGV[0];
296 if ($ENV{CONTENT_LENGTH}) {
298 read STDIN, $content, $ENV{CONTENT_LENGTH};
300 if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
301 $self->post_data({});
302 my $post_data_to_decode = { };
304 # multipart formdata can bring it's own encoding, so give it both
305 # and let it decide on it's own
306 _parse_multipart_formdata($self->post_data, $post_data_to_decode, $content, 1);
307 _recode_recursively($iconv, $post_data_to_decode, $self->post_data) if keys %$post_data_to_decode;
309 $target->{$_} = $self->post_data->{$_} for keys %{ $self->post_data };
311 } elsif (($ENV{CONTENT_TYPE} // '') =~ m{^application/json}i) {
312 $self->post_data(_parse_json_formdata($content));
315 # normal encoding must be recoded
316 _input_to_hash($data_to_decode, $content, 1);
320 _recode_recursively($iconv, $data_to_decode, $target) if keys %$data_to_decode;
322 if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
324 $::auth->restore_form_from_session(delete $target->{RESTORE_FORM_FROM_SESSION_ID}, form => \%temp_form);
325 _store_value($target, $_, $temp_form{$_}) for keys %temp_form;
328 $::lxdebug->leave_sub;
334 my ($source, $target, $prefix, $in_array) = @_;
337 # There are two edge cases that need attention. First: more than one hash
338 # inside an array. Only the first of each nested can have a [+]. Second: if
339 # an array contains mixed values _store_value will rely on autovivification.
340 # So any type change must have a [+]
341 # This closure decides one recursion step AFTER an array has been found if a
342 # [+] needs to be generated
343 my $arr_prefix = sub {
344 return $_[0] ? '[+]' : '[]' if $in_array;
351 for my $key (sort keys %$source) {
352 flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
358 for my $i (0 .. $#$source) {
359 flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
364 die "can't flatten a pure scalar" unless defined $prefix;
365 push @$target, [ $prefix . $arr_prefix->(0) => $source ];
368 die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
376 my ($data, $target) = @_;
379 for my $pair (@$data) {
380 _store_value($target, @$pair) if defined $pair->[0];
392 SL::Request.pm - request parsing, data serialization, request information
396 This module handles unpacking of CGI parameters. It also gives
397 information about the request, such as whether or not it was done via AJAX,
398 or the requested content type.
402 # read cgi input depending on request type, unflatten and recode
403 $::request->read_cgi_input($target_hash_ref);
405 # $hashref and $new_hashref should be identical
406 my $new_arrayref = flatten($hashref);
407 my $new_hashref = unflatten($new_arrayref);
409 # Handle AJAX requests differently than normal requests:
410 if ($::request->is_ajax) {
411 $controller->render('json-mask', { type => 'json' });
413 $controller->render('full-mask');
418 This module provides information about the request made by the
421 It also handles flattening and unflattening of data for request
422 roundtrip purposes. kivitendo uses the format as described below:
428 Hash entries will be connected with a dot (C<.>). A simple hash like this
435 will be serialized to
438 [ order.customer => 5 ],
442 Arrays will be marked by empty brackets (C<[]>). A hash like this
444 selected_id => [ 2, 6, 8, 9 ]
448 [ selected_id[] => 2 ],
449 [ selected_id[] => 6 ],
450 [ selected_id[] => 8 ],
451 [ selected_id[] => 9 ],
453 Since this will produce identical keys, the resulting flattened list can not be
454 used as a hash. It is however very easy to use this in a template to generate
457 [% FOREACH id = selected_ids %]
458 <input type="hidden" name="selected_id[]" value="[% id | html %]">
461 =item Nested structures
463 A special version of this are nested hashes in an array, which is very common.
464 The combined operator (C<[].>) will be used. As a special case, every time a new
465 array slice is started, the special convention (C<[+].>) will be used. Again this
466 is because it's easy to write a template with it.
485 [ order.orderitems[+].id => 1 ],
486 [ order.orderitems[].part => 15 ],
487 [ order.orderitems[+].id => 2 ],
488 [ order.orderitems[].part => 7 ],
492 The format currently does have certain limitations when compared to other
493 serialization formats.
499 The order of serialized values matters to reconstruct arrays properly. This
500 should rarely be a problem if you just flatten and dump into a url or a field
505 The current implementation of flatten does produce correct serialization of
506 empty keys, but unflatten is unable to resolve these. Do no use C<''> or
507 C<undef> as keys. C<0> is fine.
511 You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
515 It is not possible to serialize something like
517 sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
519 This is a feature, as perl doesn't do well with very large arrays.
523 There is currently no support nor prevention for flattening a circular structure.
525 =item Custom Delimiter
527 No support for other delimiters, sorry.
529 =item Other References
531 No support for globs, scalar refs, code refs, filehandles and the like. These will die.
541 =item C<flatten HASHREF [ ARRAYREF ]>
543 This function will flatten the provided hash ref into the provided array ref.
544 The array ref may be non empty, but will be changed in this case.
546 The return value is the flattened array ref.
548 =item C<unflatten ARRAYREF [ HASHREF ]>
550 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.
554 Returns trueish if the request is an XML HTTP request, also known as
559 Returns the requested content type (either C<html>, C<js> or C<json>).
563 Set and retrieve the layout object for the current request. Must be an instance
564 of L<SL::Layout::Base>. Defaults to an instance of L<SL::Layout::None>.
566 For more information about layouts, see L<SL::Layout::Dispatcher>.
568 =item C<cache $topic[, $default ]>
570 Caches an item for the duration of the request. C<$topic> must be an
571 index name referring to the thing to cache. It is used for retrieving
572 it later on. If C<$topic> doesn't start with C<::> then the caller's
573 package name is prepended to the topic. For example, if the a from
574 package C<SL::StuffedStuff> calls with topic = C<get_stuff> then the
575 actual key will be C<::SL::StuffedStuff::get_stuff>.
577 If no item exists in the cache for C<$topic> then it is created and
578 its initial value is set to C<$default>. If C<$default> is not given
579 (undefined) then a new, empty hash reference is created.
581 Returns the cached item.
585 If the client sends data in the request body with the content type of
586 either C<application/json> or C<multipart/form-data>, the content will
587 be stored in the global request object, too. It can be retrieved via
588 the C<post_data> function.
590 For content type C<multipart/form-data> the same data is additionally
591 stored in the global C<$::form> instance, potentially overwriting
592 parameters given in the URL. This is done primarily for compatibility
593 purposes with existing code that expects all parameters to be present
596 For content type C<application/json> the data is only available in
597 C<$::request>. The reason is that the top-level data in a JSON
598 documents doesn't have to be an object which could be mapped to the
599 hash C<$::form>. Instead, the top-level data can also be an
600 array. Additionally keeping the namespaces of URL and POST parameters
601 separate is cleaner and allows for fewer accidental conflicts.
605 =head1 SPECIAL FUNCTIONS
607 =head2 C<_store_value()>
609 Parses a complex var name, and stores it in the form.
612 _store_value($target, $key, $value);
614 Keys must start with a string, and can contain various tokens.
615 Supported key structures are:
618 Simple key strings work as expected
623 Separating two keys by a dot (.) will result in a hash lookup for the inner value
624 This is similar to the behaviour of java and templating mechanisms.
626 filter.description => $form->{filter}->{description}
628 3. array+hashref access
630 Adding brackets ([]) before the dot will cause the next hash to be put into an array.
631 Using [+] instead of [] will force a new array index. This is useful for recurring
632 data structures like part lists. Put a [+] into the first varname, and use [] on the
635 Repeating these names in your template:
638 invoice.items[].parts_id
642 $form->{invoice}->{items}->[
656 Using brackets at the end of a name will result in the creation of a pure array.
657 Note that you mustn't use [+], which is reserved for array+hash access and will
658 result in undefined behaviour in array context.
660 filter.status[] => $form->{status}->[ val1, val2, ... ]