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

Hello All,

I have a funky file upload function problem -

yes, I've looked all over the hallowed halls for an answer, but none has presented itself. Putting security issuses aside (this is for an internal, password protected section of a site) here is the jist of the script, its pretty cut and dry:

sub _file_upload { my $self = shift; no strict 'refs'; foreach(@_) { my $file = $_; if ($file ne "") { my $fileName = $file; $fileName =~ s!^.*(\\|\/)!!; open (OUTFILE, ">$img_dir/$fileName") or die("can't write ima +ge file '$img_dir/$fileName': $!"); while (my $bytesread = read($file, my $buffer, 1024)) { print OUTFILE $buffer; } close (OUTFILE); } } }

the above code works well... IF the filename only has numbers in it!, like 1.gif, 2.gif, 3.gif.. ect. It won't work for filenames like photo.gif, things like that.

What is going on here? I've traced this myself to a few things:

First off, the function is screwing up in the while loop. File is created, nothing is written and the file is closed.

I don't understand where/how $file is being read. Its gotta be in the /tmp directory. Maybe something's getting messed up when I'm passing the filehandle from a script that uses the CGI.pm to get the saved form information to the Perl Module (the code above) that houses the _file_upload() function.

the above code works 100% correctly on one server that I test, which is runing suEXEC, but is otherwise a similar setup to another server that's only correctly saving files that are numbers.

Anyways, why would the name of the file make any difference?

 

-justin simoni
!skazat!

Replies are listed 'Best First'.
Re: YAF(ile)U(ploading)Q(uestion)
by fpi (Monk) on Mar 15, 2001 at 05:19 UTC
    You're asking where/how $file is being read: If you read the CGI.pm docs, CGI.pm saves the uploaded file to a temp file and returns $file as a filehandle to that temp file. The docs also say that if you use $file as a scalar, it returns the filename of the original file. Which is why you should be able to pull out the filename to $fileName, and then continue to use $file when you read the file, assuming the filehandle is being created and passed correctly.

    question, which may or may not be related: are you accessing this subroutine from within the your module or from outside your module? if from within, how are you calling this function? I'm just wondering why you need to pass the object (i.e. my $self=shift;) if you never need $self. Unless, of course, this is the routine you are accessing from outside the module. It's just that I think that perl writing convention recommends that a subroutine with a name that begins with an underscore (i.e. "_file_upload") indicates an internal routine. Just a convention, though, not a rule. But it may confuse people who are used to the convention, plus I'm just trying to make sure you're not unexpectedly chopping off the first parameter you pass to this routine (depending on how you are calling it).

    Doesn't answer your question, but I was hoping to help with the insight.

      You're right, I'm calling this via inside a OO Perl Module, its a simple wrapper to the DB_File module, but it also does a bunch of other things. This is for a simple web-based database admin screen. Info from a form is sent to a script, that calls this Module kinda like

      my $input = CGI -> new(); my $image = $input -> param('image'); # some more form fields fetched through param()... my $info = new -> JustinDb(); $info ->savethis(-name => 'image', -value => $image);

      the _file_upload() is always internal. This module is one of many, and the module itself 'knows' what kind of form field the info was sent from. Yes I'm tricky :) That part is checking out fine. it looks to see if this particular info is handed to it from a fileupload field, and if it is, saves the file to the $img_dir variable.

      I've been fiddling with this some more, and filenames called 1photo.gif will work, but photo.gif will not work! this is incredibly weird. This is my problem and I'm still scratchin my head.

       

      -justin simoni
      !skazat!

Re: YAF(ile)U(ploading)Q(uestion)
by stephen (Priest) on Mar 15, 2001 at 04:51 UTC

    A couple of things could be going wrong.

    Bizarrely-worded description of CGI upload and filehandles deleted. See Re (tilly) 2: YAF(ile)U(ploading)Q(uestion) for details.

    I'm assuming that you're calling your function like so:

    use CGI; my $query = CGI->new(); &_upload_file($query->param('my_upload_field'));
    It would be best to use the upload() function on CGI, like so.
    use CGI; my $query = CGI->new(); my $uploaded_filehandle = $query->upload('my_upload_field') or print_error_page("Sorry, you must upload a file..."); &_upload_file( $uploaded_filehandle );

    That way, you'll pass only a filehandle, and not have to worry about the vagaries of packages. The only problem is that it will not preserve the original name.

    So, you might want to write your function like so:

    use CGI; use File::Copy; ## in main... my $query = CGI->new(); my $uploaded_filehandle = $query->upload('my_upload_field') or print_error_page("Sorry, you must upload a file..."); my $filename = $query->param('my_upload_field'); &_upload_file( $uploaded_filehandle, $filename ); sub _upload_file { my ($uploaded_fh, $orig_filename) = @_; my $dest_filename = $orig_filename; $dest_filename =~ s{ ^.* ( \\ | \/ ) }{}x; copy($uploaded_fh, $dest_filename) or die "Couldn't copy file to ' +$dest_filename': $!; stopped"; }

    Normally, you would NEVER want to allow user input to determine the name of a filename in a CGI script, but since it's an internal application we'll let it go this once.

    Finally, you might add

    use CGI::Carp 'fatalsToBrowser';
    at the top of your script.. that'll print out that error message.

    Update: Previously, I didn't understand the question thoroughly enough before posting... my apologies.
    Additional update:tilly pointed out that my wording was so confusing as to be altogether wrong.

    stephen

      Actually CGI does not name the file what it is named in the filename. (At least it shouldn't be, and doesn't do it instead.) Instead what it does is returns a glob. If you use the glob as a string, you will see the name of the file that they are trying to upload. If you use it as a filehandle, you will be reading from the file.

      And yes, I was confused for quite a while about how it worked. :-)

Re: YAF(ile)U(ploading)Q(uestion)
by tcf22 (Priest) on Mar 15, 2001 at 05:58 UTC
    I'm not sure why the file name is affecting the upload but Give this a try.
    Hope it helps.
    use CGI; $cgi=new CGI; $file=$cgi->param('file_field'); $newName=&FileUpload($file); sub FileUpload{ my $FileName=shift @_; my $tmpdir ="/your/path" my $bufferSize=4096; my $limit=10000000;#File Size Limit my ($debug, $FILE, $file, $size, $tmppath, $line, $buffer); $size = 0; if (length($FileName) != 0){ $| = 1; $FILE = $FileName; ($file = $FILE) =~ s/^.*?([^\\\/]+)$/$1/; $tmppath = "$tmpdir\\$file"; open (OUT, ">$tmppath") or print "Error opening $tmppa +th: $!<br>\n"; binmode OUT; while(($line = read ($FileName, $buffer, $bufferSize)) + && ($size < $limit)){ print OUT $buffer; $size += $line; if ($size > $limit){ $err_flag = 1; } } close OUT; $| = 0; $FileName = $file; } else{##Empty File $FileName = ""; } #Upload Error if ($err_flag == 1){ unlink ($tmppath); $FileName = ""; } $FileName; }
    Tom
Re: YAF(ile)U(ploading)Q(uestion)
by skazat (Chaplain) on Mar 16, 2001 at 03:32 UTC

    here's an update.

    I'm going to stick this under 'Weird Perl Oddity' until I figure out what's going on. My guess is that passing the magic variable that acts like a filehandle in some circumstances, and a plain scalar in others to an outside module that then passes it to a private subroutine is way too much to ask for and to keep the magic. I still don't understand why a filename uploaded that has a number in its name works (1photo.gif), but a filename that has only letters (hello.gif) doesn't. I'm thinking it's something to do with the tmp filename, but I may be way off on this.

    The way I solved this problem is to stick the fileupload subroutine in the script that gets called, instead of calling an OO object from the script that does the file upload stuff. Why this makes a difference, I have no idea *shrugs* I created a new CGI object just for the fileupload function, since I read that mixing the OO and regular styles of writing with the CGI.pm can muck up magic thingies.

    This resolution totally breaks my abstaction of a pretty darn intricate system (probably the hardest I've done) and plunks in code that may need to be repeated in more than one script since its now outside the OO Module.

    *grumble grumble grumble*

    Thought everyone would appreciate a resolution, as many times I tried to find an answer to my questions, I find instead an unfinished thread.

    I'm still looking for a way to put a file upload script in an outside module and maybe some wisdom on why it makes a difference where this code is anyways. I'm thinking that I need to somehow pass the filehandle to the outside (in the module) , private upload function, instead of a magic variable.

    thanks, everyone for your help

     

    -justin simoni
    !skazat!