#GPL #GPL libwhisker copyright 2000-2004 by rfp.labs #GPL #GPL This program is free software; you can redistribute it and/or #GPL modify it under the terms of the GNU General Public License #GPL as published by the Free Software Foundation; either version 2 #GPL of the License, or (at your option) any later version. #GPL #GPL This program is distributed in the hope that it will be useful, #GPL but WITHOUT ANY WARRANTY; without even the implied warranty of #GPL MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GPL GNU General Public License for more details. #GPL ######################################################################## =item B Params: \%multi_hash, $param_name, $param_value Return: nothing This function sets the named parameter to the given value within the supplied multipart hash. =cut sub multipart_set { my ( $hr, $n, $v ) = @_; return if ( !ref($hr) ); # error check return undef if ( !defined $n || $n eq '' ); $$hr{$n} = $v; } ######################################################################## =item B Params: \%multi_hash, $param_name Return: $param_value, undef on error This function retrieves the named parameter to the given value within the supplied multipart hash. There is a special case where the named parameter is actually a file--in which case the resulting value will be "\0FILE". In general, all special values will be prefixed with a NULL character. In order to get a file's info, use multipart_getfile(). =cut sub multipart_get { my ( $hr, $n ) = @_; return undef if ( !ref($hr) ); # error check return undef if ( !defined $n || $n eq '' ); return $$hr{$n}; } ######################################################################## =item B Params: \%multi_hash, $param_name, $file_path [, $filename] Return: undef on error, 1 on success NOTE: this function does not actually add the contents of $file_path into the %multi_hash; instead, multipart_write() inserts the content when generating the final request. =cut sub multipart_setfile { my ( $hr, $n, $path ) = ( shift, shift, shift ); my ($fname) = shift; return undef if ( !ref($hr) ); # error check return undef if ( !defined $n || $n eq '' ); return undef if ( !defined $path ); return undef if ( !( -e $path && -f $path ) ); if ( !defined $fname ) { $path =~ m/[\\\/]([^\\\/]+)$/; $fname = $1 || "whisker-file"; } $$hr{$n} = "\0FILE"; $$hr{"\0$n"} = [ $path, $fname ]; return 1; } ######################################################################## =item B Params: \%multi_hash, $file_param_name Return: $path, $name ($path=undef on error) multipart_getfile is used to retrieve information for a file parameter contained in %multi_hash. To use this you would most likely do: ($path,$fname)=LW2::multipart_getfile(\%multi,"param_name"); =cut sub multipart_getfile { my ( $hr, $n ) = @_; return undef if ( !ref($hr) ); # error check return undef if ( !defined $n || $n eq '' ); return undef if ( !defined $$hr{$n} || $$hr{$n} ne "\0FILE" ); return @{ $$hr{"\0$n"} }; } ######################################################################## =item B Params: \%multi_hash [, $new_boundary_name] Return: $current_boundary_name multipart_boundary is used to retrieve, and optionally set, the multipart boundary used for the request. NOTE: the function does no checking on the supplied boundary, so if you want things to work make sure it's a legit boundary. Libwhisker does *not* prefix it with any '---' characters. =cut sub multipart_boundary { my ( $hr, $new ) = @_; my $ret; return undef if ( !ref($hr) ); # error check if ( !defined $$hr{"\0BOUNDARY"} ) { # create boundary on the fly my $b = uc( utils_randstr(20) ); my $b2 = '-' x 32; $$hr{"\0BOUNDARY"} = "$b2$b"; } $ret = $$hr{"\0BOUNDARY"}; if ( defined $new ) { $$hr{"\0BOUNDARY"} = $new; } return $ret; } ######################################################################## =item B Params: \%multi_hash, \%request Return: 1 if successful, undef on error multipart_write is used to parse and construct the multipart data contained in %multi_hash, and place it ready to go in the given whisker hash (%request) structure, to be sent to the server. NOTE: file contents are read into the final %request, so it's possible for the hash to get *very* large if you have (a) large file(s). =cut sub multipart_write { my ( $mp, $hr ) = @_; return undef if ( !ref($mp) ); # error check return undef if ( !ref($hr) ); # error check if ( !defined $$mp{"\0BOUNDARY"} ) { # create boundary on the fly my $b = uc( utils_randstr(20) ); my $b2 = '-' x 32; $$mp{"\0BOUNDARY"} = "$b2$b"; } my $B = $$mp{"\0BOUNDARY"}; my $EOL = $$hr{whisker}->{http_eol} || "\x0d\x0a"; my $keycount = 0; foreach ( keys %$mp ) { next if ( substr( $_, 0, 1 ) eq "\0" ); $keycount++; if ( $$mp{$_} eq "\0FILE" ) { my ( $path, $name ) = multipart_getfile( $mp, $_ ); next if ( !defined $path ); $$hr{whisker}->{data} .= "$B$EOL"; $$hr{whisker}->{data} .= "Content-Disposition: " . "form-data; name=\"$_\"; "; $$hr{whisker}->{data} .= "filename=\"$name\"$EOL"; $$hr{whisker}->{data} .= "Content-Type: " . "application/octet-stream$EOL"; $$hr{whisker}->{data} .= $EOL; next if ( !open( IN, "<$path" ) ); binmode(IN); # stupid Windows while () { $$hr{whisker}->{data} .= $_; } close(IN); $$hr{whisker}->{data} .= $EOL; # WARNING: is this right? } else { $$hr{whisker}->{data} .= "$B$EOL"; $$hr{whisker}->{data} .= "Content-Disposition: " . "form-data; name=\"$_\"$EOL"; $$hr{whisker}->{data} .= "$EOL$$mp{$_}$EOL"; } } if ($keycount) { $$hr{whisker}->{data} .= "$B--$EOL"; # closing boundary $$hr{"Content-Length"} = length( $$hr{whisker}->{data} ); $$hr{"Content-Type"} = "multipart/form-data; boundary=$B"; return 1; } else { # multipart hash didn't contain params to upload return undef; } } ######################################################################## =item B Params: \%multi_hash, \%hout_response [, $filepath ] Return: 1 if successful, undef on error multipart_read will parse the data contents of the supplied %hout_response hash, by passing the appropriate info to multipart_read_data(). Please see multipart_read_data() for more info on parameters and behaviour. NOTE: this function will return an error if the given %hout_response Content-Type is not set to "multipart/form-data". =cut sub multipart_read { my ( $mp, $hr, $fp ) = @_; return undef if ( !( defined $mp && ref($mp) ) ); return undef if ( !( defined $hr && ref($hr) ) ); my $ctype = utils_find_lowercase_key( $hr, 'content-type' ); return undef if ( !defined $ctype ); return undef if ( $ctype !~ m#^multipart/form-data#i ); return multipart_read_data( $mp, \$$hr{'whisker'}->{'data'}, undef, $fp ); } ######################################################################## =item B Params: \%multi_hash, \$data, $boundary [, $filepath ] Return: 1 if successful, undef on error multipart_read_data parses the contents of the supplied data using the given boundary and puts the values in the supplied %multi_hash. Embedded files will *not* be saved unless a $filepath is given, which should be a directory suitable for writing out temporary files. NOTE: currently only application/octet-stream is the only supported file encoding. All other file encodings will not be parsed/saved. =cut sub multipart_read_data { my ( $mp, $dr, $bound, $fp ) = @_; return undef if ( !( defined $mp && ref($mp) ) ); return undef if ( !( defined $dr && ref($dr) ) ); # if $bound is undef, then we'll snag what looks to be # the first boundry from the data. if ( !defined $bound ) { if ( $$dr =~ /([-]{5,}[A-Z0-9]+)[\r\n]/i ) { $bound = $1; } else { # we didn't spot a typical boundary; error return undef; } } if ( defined $fp && !( -d $fp && -w $fp ) ) { $fp = undef; } my $line = utils_getline_crlf( $dr, 0 ); return undef if ( !defined $line ); return undef if ( index( $line, $bound ) != 0 ); my $done = 0; while ( !$done ) { $done = _multipart_read_data_part( $mp, $dr, $bound, $fp ); } return 1; } ######################################################################## sub _multipart_read_data_part { my ( $mp, $dr, $bound, $fp ) = @_; my $dispinfo = utils_getline_crlf($dr); return 1 if ( !defined $dispinfo ); return 1 if ( length($dispinfo) == 0 ); my $lcdisp = lc($dispinfo); if ( index( $lcdisp, 'content-disposition: form-data;' ) != 0 ) { return 1; } # bad disposition my ( $s, $e, $l ); $s = index( $lcdisp, 'name="', 30 ); $e = index( $lcdisp, '"', $s + 6 ); return 1 if ( $s == -1 || $e == -1 ); my $NAME = substr( $dispinfo, $s + 6, $e - $s - 6 ); $s = index( $lcdisp, 'filename="', $e ); my $FILENAME = undef; if ( $s != -1 ) { $e = index( $lcdisp, '"', $s + 10 ); return 1 if ( $e == -1 ); # puke; malformed filename $FILENAME = substr( $dispinfo, $s + 10, $e - $s - 10 ); $s = rindex( $FILENAME, '\\' ); $e = rindex( $FILENAME, '/' ); $s = $e if ( $e > $s ); $FILENAME = substr( $FILENAME, $s + 1, length($FILENAME) - $s ); } my $CTYPE = utils_getline_crlf($dr); return 1 if ( !defined $CTYPE ); $CTYPE = lc($CTYPE); if ( length($CTYPE) > 0 ) { $s = index( $CTYPE, 'content-type:' ); return 1 if ( $s != 0 ); # bad ctype line $CTYPE = substr( $CTYPE, 13, length($CTYPE) - 13 ); $CTYPE =~ tr/ \t//d; my $xx = utils_getline_crlf($dr); return 1 if ( !defined $xx ); return 1 if ( length($xx) > 0 ); } else { $CTYPE = 'application/octet-stream'; } my $VALUE = ''; while ( defined( $l = utils_getline_crlf($dr) ) ) { last if ( index( $l, $bound ) == 0 ); $VALUE .= $l; $VALUE .= "\r\n"; } substr( $VALUE, -2, 2 ) = ''; if ( !defined $FILENAME ) { # read in param $$mp{$NAME} = $VALUE; return 0; } else { # read in file $$mp{$NAME} = "\0FILE"; return 0 if ( !defined $fp ); # TODO: funky content types, like application/x-macbinary if ( $CTYPE ne 'application/octet-stream' ) { return 0; } my $rfn = lc( utils_randstr(12) ); my $fullpath = "$fp$rfn"; $$mp{"\0$NAME"} = [ undef, $FILENAME ]; return 0 if ( !open( OUT, ">$fullpath" ) ); # error opening file binmode(OUT); # stupid Windows $$mp{"\0$NAME"} = [ $fullpath, $FILENAME ]; print OUT $VALUE; close(OUT); return 0; } # if !defined $FILENAME return 0; # um, this should never be reached... } ######################################################################## =item B Params: \%multi_hash Return: @files multipart_files_list returns an array of parameter names for all the files that are contained in %multi_hash. =cut sub multipart_files_list { my ($mp) = shift; my @ret; return () if ( !( defined $mp && ref($mp) ) ); while ( my ( $K, $V ) = each(%$mp) ) { push( @ret, $K ) if ( $V eq "\0FILE" ); } return @ret; } ######################################################################## =item B Params: \%multi_hash Return: @params multipart_files_list returns an array of parameter names for all the regular parameters (non-file) that are contained in %multi_hash. =cut sub multipart_params_list { my ($mp) = shift; my @ret; return () if ( !( defined $mp && ref($mp) ) ); while ( my ( $K, $V ) = each(%$mp) ) { push( @ret, $K ) if ( $V ne "\0FILE" && substr( $K, 0, 1 ) ne "\0" ); } return @ret; } ########################################################################