Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Wanet Telecoms Ltd on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Uploading files without CGI.pm 2

Status
Not open for further replies.

MatthewP

Programmer
Jan 16, 2001
176
GB
Im trying to get a file upload script working, but without using CGI.pm, which I've always used before. I assume the CGI module does something with binary uploads, as I'm trying to read the the rest of the input from the form, and it seems to vanish.

Any ideas of what I need to do?
 
Or failing that, can I retrieve the whole param-> business back from CGI.pm as a hash? Basically, I've got this huge bunch of scripts and modules that run with input coming in in the way of %FORM. I've tried %FORM=%$q and the like, but I still just get a bunch of array references back!

 
Is there a reason why this time you're not using CGI.pm? Just curious. I have some code that's probably too long to post here that you can use. I can email it to you if you'll email me or post your email address. Or ... if no-one minds, I'll go ahead and post it here, but don't ask me to try to explain how it works! :)
Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
I'm not using the CGI module because it's already been done without it, and I just don't want to go through all these scripts converting everything - it'll take an age. Plus you've got me intrigued in this code now - It's always good to poke around in other people's now and again! You can email me if it really is too big for here at matt@cosmos8.freeserve.co.uk - and I promise not to ask you explain it OK!

Thanks! :eek:)
Matt.
 
Failing your first request, sorry, "Using CGI.pm"

#!/usr/local/bin/perl
use CGI;
$cgi = new CGI;
@keys = $cgi->param;
foreach $key (@keys) { $hash{$key} = $cgi->param($key); }




HTH If you are new to Tek-Tips, please use descriptive titles, check the FAQs,
and beware the evil typo.
 
OK, here's a BUNCH of code that handles form data WITHOUT using CGI.pm for y'all to puzzle over. In particular it will handle file-upload data.

NOTES:
The file-upload part only works if your form tag has the enctype="multipart/form-data" attribute added to it. I couldn't find a better way of handling file uploads.

This was specifically written to run on *nix systems. It shouldn't be too difficult to modify it to run on Windows systems, though for the life of me I don't know why you would WANT to.

This hasn't been exhaustively tested, but it works for what I use it for.

The code to call the subroutine that gets the form data:
Code:
GetFormData(*FormData);
The code to reference the form data ('fieldname' is the name of the form field):
Code:
## Normal form fields:
$fieldvalue = $FormData{'fieldname'}

## File-upload fields:
## The original file name is in $FormData{'FILE-UPLOAD'}{'fieldname'}{'filename'}
## The uploaded file's MIME type (i.e. image/gif) is in
##    $FormData{'FILE-UPLOAD'}{'fieldname'}{'type'}
## The name of the temporary file the file was uploaded to is in
##    $FormData{'FILE-UPLOAD'}{'fieldname'}{'tempfile'}
## The size of the uploaded file is in $FormData{'FILE-UPLOAD'}{'fieldname'}{'size'}

## Unix-specific code to convert the filename and copy the file from
## its temporary location to a permanent location:
my $filename = $FormData{'FILE-UPLOAD'}{'fieldname'}{'filename'};
my $Dir = "/path/relative/to/web/root";
$filename =~ s|\\|/|g; # convert backslashes to slashes
($newval) = ($filename =~ m|.*?/([^/\.]+\.[^/\.]+)\Z|); # parse out filename
$newval =~ s/ /_/g; # convert spaces to underscores
my $cmd = "mv $FormData{'FILE-UPLOAD'}{'fieldname'}{'tempfile'} $ENV{'DOCUMENT_ROOT'}$Dir/$newval";
system($cmd);
The subroutines. Note that subroutine CgiError is left to your imagination.
Code:
#---------------------------------------------------------------------
# GetFormData
#---------------------------------------------------------------------
# Calls GetEncodedFormData or GetMultipartFormData, depending on
# the value of CONTENT_TYPE environment variable.
#---------------------------------------------------------------------

sub GetFormData {

if ( $ENV{"CONTENT_TYPE"} eq "" ) {
   return GetEncodedFormData(@_);
} elsif ( $ENV{"CONTENT_TYPE"} eq "application/x-[URL unfurl="true"]www-form-urlencoded"[/URL] ) {
   return GetEncodedFormData(@_);
} elsif ( $ENV{"CONTENT_TYPE"} =~ m[multipart/form-data] ) {
   return GetMultipartFormData(@_);
} else {
   return 0;
}

} # GetFormData

#---------------------------------------------------------------------
# GetEncodedFormData
#---------------------------------------------------------------------
# Reads in GET or POST data and stores it RAW in $FormData.
# Converts plus signs back to spaces.
# Stores each key=value in a member of the list "@FormData".
# Stores key and value in the associative array %FormData.
# Uses null ('\0') to separate multiple selections.
#
# Returns TRUE (the length of the data) if there WAS any data.
#
# If a variable-glob parameter (e.g. *cgi_input) is passed, that name
# is used for the form data variables instead of $FormData, @FormData, 
# and %FormData.
#---------------------------------------------------------------------

sub GetEncodedFormData {

local (*FormData) = @_ if @_;
my ($x, $key, $val);

# Find out method and get the form data into $FormData
if ( $ENV{"REQUEST_METHOD"} eq "GET" ) {
   $FormData = $ENV{"QUERY_STRING"};
} elsif ( $ENV{"REQUEST_METHOD"} eq "POST" ) {
   read(STDIN, $FormData, $ENV{"CONTENT_LENGTH"});
} else {
   CgiError(&quot;$ThisPgm Script Error<br>&quot; . 
      &quot;Invalid Form Method: &quot; . $ENV{&quot;REQUEST_METHOD&quot;});
}

# Split data into list (array) of key=value entries
@FormData = split(/&/, $FormData);

# Process the FormData list
foreach $x (0 .. $#FormData) {
   # Convert plus signs back to spaces
   $FormData[$x] =~ s/\+/ /g;

   # Split into key and value (on the first equal sign found).
   ($key, $val) = split(/=/, $FormData[$x], 2);
   
   # Convert %XX sequences into characters
   $key =~ s/%(..)/pack(&quot;c&quot;, hex($1))/ge;
   $val =~ s/%(..)/pack(&quot;c&quot;, hex($1))/ge;

   # Convert hyphens in field names into underscores
   $key =~ s/-/_/g;
   
   # Replace list element with converted values
   $FormData[$x] = $key . &quot;=&quot; . $val;

   # Create associative array member
   # Null value ('\0') separates multiple values
   $FormData{$key} .= &quot;\0&quot; if (defined($FormData{$key}));
   $FormData{$key} .= $val;

}

return length($FormData); 

} # GetEncodedFormData

#---------------------------------------------------------------------
# GetMultipartFormData
#---------------------------------------------------------------------
# Reads in POST data from enctype=\&quot;multipart/form-data\&quot;.
# Converts plus signs back to spaces.
# Stores key and value in the associative array %FormData.
# Uses null ('\0') to separate multiple selections.
#
# Returns TRUE (the length of the data) if there WAS any data.
#
# If a variable-glob parameter (e.g. *cgi_input) is passed, that name
# is used for the form data variables instead of %FormData.
#---------------------------------------------------------------------

sub GetMultipartFormData {

local (*FormData) = @_ if @_;

# As far as I know, you can't have GET with Multipart form data

my($boundary) = ( $ENV{&quot;CONTENT_TYPE&quot;} =~ m/boundary\s*=\s*([-A-Za-z0-9]+)/i);

my($varname, $filename, $type, $value, $tempfile);

my $oldrs = $/;
$/ = &quot;\x0D\x0A&quot;;

PART: while (<STDIN>) {
   # Boundary string starts a new part
   last PART if /\A--$boundary--\Z/;
   next PART if /\A--$boundary\x0D\x0A\Z/;
   if ( /\AContent-Disposition:\s*form-data/i ) {
      ($varname) = /name\s*=\s*&quot;(.*?)&quot;/i;
      ($filename) = /filename\s*=\s*&quot;(.*?)&quot;/i;
      # Convert hyphens in field name into underscores
      $varname =~ s/-/_/g;
      next PART;
   }
   if ( /\AContent-type:\s*(\S+)/i ) {
      $type = $1;
      next PART;
   }
   next PART unless /\A\s+\Z/; # blank line ends headers, starts value
   # if it's a file, now's the time to open the temp file
   if ( $filename ) {
      # Find out which temporary directory to use
      my $tempdir = FindTempDir();
      # Create and open a temporary file
      $tempfile = &quot;TDS0000&quot;;
      while ( -e &quot;$tempdir/$tempfile&quot; ) {
         $tempfile++;
      }
      open(CGIOUT, &quot;>$tempdir/$tempfile&quot;) ||
         CgiError(qq[GetMultipartFormData could not open temp file:<br><CODE>$tempdir/$tempfile</CODE>]);
      binmode CGIOUT;
      $tempfile = $tempdir.&quot;/&quot;.$tempfile;
   }
   while (<STDIN>) {
      if ( /\A--$boundary/ ) { # boundary ends this part
         if ( $filename ) {
            chomp $value; # remove last CRLF
            $fsize += length($value);
            syswrite(CGIOUT, $value, $fsize); # write last part
            close CGIOUT;
            $FormData{$varname} = &quot;FILE-UPLOAD&quot;;
            $FormData{'FILE-UPLOAD'}{$varname}{'filename'} = $filename;
            $FormData{'FILE-UPLOAD'}{$varname}{'type'} = $type;
            $FormData{'FILE-UPLOAD'}{$varname}{'tempfile'} = $tempfile;
            $FormData{'FILE-UPLOAD'}{$varname}{'size'} = $fsize;
            $varname = &quot;&quot;;
            $value = &quot;&quot;;
            $filename = &quot;&quot;;
            $type = &quot;&quot;;
            $tempfile = &quot;&quot;;
            $fsize = 0;
            next PART;
         } else {
            chomp $value; # remove last CRLF
            $FormData{$varname} .= &quot;\0&quot; if (defined($FormData{$varname}));
            $FormData{$varname} .= $value;
            $varname = &quot;&quot;;
            $value = &quot;&quot;;
            next PART;
         }
      }
      if ( $filename ) {
         if ( $value ) { # if not first part
            $len = length($value);
            $fsize += length($value);
            syswrite(CGIOUT, $value, $fsize); # write previous part
         }
         $len = length;
         $value = $_;
      } else {
         $value .= $_;
         next;
      }
   }
}

$/ = $oldrs;

return $ENV{'CONTENT_LENGTH'}; 

} # GetMultipartFormData

#---------------------------------------------------------------------
# FindTempDir
#---------------------------------------------------------------------
# Attempt to find a usable temporary directory
#---------------------------------------------------------------------

sub FindTempDir {

unless ($TMPDIRECTORY) {
   @TEMPDIRS = (
      &quot;/tmp&quot;,
      &quot;/usr/tmp&quot;,
      &quot;/var/tmp&quot;,
      &quot;/temp&quot;,
   );
   unshift(@TEMPDIRS, $ENV{'TMPDIR'}) if $ENV{'TMPDIR'};
   unshift(@TEMPDIRS, $ENV{'TEMP'}) if $ENV{'TEMP'};

    foreach $TEMPDIR (@TEMPDIRS) {
      if ( -d $TEMPDIR && -w $TEMPDIR ) {
         return $TEMPDIR;
      }
   }
   return &quot;&quot;;
}

return $TMPDIRECTORY;

} # FindTempDir
Have fun!
Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
Thanks goBoating, that should do the trick I think. And thanks for the code as well Tracy. It doesn't look too bad actually - just long!

Matt.
 
Can I use that file upload code in my script? AIM: XCalvin1984
Email: calvin@puremadnezz.com
MSN: xcloud2000x@hotmail.com
Web Site: Yahoo Messenger: xcloud2000x

Contact me for any programming help and whatnot. I'll try my best to help! :)
 
If you mean my code, go ahead. If I hadn't wanted people to use it, I wouldn't have posted it in a public forum. Tracy Dryden
tracy@bydisn.com

Meddle not in the affairs of dragons,
For you are crunchy, and good with mustard.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top