qbxk has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to do some funky things with HTTP file uploading, i'm having trouble getting CGI to work for me. Basically, it seems that i've stumbled upon a catch 22 that ends in: you can't have access to anything in your CGI variable until you're done reading all of <STDIN> (which may be substantial, and thus my very desire to acess that OTHER info first). The problem can be explained like so, by creating a CGI object STDIN is parsed and the contents of any uploaded files are read into a temp file, using the "upload_hook" feature available, you cannot yet have access to the contents of a CGI variable because it isnt' created yet, and creating one will try to parse STDIN and run your upload hook, and on and on...

let's get contextual, a simple example showing that I can't access $cgi, and therefore (right?) any of the other CGI variables that got POSTed with the file! here's a true blue script you can toss on a server and hit, it'll show you a form and when you submit it if you commented out the "ERROR!" line, it'll print the contents of your uploaded file (we suggest you upload a small text file)
#!/usr/bin/perl $|++; use strict; use warnings; use CGI; unless( $ENV{REQUEST_METHOD} eq 'POST' ) { #show the itty bitty upload form: my $cgi = new CGI; print $cgi->header() . qq{ <form action="file_upload" method="post" enctype="multipart/form +-data"> <input type="file" name="myFile"> <input type=submit> </form> }; } else { print "Content-Type: text/plain\n\n"; # [sic] can't use CGI yet, 'me +mber? sub hook; my $cgi = new CGI( \&hook, undef, 0 ); #use the hook, and don't use a tempfile, i'll handle that myself, #don't pass any "data" to hook either, what would i pass? my %uploads; sub hook { my ($filename, $buffer, $bytes_read, $data) = @_; my $fh; unless( exists $uploads{$filename} ) { #oh yeah, and $filename might not be unique either, but what other cho +ice do i have?! this condition tells me that it's our first time on t +his file my $somevar = $cgi->param('somevar'); #ERROR! #Can't call method "param" on an undefined value warn "Getting an upload for $filename"; $uploads{$filename} = 1; } print $buffer if $buffer; #back at ya! } }
now you may say "well, you don't even need the $cgi variable in your hook!" and i'll tell you that i do, this is only a demo. ;) specifically, if i could have access to the form name that contained the file the hook is currently running against, i'd be happy. (in this case that would be "myFile")

thanks ye brothers and sisters, your thoughts i will treasure.

It's not what you look like, when you're doin' what you’re doin'.
It's what you’re doin' when you’re doin' what you look like you’re doin'!
     - Charles Wright & the Watts 103rd Street Rhythm Band

Replies are listed 'Best First'.
Re: cgi and upload_hook
by TedPride (Priest) on May 22, 2006 at 10:12 UTC
    Thing is, there's no way to tell for sure where inside the passed data any specific variable is until the entire batch of data is processed, since browsers can pass the fields in any order. So the entire data feed has to be processed first. This means that your hook probably won't be able access the $cgi variable, since the hook runs during the processing. You'll just have to live with that.
Re: cgi and upload_hook
by Miguel (Friar) on May 22, 2006 at 12:34 UTC
    "if i could have access to the form name that contained the file the hook is currently running against, i'd be happy. (in this case that would be "myFile")"

    I'm not sure if I understand your question but you cannot access the form name. On the other side, you can read it from an hidden field:

    <input type='hidden' name='FormName' value='MyFile' />
      not the form name, the form field name. when hook get's called it get's passed a filename, which is the value of that form field (the input box) - i want the *name* of the field (not the value) that hook is running against.

      It's not what you look like, when you're doin' what you’re doin'.
      It's what you’re doin' when you’re doin' what you look like you’re doin'!
           - Charles Wright & the Watts 103rd Street Rhythm Band
        not the form name, the form field name
        If you have this in your form:
        <input type="file" name="myFile">
        the form field name, the one that holds the file you're uploading, is.... 'myFile'.
        You can access it with:
        my $var = $cgi->param("myFile");
Re: cgi and upload_hook
by jdtoronto (Prior) on May 22, 2006 at 15:32 UTC
    Both Miguel and TedPride are correct. But ponder this, is there isything in teh specifications that requires the data from the remote client to be sent in any particular order? No. So until the HTTP transaction is complete there is no way of knowing what data has actually been sent, let alone where it might be!

    There is, however, a solution. Using JavaScript on the client you can have the client perform one transaction which tells the server code the filename and a second that actually runs the form upload. THis is not trivial, and if I recall correctly, requires a second action from the user and the use of a cookie to track what is coming from where (or at least some session management).

    jdtoronto

      Talking about JavaScript... Ajax may be the answer here. You can process forms without pushing a submit button.
Re: cgi and upload_hook
by daitau (Beadle) on May 26, 2006 at 08:25 UTC

    This assumes you have no need to associate the file upload with the field name (ie single file upload in the whole form). You could arrange your script to be a 404 handler and pass the information needed in url instead (http://example.com/myFile_sessionID). In other words put the "field name" (myFile) as part of the action url instead of the real field name.

    # example only, need proper sanitization here in real life my ($name)=$ENV{REQUEST_URI}=~/(\w+)$/; my @param=split /_/,$name;

    You also need to change REQUEST_METHOD in your script to REDIRECT_REQUEST_METHOD.

      I'm won't do this as a redirect, but your idea is good - to just add the crucial info to the action string. it's not great, as it's not ok to do it this way in some cases (multiple file upload forms), but for my needs right now it will ensure that i get that field name from the form every time, so i won't be at the mercy of the browser deciding in which order to put my post variables.

      your issue of redirection though, that's an odd solution. i think the problem you're trying to solve is that CGI won't expose variables on the url (GET vars) during a POST. my solution to that is as follows:
      my $get_cgi = new CGI( $ENV{REDIRECT_QUERY_STRING} || $ENV{QUERY_STRIN +G}); my $cgi = new CGI; #will read ALL of STDIN (file uploads will take tim +e..) my $all_vars = \%{ $cgi->Vars, $get_cgi->Vars }; #GET vars will take p +recedence.
      the problem with this code is that if you have multiple fields with the same name you lose some values, but if you have that situation you need to build your own hash more explicitly, by calling $cgi->param on each variable until there's nothing left. but this does illustrate my concept: use 2 cgi instances, one for GET and one for POST and merge them

      thanks for your insight [id://daitau]

      It's not what you look like, when you're doin' what you’re doin'.
      It's what you’re doin' when you’re doin' what you look like you’re doin'!
           - Charles Wright & the Watts 103rd Street Rhythm Band