1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::Web::Request;
10
11use strict;
12use warnings;
13
14use CGI ();
15use CGI::Carp;
16use File::Path qw();
17
18use Kernel::System::VariableCheck qw(:all);
19
20our @ObjectDependencies = (
21    'Kernel::Config',
22    'Kernel::System::CheckItem',
23    'Kernel::System::Encode',
24    'Kernel::System::Web::UploadCache',
25    'Kernel::System::FormDraft',
26    'Kernel::System::Main',
27);
28
29=head1 NAME
30
31Kernel::System::Web::Request - global CGI interface
32
33=head1 DESCRIPTION
34
35All cgi param functions.
36
37=head1 PUBLIC INTERFACE
38
39=head2 new()
40
41create param object. Do not use it directly, instead use:
42
43    use Kernel::System::ObjectManager;
44    local $Kernel::OM = Kernel::System::ObjectManager->new(
45        'Kernel::System::Web::Request' => {
46            WebRequest   => CGI::Fast->new(), # optional, e. g. if fast cgi is used
47        }
48    );
49    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
50
51If Kernel::System::Web::Request is instantiated several times, they will share the
52same CGI data (this can be helpful in filters which do not have access to the
53ParamObject, for example.
54
55If you need to reset the CGI data before creating a new instance, use
56
57    CGI::initialize_globals();
58
59before calling Kernel::System::Web::Request->new();
60
61=cut
62
63sub new {
64    my ( $Type, %Param ) = @_;
65
66    # allocate new hash for object
67    my $Self = {};
68    bless( $Self, $Type );
69
70    # get config object
71    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
72
73    # max 5 MB posts
74    $CGI::POST_MAX = $ConfigObject->Get('WebMaxFileUpload') || 1024 * 1024 * 5;    ## no critic
75
76    # query object (in case use already existing WebRequest, e. g. fast cgi)
77    $Self->{Query} = $Param{WebRequest} || CGI->new();
78
79    return $Self;
80}
81
82=head2 Error()
83
84to get the error back
85
86    if ( $ParamObject->Error() ) {
87        print STDERR $ParamObject->Error() . "\n";
88    }
89
90=cut
91
92sub Error {
93    my ( $Self, %Param ) = @_;
94
95    # Workaround, do not check cgi_error() with perlex, CGI module is not
96    # working with perlex.
97    if ( $ENV{'GATEWAY_INTERFACE'} && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/ ) {
98        return;
99    }
100
101    return if !$Self->{Query}->cgi_error();
102    ## no critic
103    return $Self->{Query}->cgi_error() . ' - POST_MAX=' . ( $CGI::POST_MAX / 1024 ) . 'KB';
104    ## use critic
105}
106
107=head2 GetParam()
108
109to get single request parameters. By default, trimming is performed on the data.
110
111    my $Param = $ParamObject->GetParam(
112        Param => 'ID',
113        Raw   => 1,       # optional, input data is not changed
114    );
115
116=cut
117
118sub GetParam {
119    my ( $Self, %Param ) = @_;
120
121    my $Value = $Self->{Query}->param( $Param{Param} );
122
123    # Fallback to query string for mixed requests.
124    my $RequestMethod = $Self->{Query}->request_method() // '';
125    if ( $RequestMethod eq 'POST' && !defined $Value ) {
126        $Value = $Self->{Query}->url_param( $Param{Param} );
127    }
128
129    $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Value );
130
131    my $Raw = defined $Param{Raw} ? $Param{Raw} : 0;
132
133    if ( !$Raw ) {
134
135        # If it is a plain string, perform trimming
136        if ( ref \$Value eq 'SCALAR' ) {
137            $Kernel::OM->Get('Kernel::System::CheckItem')->StringClean(
138                StringRef => \$Value,
139                TrimLeft  => 1,
140                TrimRight => 1,
141            );
142        }
143    }
144
145    return $Value;
146}
147
148=head2 GetParamNames()
149
150to get names of all parameters passed to the script.
151
152    my @ParamNames = $ParamObject->GetParamNames();
153
154Example:
155
156Called URL: index.pl?Action=AdminSystemConfiguration;Subaction=Save;Name=Config::Option::Valid
157
158    my @ParamNames = $ParamObject->GetParamNames();
159    print join " :: ", @ParamNames;
160    #prints Action :: Subaction :: Name
161
162=cut
163
164sub GetParamNames {
165    my $Self = shift;
166
167    # fetch all names
168    my @ParamNames = $Self->{Query}->param();
169
170    # Fallback to query string for mixed requests.
171    my $RequestMethod = $Self->{Query}->request_method() // '';
172    if ( $RequestMethod eq 'POST' ) {
173        my %POSTNames;
174        @POSTNames{@ParamNames} = @ParamNames;
175        my @GetNames = $Self->{Query}->url_param();
176        GETNAME:
177        for my $GetName (@GetNames) {
178            next GETNAME if !defined $GetName;
179            push @ParamNames, $GetName if !exists $POSTNames{$GetName};
180        }
181    }
182
183    for my $Name (@ParamNames) {
184        $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Name );
185    }
186
187    return @ParamNames;
188}
189
190=head2 GetArray()
191
192to get array request parameters.
193By default, trimming is performed on the data.
194
195    my @Param = $ParamObject->GetArray(
196        Param => 'ID',
197        Raw   => 1,     # optional, input data is not changed
198    );
199
200=cut
201
202sub GetArray {
203    my ( $Self, %Param ) = @_;
204
205    my @Values = $Self->{Query}->multi_param( $Param{Param} );
206
207    # Fallback to query string for mixed requests.
208    my $RequestMethod = $Self->{Query}->request_method() // '';
209    if ( $RequestMethod eq 'POST' && !@Values ) {
210        @Values = $Self->{Query}->url_param( $Param{Param} );
211    }
212
213    $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \@Values );
214
215    my $Raw = defined $Param{Raw} ? $Param{Raw} : 0;
216
217    if ( !$Raw ) {
218
219        # get check item object
220        my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
221
222        VALUE:
223        for my $Value (@Values) {
224
225            # don't validate CGI::File::Temp objects from file uploads
226            next VALUE if !$Value || ref \$Value ne 'SCALAR';
227
228            $CheckItemObject->StringClean(
229                StringRef => \$Value,
230                TrimLeft  => 1,
231                TrimRight => 1,
232            );
233        }
234    }
235
236    return @Values;
237}
238
239=head2 GetUploadAll()
240
241gets file upload data.
242
243    my %File = $ParamObject->GetUploadAll(
244        Param  => 'FileParam',  # the name of the request parameter containing the file data
245    );
246
247    returns (
248        Filename    => 'abc.txt',
249        ContentType => 'text/plain',
250        Content     => 'Some text',
251    );
252
253=cut
254
255sub GetUploadAll {
256    my ( $Self, %Param ) = @_;
257
258    # get upload
259    my $Upload = $Self->{Query}->upload( $Param{Param} );
260    return if !$Upload;
261
262    # get real file name
263    my $UploadFilenameOrig = $Self->GetParam( Param => $Param{Param} ) || 'unknown';
264
265    my $NewFileName = "$UploadFilenameOrig";    # use "" to get filename of anony. object
266    $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$NewFileName );
267
268    # replace all devices like c: or d: and dirs for IE!
269    $NewFileName =~ s/.:\\(.*)/$1/g;
270    $NewFileName =~ s/.*\\(.+?)/$1/g;
271
272    # return a string
273    my $Content = '';
274    while (<$Upload>) {
275        $Content .= $_;
276    }
277    close $Upload;
278
279    my $ContentType = $Self->_GetUploadInfo(
280        Filename => $UploadFilenameOrig,
281        Header   => 'Content-Type',
282    );
283
284    return (
285        Filename    => $NewFileName,
286        Content     => $Content,
287        ContentType => $ContentType,
288    );
289}
290
291sub _GetUploadInfo {
292    my ( $Self, %Param ) = @_;
293
294    # get file upload info
295    my $FileInfo = $Self->{Query}->uploadInfo( $Param{Filename} );
296
297    # return if no upload info exists
298    return 'application/octet-stream' if !$FileInfo;
299
300    # return if no content type of upload info exists
301    return 'application/octet-stream' if !$FileInfo->{ $Param{Header} };
302
303    # return content type of upload info
304    return $FileInfo->{ $Param{Header} };
305}
306
307=head2 SetCookie()
308
309set a cookie
310
311    $ParamObject->SetCookie(
312        Key     => ID,
313        Value   => 123456,
314        Expires => '+3660s',
315        Path    => 'otrs/',     # optional, only allow cookie for given path
316        Secure  => 1,           # optional, set secure attribute to disable cookie on HTTP (HTTPS only)
317        HTTPOnly => 1,          # optional, sets HttpOnly attribute of cookie to prevent access via JavaScript
318    );
319
320=cut
321
322sub SetCookie {
323    my ( $Self, %Param ) = @_;
324
325    $Param{Path} ||= '';
326
327    return $Self->{Query}->cookie(
328        -name     => $Param{Key},
329        -value    => $Param{Value},
330        -expires  => $Param{Expires},
331        -secure   => $Param{Secure} || '',
332        -httponly => $Param{HTTPOnly} || '',
333        -path     => '/' . $Param{Path},
334    );
335}
336
337=head2 GetCookie()
338
339get a cookie
340
341    my $String = $ParamObject->GetCookie(
342        Key => ID,
343    );
344
345=cut
346
347sub GetCookie {
348    my ( $Self, %Param ) = @_;
349
350    return $Self->{Query}->cookie( $Param{Key} );
351}
352
353=head2 IsAJAXRequest()
354
355checks if the current request was sent by AJAX
356
357    my $IsAJAXRequest = $ParamObject->IsAJAXRequest();
358
359=cut
360
361sub IsAJAXRequest {
362    my ( $Self, %Param ) = @_;
363
364    return ( $Self->{Query}->http('X-Requested-With') // '' ) eq 'XMLHttpRequest' ? 1 : 0;
365}
366
367=head2 LoadFormDraft()
368
369Load specified draft.
370This will read stored draft data and inject it into the param object
371for transparent use by frontend module.
372
373    my $FormDraftID = $ParamObject->LoadFormDraft(
374        FormDraftID => 123,
375        UserID  => 1,
376    );
377
378=cut
379
380sub LoadFormDraft {
381    my ( $Self, %Param ) = @_;
382
383    return if !$Param{FormDraftID} || !$Param{UserID};
384
385    # get draft data
386    my $FormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet(
387        FormDraftID => $Param{FormDraftID},
388        UserID      => $Param{UserID},
389    );
390    return if !IsHashRefWithData($FormDraft);
391
392    # Verify action.
393    my $Action = $Self->GetParam( Param => 'Action' );
394    return if $FormDraft->{Action} ne $Action;
395
396    # add draft name to form data
397    $FormDraft->{FormData}->{FormDraftTitle} = $FormDraft->{Title};
398
399    # create FormID and add to form data
400    my $FormID = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate();
401    $FormDraft->{FormData}->{FormID} = $FormID;
402
403    # set form data to param object, depending on type
404    KEY:
405    for my $Key ( sort keys %{ $FormDraft->{FormData} } ) {
406        my $Value = $FormDraft->{FormData}->{$Key} // '';
407
408        # array value
409        if ( IsArrayRefWithData($Value) ) {
410            $Self->{Query}->param(
411                -name   => $Key,
412                -values => $Value,
413            );
414            next KEY;
415        }
416
417        # scalar value
418        $Self->{Query}->param(
419            -name  => $Key,
420            -value => $Value,
421        );
422    }
423
424    # add UploadCache data
425    my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache');
426    for my $File ( @{ $FormDraft->{FileData} } ) {
427        return if !$UploadCacheObject->FormIDAddFile(
428            %{$File},
429            FormID => $FormID,
430        );
431    }
432
433    return $Param{FormDraftID};
434}
435
436=head2 SaveFormDraft()
437
438Create or replace draft using data from param object and upload cache.
439Specified params can be overwritten if necessary.
440
441    my $FormDraftID = $ParamObject->SaveFormDraft(
442        UserID         => 1
443        ObjectType     => 'Ticket',
444        ObjectID       => 123,
445        OverrideParams => {               # optional, can contain strings and array references
446            Subaction   => undef,
447            UserID      => 1,
448            CustomParam => [ 1, 2, 3, ],
449            ...
450        },
451    );
452
453=cut
454
455sub SaveFormDraft {
456    my ( $Self, %Param ) = @_;
457
458    # check params
459    return if !$Param{UserID} || !$Param{ObjectType} || !IsInteger( $Param{ObjectID} );
460
461    # gather necessary data for backend
462    my %MetaParams;
463    for my $Param (qw(Action FormDraftID FormDraftTitle FormID)) {
464        $MetaParams{$Param} = $Self->GetParam(
465            Param => $Param,
466        );
467    }
468    return if !$MetaParams{Action};
469
470    # determine session name param (SessionUseCookie = 0) for exclusion
471    my $SessionName = $Kernel::OM->Get('Kernel::Config')->Get('SessionName') || 'SessionID';
472
473    # compile override list
474    my %OverrideParams = IsHashRefWithData( $Param{OverrideParams} ) ? %{ $Param{OverrideParams} } : ();
475
476    # these params must always be excluded for safety, they take precedence
477    for my $Name (
478        qw(Action ChallengeToken FormID FormDraftID FormDraftTitle FormDraftAction LoadFormDraftID),
479        $SessionName
480        )
481    {
482        $OverrideParams{$Name} = undef;
483    }
484
485    # Gather all params.
486    #   Exclude, add or override by using OverrideParams if necessary.
487    my @ParamNames = $Self->GetParamNames();
488    my %ParamSeen;
489    my %FormData;
490    PARAM:
491    for my $Param ( @ParamNames, sort keys %OverrideParams ) {
492        next PARAM if $ParamSeen{$Param}++;
493        my $Value;
494
495        # check for overrides first
496        if ( exists $OverrideParams{$Param} ) {
497
498            # allow only strings and array references as value
499            if (
500                IsStringWithData( $OverrideParams{$Param} )
501                || IsArrayRefWithData( $OverrideParams{$Param} )
502                )
503            {
504                $Value = $OverrideParams{$Param};
505            }
506
507            # skip all other parameters (including those specified to be excluded by using 'undef')
508            else {
509                next PARAM;
510            }
511        }
512
513        # get other values from param object
514        if ( !defined $Value ) {
515            my @Values = $Self->GetArray( Param => $Param );
516            next PARAM if !IsArrayRefWithData( \@Values );
517
518            # store single occurances as string
519            if ( scalar @Values == 1 ) {
520                $Value = $Values[0];
521            }
522
523            # store multiple occurances as array reference
524            else {
525                $Value = \@Values;
526            }
527        }
528
529        $FormData{$Param} = $Value;
530    }
531
532    # get files from upload cache
533    my @FileData = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDGetAllFilesData(
534        FormID => $MetaParams{FormID},
535    );
536
537    # prepare data to add or update draft
538    my %FormDraft = (
539        FormData    => \%FormData,
540        FileData    => \@FileData,
541        FormDraftID => $MetaParams{FormDraftID},
542        ObjectType  => $Param{ObjectType},
543        ObjectID    => $Param{ObjectID},
544        Action      => $MetaParams{Action},
545        Title       => $MetaParams{FormDraftTitle},
546        UserID      => $Param{UserID},
547    );
548
549    # update draft
550    if ( $MetaParams{FormDraftID} ) {
551        return if !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftUpdate(%FormDraft);
552        return 1;
553    }
554
555    # create new draft
556    return if !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftAdd(%FormDraft);
557    return 1;
558}
559
5601;
561
562=head1 TERMS AND CONDITIONS
563
564This software is part of the OTRS project (L<https://otrs.org/>).
565
566This software comes with ABSOLUTELY NO WARRANTY. For details, see
567the enclosed file COPYING for license information (GPL). If you
568did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
569
570=cut
571