One Hat Cyber Team
  • Dir : ~/usr/share/perl5/
  • View File Name : CGI.pm
    tag # sub textarea { my($self,@p) = self_or_default(@_); my($name,$default,$rows,$cols,$override,$tabindex,@other) = rearrange([NAME,[DEFAULT,VALUE],ROWS,[COLS,COLUMNS],[OVERRIDE,FORCE],TABINDEX],@p); my($current)= $override ? $default : (defined($self->param($name)) ? $self->param($name) : $default); $name = defined($name) ? $self->_maybe_escapeHTML($name) : ''; $current = defined($current) ? $self->_maybe_escapeHTML($current) : ''; my($r) = $rows ? qq/ rows="$rows"/ : ''; my($c) = $cols ? qq/ cols="$cols"/ : ''; my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return qq{}; } #### Method: button # Create a javascript button. # Parameters: # $name -> (optional) Name for the button. (-name) # $value -> (optional) Value of the button when selected (and visible name) (-value) # $onclick -> (optional) Text of the JavaScript to run when the button is # clicked. # Returns: # A string containing a tag #### sub button { my($self,@p) = self_or_default(@_); my($label,$value,$script,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL], [ONCLICK,SCRIPT],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label); $value=$self->_maybe_escapeHTML($value,1); $script=$self->_maybe_escapeHTML($script); $script ||= ''; my($name) = ''; $name = qq/ name="$label"/ if $label; $value = $value || $label; my($val) = ''; $val = qq/ value="$value"/ if $value; $script = qq/ onclick="$script"/ if $script; my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return $XHTML ? qq() : qq(); } #### Method: submit # Create a "submit query" button. # Parameters: # $name -> (optional) Name for the button. # $value -> (optional) Value of the button when selected (also doubles as label). # $label -> (optional) Label printed on the button(also doubles as the value). # Returns: # A string containing a tag #### sub submit { my($self,@p) = self_or_default(@_); my($label,$value,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label); $value=$self->_maybe_escapeHTML($value,1); my $name = $NOSTICKY ? '' : 'name=".submit" '; $name = qq/name="$label" / if defined($label); $value = defined($value) ? $value : $label; my $val = ''; $val = qq/value="$value" / if defined($value); $tabindex = $self->element_tab($tabindex); my($other) = @other ? "@other " : ''; return $XHTML ? qq() : qq(); } #### Method: reset # Create a "reset" button. # Parameters: # $name -> (optional) Name for the button. # Returns: # A string containing a tag #### sub reset { my($self,@p) = self_or_default(@_); my($label,$value,$tabindex,@other) = rearrange(['NAME',['VALUE','LABEL'],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label); $value=$self->_maybe_escapeHTML($value,1); my ($name) = ' name=".reset"'; $name = qq/ name="$label"/ if defined($label); $value = defined($value) ? $value : $label; my($val) = ''; $val = qq/ value="$value"/ if defined($value); my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return $XHTML ? qq() : qq(); } #### Method: defaults # Create a "defaults" button. # Parameters: # $name -> (optional) Name for the button. # Returns: # A string containing a tag # # Note: this button has a special meaning to the initialization script, # and tells it to ERASE the current query string so that your defaults # are used again! #### sub defaults { my($self,@p) = self_or_default(@_); my($label,$tabindex,@other) = rearrange([[NAME,VALUE],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label,1); $label = $label || "Defaults"; my($value) = qq/ value="$label"/; my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return $XHTML ? qq() : qq//; } #### Method: comment # Create an HTML # Parameters: a string sub comment { my($self,@p) = self_or_CGI(@_); return ""; } #### Method: checkbox # Create a checkbox that is not logically linked to any others. # The field value is "on" when the button is checked. # Parameters: # $name -> Name of the checkbox # $checked -> (optional) turned on by default if true # $value -> (optional) value of the checkbox, 'on' by default # $label -> (optional) a user-readable label printed next to the box. # Otherwise the checkbox name is used. # Returns: # A string containing a field #### sub checkbox { my($self,@p) = self_or_default(@_); my($name,$checked,$value,$label,$labelattributes,$override,$tabindex,@other) = rearrange([NAME,[CHECKED,SELECTED,ON],VALUE,LABEL,LABELATTRIBUTES, [OVERRIDE,FORCE],TABINDEX],@p); $value = defined $value ? $value : 'on'; if (!$override && ($self->{'.fieldnames'}->{$name} || defined $self->param($name))) { $checked = grep($_ eq $value,$self->param($name)) ? $self->_checked(1) : ''; } else { $checked = $self->_checked($checked); } my($the_label) = defined $label ? $label : $name; $name = $self->_maybe_escapeHTML($name); $value = $self->_maybe_escapeHTML($value,1); $the_label = $self->_maybe_escapeHTML($the_label); my($other) = @other ? "@other " : ''; $tabindex = $self->element_tab($tabindex); $self->register_parameter($name); return $XHTML ? CGI::label($labelattributes, qq{$the_label}) : qq{$the_label}; } # Escape HTML sub escapeHTML { require HTML::Entities; # hack to work around earlier hacks push @_,$_[0] if @_==1 && $_[0] eq 'CGI'; my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_); return undef unless defined($toencode); my $encode_entities = $ENCODE_ENTITIES; $encode_entities .= "\012\015" if ( $encode_entities && $newlinestoo ); return HTML::Entities::encode_entities($toencode,$encode_entities); } # unescape HTML -- used internally sub unescapeHTML { require HTML::Entities; # hack to work around earlier hacks push @_,$_[0] if @_==1 && $_[0] eq 'CGI'; my ($self,$string) = CGI::self_or_default(@_); return undef unless defined($string); return HTML::Entities::decode_entities($string); } # Internal procedure - don't use sub _tableize { my($rows,$columns,$rowheaders,$colheaders,@elements) = @_; my @rowheaders = $rowheaders ? @$rowheaders : (); my @colheaders = $colheaders ? @$colheaders : (); my($result); if (defined($columns)) { $rows = int(0.99 + @elements/$columns) unless defined($rows); } if (defined($rows)) { $columns = int(0.99 + @elements/$rows) unless defined($columns); } # rearrange into a pretty table $result = ""; my($row,$column); unshift(@colheaders,'') if @colheaders && @rowheaders; $result .= "" if @colheaders; for (@colheaders) { $result .= ""; } for ($row=0;$row<$rows;$row++) { $result .= ""; $result .= "" if @rowheaders; for ($column=0;$column<$columns;$column++) { $result .= "" if defined($elements[$column*$rows + $row]); } $result .= ""; } $result .= "
    $_
    $rowheaders[$row]" . $elements[$column*$rows + $row] . "
    "; return $result; } #### Method: radio_group # Create a list of logically-linked radio buttons. # Parameters: # $name -> Common name for all the buttons. # $values -> A pointer to a regular array containing the # values for each button in the group. # $default -> (optional) Value of the button to turn on by default. Pass '-' # to turn _nothing_ on. # $linebreak -> (optional) Set to true to place linebreaks # between the buttons. # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # An ARRAY containing a series of fields #### sub radio_group { my($self,@p) = self_or_default(@_); $self->_box_group('radio',@p); } #### Method: checkbox_group # Create a list of logically-linked checkboxes. # Parameters: # $name -> Common name for all the check boxes # $values -> A pointer to a regular array containing the # values for each checkbox in the group. # $defaults -> (optional) # 1. If a pointer to a regular array of checkbox values, # then this will be used to decide which # checkboxes to turn on by default. # 2. If a scalar, will be assumed to hold the # value of a single checkbox in the group to turn on. # $linebreak -> (optional) Set to true to place linebreaks # between the buttons. # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # An ARRAY containing a series of fields #### sub checkbox_group { my($self,@p) = self_or_default(@_); $self->_box_group('checkbox',@p); } sub _box_group { my $self = shift; my $box_type = shift; my($name,$values,$defaults,$linebreak,$labels,$labelattributes, $attributes,$rows,$columns,$rowheaders,$colheaders, $override,$nolabels,$tabindex,$disabled,@other) = rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LINEBREAK,LABELS,LABELATTRIBUTES, ATTRIBUTES,ROWS,[COLUMNS,COLS],[ROWHEADERS,ROWHEADER],[COLHEADERS,COLHEADER], [OVERRIDE,FORCE],NOLABELS,TABINDEX,DISABLED ],@_); my($result,$checked,@elements,@values); @values = $self->_set_values_and_labels($values,\$labels,$name); my %checked = $self->previous_or_default($name,$defaults,$override); # If no check array is specified, check the first by default $checked{$values[0]}++ if $box_type eq 'radio' && !%checked; $name=$self->_maybe_escapeHTML($name); my %tabs = (); if ($TABINDEX && $tabindex) { if (!ref $tabindex) { $self->element_tab($tabindex); } elsif (ref $tabindex eq 'ARRAY') { %tabs = map {$_=>$self->element_tab} @$tabindex; } elsif (ref $tabindex eq 'HASH') { %tabs = %$tabindex; } } %tabs = map {$_=>$self->element_tab} @values unless %tabs; my $other = @other ? "@other " : ''; my $radio_checked; # for disabling groups of radio/checkbox buttons my %disabled; for (@{$disabled}) { $disabled{$_}=1; } for (@values) { my $disable=""; if ($disabled{$_}) { $disable="disabled='1'"; } my $checkit = $self->_checked($box_type eq 'radio' ? ($checked{$_} && !$radio_checked++) : $checked{$_}); my($break); if ($linebreak) { $break = $XHTML ? "
    " : "
    "; } else { $break = ''; } my($label)=''; unless (defined($nolabels) && $nolabels) { $label = $_; $label = $labels->{$_} if defined($labels) && defined($labels->{$_}); $label = $self->_maybe_escapeHTML($label,1); $label = "$label" if $disabled{$_}; } my $attribs = $self->_set_attributes($_, $attributes); my $tab = $tabs{$_}; $_=$self->_maybe_escapeHTML($_); if ($XHTML) { push @elements, CGI::label($labelattributes, qq($label)).${break}; } else { push(@elements,qq/${label}${break}/); } } $self->register_parameter($name); return wantarray ? @elements : "@elements" unless defined($columns) || defined($rows); return _tableize($rows,$columns,$rowheaders,$colheaders,@elements); } #### Method: popup_menu # Create a popup menu. # Parameters: # $name -> Name for all the menu # $values -> A pointer to a regular array containing the # text of each menu item. # $default -> (optional) Default item to display # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # A string containing the definition of a popup menu. #### sub popup_menu { my($self,@p) = self_or_default(@_); my($name,$values,$default,$labels,$attributes,$override,$tabindex,@other) = rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LABELS, ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p); my($result,%selected); if (!$override && defined($self->param($name))) { $selected{$self->param($name)}++; } elsif (defined $default) { %selected = map {$_=>1} ref($default) eq 'ARRAY' ? @$default : $default; } $name=$self->_maybe_escapeHTML($name); # RT #30057 - ignore -multiple, if you need this # then use scrolling_list @other = grep { $_ !~ /^multiple=/i } @other; my($other) = @other ? " @other" : ''; my(@values); @values = $self->_set_values_and_labels($values,\$labels,$name); $tabindex = $self->element_tab($tabindex); $name = q{} if ! defined $name; $result = qq/"; return $result; } #### Method: optgroup # Create a optgroup. # Parameters: # $name -> Label for the group # $values -> A pointer to a regular array containing the # values for each option line in the group. # $labels -> (optional) # A pointer to a hash of labels to print next to each item # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # $labeled -> (optional) # A true value indicates the value should be used as the label attribute # in the option elements. # The label attribute specifies the option label presented to the user. # This defaults to the content of the \n/; for (@values) { if (/_set_attributes($_, $attributes); my($label) = $_; $label = $labels->{$_} if defined($labels) && defined($labels->{$_}); $label=$self->_maybe_escapeHTML($label); my($value)=$self->_maybe_escapeHTML($_,1); $result .= $labeled ? $novals ? "$label\n" : "$label\n" : $novals ? "$label\n" : "$label\n"; } } $result .= ""; return $result; } #### Method: scrolling_list # Create a scrolling list. # Parameters: # $name -> name for the list # $values -> A pointer to a regular array containing the # values for each option line in the list. # $defaults -> (optional) # 1. If a pointer to a regular array of options, # then this will be used to decide which # lines to turn on by default. # 2. Otherwise holds the value of the single line to turn on. # $size -> (optional) Size of the list. # $multiple -> (optional) If set, allow multiple selections. # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # A string containing the definition of a scrolling list. #### sub scrolling_list { my($self,@p) = self_or_default(@_); my($name,$values,$defaults,$size,$multiple,$labels,$attributes,$override,$tabindex,@other) = rearrange([NAME,[VALUES,VALUE],[DEFAULTS,DEFAULT], SIZE,MULTIPLE,LABELS,ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p); my($result,@values); @values = $self->_set_values_and_labels($values,\$labels,$name); $size = $size || scalar(@values); my(%selected) = $self->previous_or_default($name,$defaults,$override); my($is_multiple) = $multiple ? qq/ multiple="multiple"/ : ''; my($has_size) = $size ? qq/ size="$size"/: ''; my($other) = @other ? " @other" : ''; $name=$self->_maybe_escapeHTML($name); $tabindex = $self->element_tab($tabindex); $result = qq/"; $self->register_parameter($name); return $result; } #### Method: hidden # Parameters: # $name -> Name of the hidden field # @default -> (optional) Initial values of field (may be an array) # or # $default->[initial values of field] # Returns: # A string containing a #### sub hidden { my($self,@p) = self_or_default(@_); # this is the one place where we departed from our standard # calling scheme, so we have to special-case (darn) my(@result,@value); my($name,$default,$override,@other) = rearrange([NAME,[DEFAULT,VALUE,VALUES],[OVERRIDE,FORCE]],@p); my $do_override = 0; if ( ref($p[0]) || substr($p[0],0,1) eq '-') { @value = ref($default) ? @{$default} : $default; $do_override = $override; } else { for ($default,$override,@other) { push(@value,$_) if defined($_); } undef @other; } # use previous values if override is not set my @prev = $self->param($name); @value = @prev if !$do_override && @prev; $name=$self->_maybe_escapeHTML($name); for (@value) { $_ = defined($_) ? $self->_maybe_escapeHTML($_,1) : ''; push @result,$XHTML ? qq() : qq(); } return wantarray ? @result : join('',@result); } #### Method: image_button # Parameters: # $name -> Name of the button # $src -> URL of the image source # $align -> Alignment style (TOP, BOTTOM or MIDDLE) # Returns: # A string containing a #### sub image_button { my($self,@p) = self_or_default(@_); my($name,$src,$alignment,@other) = rearrange([NAME,SRC,ALIGN],@p); my($align) = $alignment ? " align=\L\"$alignment\"" : ''; my($other) = @other ? " @other" : ''; $name=$self->_maybe_escapeHTML($name); return $XHTML ? qq() : qq//; } #### Method: self_url # Returns a URL containing the current script and all its # param/value pairs arranged as a query. You can use this # to create a link that, when selected, will reinvoke the # script with all its state information preserved. #### sub self_url { my($self,@p) = self_or_default(@_); return $self->url('-path_info'=>1,'-query'=>1,'-full'=>1,@p); } # This is provided as a synonym to self_url() for people unfortunate # enough to have incorporated it into their programs already! sub state { &self_url; } #### Method: url # Like self_url, but doesn't return the query string part of # the URL. #### sub url { my($self,@p) = self_or_default(@_); my ($relative,$absolute,$full,$path_info,$query,$base,$rewrite) = rearrange(['RELATIVE','ABSOLUTE','FULL',['PATH','PATH_INFO'],['QUERY','QUERY_STRING'],'BASE','REWRITE'],@p); my $url = ''; $full++ if $base || !($relative || $absolute); $rewrite++ unless defined $rewrite; my $path = $self->path_info; my $script_name = $self->script_name; my $request_uri = $self->request_uri || ''; my $query_str = $query ? $self->query_string : ''; $script_name =~ s/\?.*$//s; # remove query string $request_uri =~ s/\?.*$//s; # remove query string $request_uri = unescape($request_uri); my $uri = $rewrite && $request_uri ? $request_uri : $script_name; if ( defined( $ENV{PATH_INFO} ) ) { # IIS sometimes sets PATH_INFO to the same value as SCRIPT_NAME so only sub it out # if SCRIPT_NAME isn't defined or isn't the same value as PATH_INFO $uri =~ s/\Q$ENV{PATH_INFO}\E$// if ( ! defined( $ENV{SCRIPT_NAME} ) or $ENV{PATH_INFO} ne $ENV{SCRIPT_NAME} ); # if we're not IIS then keep to spec, the relevant info is here: # https://tools.ietf.org/html/rfc3875#section-4.1.13, namely # "No PATH_INFO segment (see section 4.1.5) is included in the # SCRIPT_NAME value." (see GH #126, GH #152, GH #176) if ( ! $IIS ) { $uri =~ s/\Q$ENV{PATH_INFO}\E$//; } } if ($full) { my $protocol = $self->protocol(); $url = "$protocol://"; my $vh = http('x_forwarded_host') || http('host') || ''; $vh =~ s/^.*,\s*//; # x_forwarded_host may be a comma-separated list (e.g. when the request has # passed through multiple reverse proxies. Take the last one. $vh =~ s/\:\d+$//; # some clients add the port number (incorrectly). Get rid of it. $url .= $vh || server_name(); my $port = $self->virtual_port; # add the port to the url unless it's the protocol's default port $url .= ':' . $port unless (lc($protocol) eq 'http' && $port == 80) or (lc($protocol) eq 'https' && $port == 443); return $url if $base; $url .= $uri; } elsif ($relative) { ($url) = $uri =~ m!([^/]+)$!; } elsif ($absolute) { $url = $uri; } $url .= $path if $path_info and defined $path; $url .= "?$query_str" if $query and $query_str ne ''; $url ||= ''; $url = URI->new( $url )->canonical->as_string; return $url } #### Method: cookie # Set or read a cookie from the specified name. # Cookie can then be passed to header(). # Usual rules apply to the stickiness of -value. # Parameters: # -name -> name for this cookie (optional) # -value -> value of this cookie (scalar, array or hash) # -path -> paths for which this cookie is valid (optional) # -domain -> internet domain in which this cookie is valid (optional) # -secure -> if true, cookie only passed through secure channel (optional) # -expires -> expiry date in format Wdy, DD Mon YYYY HH:MM:SS GMT (optional) #### sub cookie { my($self,@p) = self_or_default(@_); my($name,$value,$path,$domain,$secure,$expires,$httponly,$max_age,$samesite,$priority) = rearrange([NAME,[VALUE,VALUES],PATH,DOMAIN,SECURE,EXPIRES,HTTPONLY,'MAX-AGE',SAMESITE,PRIORITY],@p); require CGI::Cookie; # if no value is supplied, then we retrieve the # value of the cookie, if any. For efficiency, we cache the parsed # cookies in our state variables. unless ( defined($value) ) { $self->{'.cookies'} = CGI::Cookie->fetch unless $COOKIE_CACHE && exists $self->{'.cookies'}; # If no name is supplied, then retrieve the names of all our cookies. return () unless $self->{'.cookies'}; return keys %{$self->{'.cookies'}} unless $name; return () unless $self->{'.cookies'}->{$name}; return $self->{'.cookies'}->{$name}->value if defined($name) && $name ne ''; } # If we get here, we're creating a new cookie return undef unless defined($name) && $name ne ''; # this is an error my @param; push(@param,'-name'=>$name); push(@param,'-value'=>$value); push(@param,'-domain'=>$domain) if $domain; push(@param,'-path'=>$path) if $path; push(@param,'-expires'=>$expires) if $expires; push(@param,'-secure'=>$secure) if $secure; push(@param,'-httponly'=>$httponly) if $httponly; push(@param,'-max-age'=>$max_age) if $max_age; push(@param,'-samesite'=>$samesite) if $samesite; push(@param,'-priority'=>$priority) if $priority; return CGI::Cookie->new(@param); } sub parse_keywordlist { my($self,$tosplit) = @_; $tosplit = unescape($tosplit); # unescape the keywords $tosplit=~tr/+/ /; # pluses to spaces my(@keywords) = split(/\s+/,$tosplit); return @keywords; } sub param_fetch { my($self,@p) = self_or_default(@_); my($name) = rearrange([NAME],@p); return [] unless defined $name; unless (exists($self->{param}{$name})) { $self->add_parameter($name); $self->{param}{$name} = []; } return $self->{param}{$name}; } ############################################### # OTHER INFORMATION PROVIDED BY THE ENVIRONMENT ############################################### #### Method: path_info # Return the extra virtual path information provided # after the URL (if any) #### sub path_info { my ($self,$info) = self_or_default(@_); if (defined($info)) { $info = "/$info" if $info ne '' && substr($info,0,1) ne '/'; $self->{'.path_info'} = $info; } elsif (! defined($self->{'.path_info'}) ) { my (undef,$path_info) = $self->_name_and_path_from_env; $self->{'.path_info'} = $path_info || ''; } return $self->{'.path_info'}; } # This function returns a potentially modified version of SCRIPT_NAME # and PATH_INFO. Some HTTP servers do sanitise the paths in those # variables. It is the case of at least Apache 2. If for instance the # user requests: /path/./to/script.cgi/x//y/z/../x?y, Apache will set: # REQUEST_URI=/path/./to/script.cgi/x//y/z/../x?y # SCRIPT_NAME=/path/to/env.cgi # PATH_INFO=/x/y/x # # This is all fine except that some bogus CGI scripts expect # PATH_INFO=/http://foo when the user requests # http://xxx/script.cgi/http://foo # # Old versions of this module used to accomodate with those scripts, so # this is why we do this here to keep those scripts backward compatible. # Basically, we accomodate with those scripts but within limits, that is # we only try to preserve the number of / that were provided by the user # if $REQUEST_URI and "$SCRIPT_NAME$PATH_INFO" only differ by the number # of consecutive /. # # So for instance, in: http://foo/x//y/script.cgi/a//b, we'll return a # script_name of /x//y/script.cgi and a path_info of /a//b, but in: # http://foo/./x//z/script.cgi/a/../b//c, we'll return the versions # possibly sanitised by the HTTP server, so in the case of Apache 2: # script_name == /foo/x/z/script.cgi and path_info == /b/c. # # Future versions of this module may no longer do that, so one should # avoid relying on the browser, proxy, server, and CGI.pm preserving the # number of consecutive slashes as no guarantee can be made there. sub _name_and_path_from_env { my $self = shift; my $script_name = $ENV{SCRIPT_NAME} || ''; my $path_info = $ENV{PATH_INFO} || ''; my $uri = $self->request_uri || ''; $uri =~ s/\?.*//s; $uri = unescape($uri); if ( $IIS ) { # IIS doesn't set $ENV{PATH_INFO} correctly. It sets it to # $ENV{SCRIPT_NAME}path_info # IIS also doesn't set $ENV{REQUEST_URI} so we don't want to do # the test below, hence this comes first $path_info =~ s/^\Q$script_name\E(.*)/$1/; } elsif ($uri ne "$script_name$path_info") { my $script_name_pattern = quotemeta($script_name); my $path_info_pattern = quotemeta($path_info); $script_name_pattern =~ s{(?:\\/)+}{/+}g; $path_info_pattern =~ s{(?:\\/)+}{/+}g; if ($uri =~ /^($script_name_pattern)($path_info_pattern)$/s) { # REQUEST_URI and SCRIPT_NAME . PATH_INFO only differ by the # numer of consecutive slashes, so we can extract the info from # REQUEST_URI: ($script_name, $path_info) = ($1, $2); } } return ($script_name,$path_info); } #### Method: request_method # Returns 'POST', 'GET', 'PUT', 'PATCH' or 'HEAD' #### sub request_method { return (defined $ENV{'REQUEST_METHOD'}) ? $ENV{'REQUEST_METHOD'} : undef; } #### Method: content_type # Returns the content_type string #### sub content_type { return (defined $ENV{'CONTENT_TYPE'}) ? $ENV{'CONTENT_TYPE'} : undef; } #### Method: path_translated # Return the physical path information provided # by the URL (if any) #### sub path_translated { return (defined $ENV{'PATH_TRANSLATED'}) ? $ENV{'PATH_TRANSLATED'} : undef; } #### Method: request_uri # Return the literal request URI #### sub request_uri { return (defined $ENV{'REQUEST_URI'}) ? $ENV{'REQUEST_URI'} : undef; } #### Method: query_string # Synthesize a query string from our current # parameters #### sub query_string { my($self) = self_or_default(@_); my($param,$value,@pairs); for $param ($self->param) { my($eparam) = escape($param); for $value ($self->param($param)) { $value = escape($value); next unless defined $value; push(@pairs,"$eparam=$value"); } } for (sort keys %{$self->{'.fieldnames'}}) { push(@pairs,".cgifields=".escape("$_")); } return join($USE_PARAM_SEMICOLONS ? ';' : '&',@pairs); } sub env_query_string { return (defined $ENV{'QUERY_STRING'}) ? $ENV{'QUERY_STRING'} : undef; } #### Method: accept # Without parameters, returns an array of the # MIME types the browser accepts. # With a single parameter equal to a MIME # type, will return undef if the browser won't # accept it, 1 if the browser accepts it but # doesn't give a preference, or a floating point # value between 0.0 and 1.0 if the browser # declares a quantitative score for it. # This handles MIME type globs correctly. #### sub Accept { my($self,$search) = self_or_CGI(@_); my(%prefs,$type,$pref,$pat); my(@accept) = defined $self->http('accept') ? split(',',$self->http('accept')) : (); for (@accept) { ($pref) = /q=(\d\.\d+|\d+)/; ($type) = m#(\S+/[^;]+)#; next unless $type; $prefs{$type}=$pref || 1; } return keys %prefs unless $search; # if a search type is provided, we may need to # perform a pattern matching operation. # The MIME types use a glob mechanism, which # is easily translated into a perl pattern match # First return the preference for directly supported # types: return $prefs{$search} if $prefs{$search}; # Didn't get it, so try pattern matching. for (sort keys %prefs) { next unless /\*/; # not a pattern match ($pat = $_) =~ s/([^\w*])/\\$1/g; # escape meta characters $pat =~ s/\*/.*/g; # turn it into a pattern return $prefs{$_} if $search=~/$pat/; } } #### Method: user_agent # If called with no parameters, returns the user agent. # If called with one parameter, does a pattern match (case # insensitive) on the user agent. #### sub user_agent { my($self,$match)=self_or_CGI(@_); my $user_agent = $self->http('user_agent'); return $user_agent unless defined $match && $match && $user_agent; return $user_agent =~ /$match/i; } #### Method: raw_cookie # Returns the magic cookies for the session. # The cookies are not parsed or altered in any way, i.e. # cookies are returned exactly as given in the HTTP # headers. If a cookie name is given, only that cookie's # value is returned, otherwise the entire raw cookie # is returned. #### sub raw_cookie { my($self,$key) = self_or_CGI(@_); require CGI::Cookie; if (defined($key)) { $self->{'.raw_cookies'} = CGI::Cookie->raw_fetch unless $self->{'.raw_cookies'}; return () unless $self->{'.raw_cookies'}; return () unless $self->{'.raw_cookies'}->{$key}; return $self->{'.raw_cookies'}->{$key}; } return $self->http('cookie') || $ENV{'COOKIE'} || ''; } #### Method: virtual_host # Return the name of the virtual_host, which # is not always the same as the server ###### sub virtual_host { my $vh = http('x_forwarded_host') || http('host') || server_name(); $vh =~ s/:\d+$//; # get rid of port number return $vh; } #### Method: remote_host # Return the name of the remote host, or its IP # address if unavailable. If this variable isn't # defined, it returns "localhost" for debugging # purposes. #### sub remote_host { return $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'} || 'localhost'; } #### Method: remote_addr # Return the IP addr of the remote host. #### sub remote_addr { return $ENV{'REMOTE_ADDR'} || '127.0.0.1'; } #### Method: script_name # Return the partial URL to this script for # self-referencing scripts. Also see # self_url(), which returns a URL with all state information # preserved. #### sub script_name { my ($self,@p) = self_or_default(@_); if (@p) { $self->{'.script_name'} = shift @p; } elsif (!exists $self->{'.script_name'}) { my ($script_name,$path_info) = $self->_name_and_path_from_env(); $self->{'.script_name'} = $script_name; } return $self->{'.script_name'}; } #### Method: referer # Return the HTTP_REFERER: useful for generating # a GO BACK button. #### sub referer { my($self) = self_or_CGI(@_); return $self->http('referer'); } #### Method: server_name # Return the name of the server #### sub server_name { return $ENV{'SERVER_NAME'} || 'localhost'; } #### Method: server_software # Return the name of the server software #### sub server_software { return $ENV{'SERVER_SOFTWARE'} || 'cmdline'; } #### Method: virtual_port # Return the server port, taking virtual hosts into account #### sub virtual_port { my($self) = self_or_default(@_); my $vh = $self->http('x_forwarded_host') || $self->http('host'); my $protocol = $self->protocol; if ($vh) { return ($vh =~ /:(\d+)$/)[0] || ($protocol eq 'https' ? 443 : 80); } else { return $self->server_port(); } } #### Method: server_port # Return the tcp/ip port the server is running on #### sub server_port { return $ENV{'SERVER_PORT'} || 80; # for debugging } #### Method: server_protocol # Return the protocol (usually HTTP/1.0) #### sub server_protocol { return $ENV{'SERVER_PROTOCOL'} || 'HTTP/1.0'; # for debugging } #### Method: http # Return the value of an HTTP variable, or # the list of variables if none provided #### sub http { my ($self,$parameter) = self_or_CGI(@_); if ( defined($parameter) ) { $parameter =~ tr/-a-z/_A-Z/; if ( $parameter =~ /^HTTP(?:_|$)/ ) { return $ENV{$parameter}; } return $ENV{"HTTP_$parameter"}; } return grep { /^HTTP(?:_|$)/ } sort keys %ENV; } #### Method: https # Return the value of HTTPS, or # the value of an HTTPS variable, or # the list of variables #### sub https { my ($self,$parameter) = self_or_CGI(@_); if ( defined($parameter) ) { $parameter =~ tr/-a-z/_A-Z/; if ( $parameter =~ /^HTTPS(?:_|$)/ ) { return $ENV{$parameter}; } return $ENV{"HTTPS_$parameter"}; } return wantarray ? grep { /^HTTPS(?:_|$)/ } sort keys %ENV : $ENV{'HTTPS'}; } #### Method: protocol # Return the protocol (http or https currently) #### sub protocol { local($^W)=0; my $self = shift; return 'https' if uc($self->https()) eq 'ON'; return 'https' if $self->server_port == 443; my $prot = $self->server_protocol; my($protocol,$version) = split('/',$prot); return "\L$protocol\E"; } #### Method: remote_ident # Return the identity of the remote user # (but only if his host is running identd) #### sub remote_ident { return (defined $ENV{'REMOTE_IDENT'}) ? $ENV{'REMOTE_IDENT'} : undef; } #### Method: auth_type # Return the type of use verification/authorization in use, if any. #### sub auth_type { return (defined $ENV{'AUTH_TYPE'}) ? $ENV{'AUTH_TYPE'} : undef; } #### Method: remote_user # Return the authorization name used for user # verification. #### sub remote_user { return (defined $ENV{'REMOTE_USER'}) ? $ENV{'REMOTE_USER'} : undef; } #### Method: user_name # Try to return the remote user's name by hook or by # crook #### sub user_name { my ($self) = self_or_CGI(@_); return $self->http('from') || $ENV{'REMOTE_IDENT'} || $ENV{'REMOTE_USER'}; } #### Method: nosticky # Set or return the NOSTICKY global flag #### sub nosticky { my ($self,$param) = self_or_CGI(@_); $CGI::NOSTICKY = $param if defined($param); return $CGI::NOSTICKY; } #### Method: nph # Set or return the NPH global flag #### sub nph { my ($self,$param) = self_or_CGI(@_); $CGI::NPH = $param if defined($param); return $CGI::NPH; } #### Method: private_tempfiles # Set or return the private_tempfiles global flag #### sub private_tempfiles { warn "private_tempfiles has been deprecated"; return 0; } #### Method: close_upload_files # Set or return the close_upload_files global flag #### sub close_upload_files { my ($self,$param) = self_or_CGI(@_); $CGI::CLOSE_UPLOAD_FILES = $param if defined($param); return $CGI::CLOSE_UPLOAD_FILES; } #### Method: default_dtd # Set or return the default_dtd global #### sub default_dtd { my ($self,$param,$param2) = self_or_CGI(@_); if (defined $param2 && defined $param) { $CGI::DEFAULT_DTD = [ $param, $param2 ]; } elsif (defined $param) { $CGI::DEFAULT_DTD = $param; } return $CGI::DEFAULT_DTD; } # -------------- really private subroutines ----------------- sub _maybe_escapeHTML { # hack to work around earlier hacks push @_,$_[0] if @_==1 && $_[0] eq 'CGI'; my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_); return undef unless defined($toencode); return $toencode if ref($self) && !$self->{'escape'}; return $self->escapeHTML($toencode, $newlinestoo); } sub previous_or_default { my($self,$name,$defaults,$override) = @_; my(%selected); if (!$override && ($self->{'.fieldnames'}->{$name} || defined($self->param($name)) ) ) { $selected{$_}++ for $self->param($name); } elsif (defined($defaults) && ref($defaults) && (ref($defaults) eq 'ARRAY')) { $selected{$_}++ for @{$defaults}; } else { $selected{$defaults}++ if defined($defaults); } return %selected; } sub register_parameter { my($self,$param) = @_; $self->{'.parametersToAdd'}->{$param}++; } sub get_fields { my($self) = @_; return $self->CGI::hidden('-name'=>'.cgifields', '-values'=>[sort keys %{$self->{'.parametersToAdd'}}], '-override'=>1); } sub read_from_cmdline { my($input,@words); my($query_string); my($subpath); if ($DEBUG && @ARGV) { @words = @ARGV; } elsif ($DEBUG > 1) { require Text::ParseWords; print STDERR "(offline mode: enter name=value pairs on standard input; press ^D or ^Z when done)\n"; chomp(@lines = ); # remove newlines $input = join(" ",@lines); @words = &Text::ParseWords::old_shellwords($input); } for (@words) { s/\\=/%3D/g; s/\\&/%26/g; } if ("@words"=~/=/) { $query_string = join('&',@words); } else { $query_string = join('+',@words); } if ($query_string =~ /^(.*?)\?(.*)$/) { $query_string = $2; $subpath = $1; } return { 'query_string' => $query_string, 'subpath' => $subpath }; } ##### # subroutine: read_multipart # # Read multipart data and store it into our parameters. # An interesting feature is that if any of the parts is a file, we # create a temporary file and open up a filehandle on it so that the # caller can read from it if necessary. ##### sub read_multipart { my($self,$boundary,$length) = @_; my($buffer) = $self->new_MultipartBuffer($boundary,$length); return unless $buffer; my(%header,$body); my $filenumber = 0; while (!$buffer->eof) { %header = $buffer->readHeader; unless (%header) { $self->cgi_error("400 Bad request (malformed multipart POST)"); return; } $header{'Content-Disposition'} ||= ''; # quench uninit variable warning my $param = _mp_value_parse( $header{'Content-Disposition'},'name' ); $param .= $TAINTED; # See RFC 1867, 2183, 2045 # NB: File content will be loaded into memory should # content-disposition parsing fail. my ($filename) = $header{'Content-Disposition'} =~/ filename=(("[^"]*")|([a-z\d!\#'\*\+,\.^_\`\{\}\|\~]*))/i; $filename ||= ''; # quench uninit variable warning $filename =~ s/^"([^"]*)"$/$1/; # Test for Opera's multiple upload feature my($multipart) = ( defined( $header{'Content-Type'} ) && $header{'Content-Type'} =~ /multipart\/mixed/ ) ? 1 : 0; # add this parameter to our list $self->add_parameter($param); # If no filename specified, then just read the data and assign it # to our parameter list. if ( ( !defined($filename) || $filename eq '' ) && !$multipart ) { my($value) = $buffer->readBody; $value .= $TAINTED; push(@{$self->{param}{$param}},$value); next; } UPLOADS: { # If we get here, then we are dealing with a potentially large # uploaded form. Save the data to a temporary file, then open # the file for reading. # skip the file if uploads disabled if ($DISABLE_UPLOADS) { while (defined($data = $buffer->read)) { } last UPLOADS; } # set the filename to some recognizable value if ( ( !defined($filename) || $filename eq '' ) && $multipart ) { $filename = "multipart/mixed"; } my $tmp_dir = $CGI::OS eq 'WINDOWS' ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) ) : undef; # File::Temp defaults to TMPDIR require CGI::File::Temp; my $filehandle = CGI::File::Temp->new( UNLINK => $UNLINK_TMP_FILES, DIR => $tmp_dir, ); $filehandle->_mp_filename( $filename ); $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode && defined fileno($filehandle); # if this is an multipart/mixed attachment, save the header # together with the body for later parsing with an external # MIME parser module if ( $multipart ) { for ( sort keys %header ) { print $filehandle "$_: $header{$_}${CRLF}"; } print $filehandle "${CRLF}"; } my ($data); local($\) = ''; my $totalbytes = 0; while (defined($data = $buffer->read)) { if (defined $self->{'.upload_hook'}) { $totalbytes += length($data); &{$self->{'.upload_hook'}}($filename ,$data, $totalbytes, $self->{'.upload_data'}); } print $filehandle $data if ($self->{'use_tempfile'}); } # back up to beginning of file seek($filehandle,0,0); ## Close the filehandle if requested this allows a multipart MIME ## upload to contain many files, and we won't die due to too many ## open file handles. The user can access the files using the hash ## below. close $filehandle if $CLOSE_UPLOAD_FILES; $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode; # Save some information about the uploaded file where we can get # at it later. # Use the typeglob + filename as the key, as this is guaranteed to be # unique for each filehandle. Don't use the file descriptor as # this will be re-used for each filehandle if the # close_upload_files feature is used. $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = { hndl => $filehandle, name => $filehandle->filename, info => {%header}, }; push(@{$self->{param}{$param}},$filehandle); } } } sub _mp_value_parse { my ( $string,$field ) = @_; my $is_quoted = $string =~/[\s;]$field="/ ? 1 : 0; my $param; if ( $is_quoted ) { # a quoted token cannot contain anything but an unescaped quote ($param) = $string =~/[\s;]$field="((?:\\"|[^"])*)"/; } else { # a plain token cannot contain any reserved characters # https://tools.ietf.org/html/rfc2616#section-2.2 # separators = "(" | ")" | "<" | ">" | "@" # | "," | ";" | ":" | "\" | <"> # | "/" | "[" | "]" | "?" | "=" # | "{" | "}" | SP | HT ($param) = $string =~/[\s;]$field=([^\(\)<>\@,;:\\"\/\[\]\?=\{\} \015\n\t]*)/; } return $param; } ##### # subroutine: read_multipart_related # # Read multipart/related data and store it into our parameters. The # first parameter sets the start of the data. The part identified by # this Content-ID will not be stored as a file upload, but will be # returned by this method. All other parts will be available as file # uploads accessible by their Content-ID ##### sub read_multipart_related { my($self,$start,$boundary,$length) = @_; my($buffer) = $self->new_MultipartBuffer($boundary,$length); return unless $buffer; my(%header,$body); my $filenumber = 0; my $returnvalue; while (!$buffer->eof) { %header = $buffer->readHeader; unless (%header) { $self->cgi_error("400 Bad request (malformed multipart POST)"); return; } my($param) = $header{'Content-ID'}=~/\<([^\>]*)\>/; $param .= $TAINTED; # If this is the start part, then just read the data and assign it # to our return variable. if ( $param eq $start ) { $returnvalue = $buffer->readBody; $returnvalue .= $TAINTED; next; } # add this parameter to our list $self->add_parameter($param); UPLOADS: { # If we get here, then we are dealing with a potentially large # uploaded form. Save the data to a temporary file, then open # the file for reading. # skip the file if uploads disabled if ($DISABLE_UPLOADS) { while (defined($data = $buffer->read)) { } last UPLOADS; } my $tmp_dir = $CGI::OS eq 'WINDOWS' ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) ) : undef; # File::Temp defaults to TMPDIR require CGI::File::Temp; my $filehandle = CGI::File::Temp->new( UNLINK => $UNLINK_TMP_FILES, DIR => $tmp_dir, ); $filehandle->_mp_filename( $filehandle->filename ); $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode && defined fileno($filehandle); my ($data); local($\) = ''; my $totalbytes; while (defined($data = $buffer->read)) { if (defined $self->{'.upload_hook'}) { $totalbytes += length($data); &{$self->{'.upload_hook'}}($param ,$data, $totalbytes, $self->{'.upload_data'}); } print $filehandle $data if ($self->{'use_tempfile'}); } # back up to beginning of file seek($filehandle,0,0); ## Close the filehandle if requested this allows a multipart MIME ## upload to contain many files, and we won't die due to too many ## open file handles. The user can access the files using the hash ## below. close $filehandle if $CLOSE_UPLOAD_FILES; $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode; # Save some information about the uploaded file where we can get # at it later. # Use the typeglob + filename as the key, as this is guaranteed to be # unique for each filehandle. Don't use the file descriptor as # this will be re-used for each filehandle if the # close_upload_files feature is used. $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = { hndl => $filehandle, name => $filehandle->filename, info => {%header}, }; push(@{$self->{param}{$param}},$filehandle); } } return $returnvalue; } sub upload { my($self,$param_name) = self_or_default(@_); my @param = grep {ref($_) && defined(fileno($_))} $self->param($param_name); return unless @param; return wantarray ? @param : $param[0]; } sub tmpFileName { my($self,$filename) = self_or_default(@_); # preferred calling convention: $filename came directly from param or upload if (ref $filename) { return $self->{'.tmpfiles'}->{$$filename . $filename}->{name} || ''; } # backwards compatible with older versions: $filename is merely equal to # one of our filenames when compared as strings foreach my $param_name ($self->param) { foreach my $filehandle ($self->multi_param($param_name)) { if ($filehandle eq $filename) { return $self->{'.tmpfiles'}->{$$filehandle . $filehandle}->{name} || ''; } } } return ''; } sub uploadInfo { my($self,$filename) = self_or_default(@_); return if ! defined $$filename; return $self->{'.tmpfiles'}->{$$filename . $filename}->{info}; } # internal routine, don't use sub _set_values_and_labels { my $self = shift; my ($v,$l,$n) = @_; $$l = $v if ref($v) eq 'HASH' && !ref($$l); return $self->param($n) if !defined($v); return $v if !ref($v); return ref($v) eq 'HASH' ? sort keys %$v : @$v; } # internal routine, don't use sub _set_attributes { my $self = shift; my($element, $attributes) = @_; return '' unless defined($attributes->{$element}); $attribs = ' '; for my $attrib (sort keys %{$attributes->{$element}}) { (my $clean_attrib = $attrib) =~ s/^-//; $attribs .= "@{[lc($clean_attrib)]}=\"$attributes->{$element}{$attrib}\" "; } $attribs =~ s/ $//; return $attribs; } ######################################################### # Globals and stubs for other packages that we use. ######################################################### ######################## CGI::MultipartBuffer #################### package CGI::MultipartBuffer; $_DEBUG = 0; # how many bytes to read at a time. We use # a 4K buffer by default. $MultipartBuffer::INITIAL_FILLUNIT ||= 1024 * 4; $MultipartBuffer::TIMEOUT ||= 240*60; # 4 hour timeout for big files $MultipartBuffer::SPIN_LOOP_MAX ||= 2000; # bug fix for some Netscape servers $MultipartBuffer::CRLF ||= $CGI::CRLF; $INITIAL_FILLUNIT = $MultipartBuffer::INITIAL_FILLUNIT; $TIMEOUT = $MultipartBuffer::TIMEOUT; $SPIN_LOOP_MAX = $MultipartBuffer::SPIN_LOOP_MAX; $CRLF = $MultipartBuffer::CRLF; sub new { my($package,$interface,$boundary,$length) = @_; $FILLUNIT = $INITIAL_FILLUNIT; $CGI::DefaultClass->binmode($IN); # if $CGI::needs_binmode; # just do it always # If the user types garbage into the file upload field, # then Netscape passes NOTHING to the server (not good). # We may hang on this read in that case. So we implement # a read timeout. If nothing is ready to read # by then, we return. # Netscape seems to be a little bit unreliable # about providing boundary strings. my $boundary_read = 0; if ($boundary) { # Under the MIME spec, the boundary consists of the # characters "--" PLUS the Boundary string # BUG: IE 3.01 on the Macintosh uses just the boundary -- not # the two extra hyphens. We do a special case here on the user-agent!!!! $boundary = "--$boundary" unless CGI::user_agent('MSIE\s+3\.0[12];\s*Mac|DreamPassport'); } else { # otherwise we find it ourselves my($old); ($old,$/) = ($/,$CRLF); # read a CRLF-delimited line $boundary = ; # BUG: This won't work correctly under mod_perl $length -= length($boundary); chomp($boundary); # remove the CRLF $/ = $old; # restore old line separator $boundary_read++; } my $self = {LENGTH=>$length, CHUNKED=>!$length, BOUNDARY=>$boundary, INTERFACE=>$interface, BUFFER=>'', }; $FILLUNIT = length($boundary) if length($boundary) > $FILLUNIT; my $retval = bless $self,ref $package || $package; # Read the preamble and the topmost (boundary) line plus the CRLF. unless ($boundary_read) { while ($self->read(0)) { } } die "Malformed multipart POST: data truncated\n" if $self->eof; return $retval; } sub readHeader { my($self) = @_; my($end); my($ok) = 0; my($bad) = 0; local($CRLF) = "\015\012" if $CGI::OS eq 'VMS' || $CGI::EBCDIC; do { $self->fillBuffer($FILLUNIT); $ok++ if ($end = index($self->{BUFFER},"${CRLF}${CRLF}")) >= 0; $ok++ if $self->{BUFFER} eq ''; $bad++ if !$ok && $self->{LENGTH} <= 0; # this was a bad idea # $FILLUNIT *= 2 if length($self->{BUFFER}) >= $FILLUNIT; } until $ok || $bad; return () if $bad; #EBCDIC NOTE: translate header into EBCDIC, but watch out for continuation lines! my($header) = substr($self->{BUFFER},0,$end+2); substr($self->{BUFFER},0,$end+4) = ''; my %return; if ($CGI::EBCDIC) { warn "untranslated header=$header\n" if $_DEBUG; $header = CGI::Util::ascii2ebcdic($header); warn "translated header=$header\n" if $_DEBUG; } # See RFC 2045 Appendix A and RFC 822 sections 3.4.8 # (Folding Long Header Fields), 3.4.3 (Comments) # and 3.4.5 (Quoted-Strings). my $token = '[-\w!\#$%&\'*+.^_\`|{}~]'; $header=~s/$CRLF\s+/ /og; # merge continuation lines while ($header=~/($token+):\s+([^$CRLF]*)/mgox) { my ($field_name,$field_value) = ($1,$2); $field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize $return{$field_name}=$field_value; } return %return; } # This reads and returns the body as a single scalar value. sub readBody { my($self) = @_; my($data); my($returnval)=''; #EBCDIC NOTE: want to translate returnval into EBCDIC HERE while (defined($data = $self->read)) { $returnval .= $data; } if ($CGI::EBCDIC) { warn "untranslated body=$returnval\n" if $_DEBUG; $returnval = CGI::Util::ascii2ebcdic($returnval); warn "translated body=$returnval\n" if $_DEBUG; } return $returnval; } # This will read $bytes or until the boundary is hit, whichever happens # first. After the boundary is hit, we return undef. The next read will # skip over the boundary and begin reading again; sub read { my($self,$bytes) = @_; # default number of bytes to read $bytes = $bytes || $FILLUNIT; # Fill up our internal buffer in such a way that the boundary # is never split between reads. $self->fillBuffer($bytes); my $boundary_start = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}) : $self->{BOUNDARY}; my $boundary_end = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}.'--') : $self->{BOUNDARY}.'--'; # Find the boundary in the buffer (it may not be there). my $start = index($self->{BUFFER},$boundary_start); warn "boundary=$self->{BOUNDARY} length=$self->{LENGTH} start=$start\n" if $_DEBUG; # protect against malformed multipart POST operations die "Malformed multipart POST\n" unless $self->{CHUNKED} || ($start >= 0 || $self->{LENGTH} > 0); #EBCDIC NOTE: want to translate boundary search into ASCII here. # If the boundary begins the data, then skip past it # and return undef. if ($start == 0) { # clear us out completely if we've hit the last boundary. if (index($self->{BUFFER},$boundary_end)==0) { $self->{BUFFER}=''; $self->{LENGTH}=0; return undef; } # just remove the boundary. substr($self->{BUFFER},0,length($boundary_start))=''; $self->{BUFFER} =~ s/^\012\015?//; return undef; } my $bytesToReturn; if ($start > 0) { # read up to the boundary $bytesToReturn = $start-2 > $bytes ? $bytes : $start; } else { # read the requested number of bytes # leave enough bytes in the buffer to allow us to read # the boundary. Thanks to Kevin Hendrick for finding # this one. $bytesToReturn = $bytes - (length($boundary_start)+1); } my $returnval=substr($self->{BUFFER},0,$bytesToReturn); substr($self->{BUFFER},0,$bytesToReturn)=''; # If we hit the boundary, remove the CRLF from the end. return ($bytesToReturn==$start) ? substr($returnval,0,-2) : $returnval; } # This fills up our internal buffer in such a way that the # boundary is never split between reads sub fillBuffer { my($self,$bytes) = @_; return unless $self->{CHUNKED} || $self->{LENGTH}; my($boundaryLength) = length($self->{BOUNDARY}); my($bufferLength) = length($self->{BUFFER}); my($bytesToRead) = $bytes - $bufferLength + $boundaryLength + 2; $bytesToRead = $self->{LENGTH} if !$self->{CHUNKED} && $self->{LENGTH} < $bytesToRead; # Try to read some data. We may hang here if the browser is screwed up. my $bytesRead = $self->{INTERFACE}->read_from_client(\$self->{BUFFER}, $bytesToRead, $bufferLength); warn "bytesToRead=$bytesToRead, bufferLength=$bufferLength, buffer=$self->{BUFFER}\n" if $_DEBUG; $self->{BUFFER} = '' unless defined $self->{BUFFER}; # An apparent bug in the Apache server causes the read() # to return zero bytes repeatedly without blocking if the # remote user aborts during a file transfer. I don't know how # they manage this, but the workaround is to abort if we get # more than SPIN_LOOP_MAX consecutive zero reads. if ($bytesRead <= 0) { die "CGI.pm: Server closed socket during multipart read (client aborted?).\n" if ($self->{ZERO_LOOP_COUNTER}++ >= $SPIN_LOOP_MAX); } else { $self->{ZERO_LOOP_COUNTER}=0; } $self->{LENGTH} -= $bytesRead if !$self->{CHUNKED} && $bytesRead; } # Return true when we've finished reading sub eof { my($self) = @_; return 1 if (length($self->{BUFFER}) == 0) && ($self->{LENGTH} <= 0); undef; } 1; package CGI; # We get a whole bunch of warnings about "possibly uninitialized variables" # when running with the -w switch. Touch them all once to get rid of the # warnings. This is ugly and I hate it. if ($^W) { $CGI::CGI = ''; $CGI::CGI=<