Do you really want to keep your files in a safe place and not run the risk of having people pluck them off of your webserver? Then you should take 3 steps: cheers!
sub DownloadFile { # $filepath is the directory # $filename is the name of the file my ($filepath,$filename) = @_; chdir($filepath) || return(0); my $filesize = -s $filename; # print full header print "Content-disposition: inline; filename=$filename\n"; print "Content-Length: $filesize\n"; print "Content-Type: application/octet-stream\n\n"; # open in binmode open(READ,$filename) || die; binmode READ; # stream it out binmode STDOUT; while (<READ>) { print; } close(READ); # should always return true return(1); }

Replies are listed 'Best First'.
RE: Download, don't redirect.
by Marburg (Novice) on Apr 28, 2000 at 20:52 UTC

    I see what you are doing ... not giving away information about the root directory, etc. details of your webserver.

    Also a small point ... two actually: I never use die if possible in CGI scripts - I prefer to use the carp module or or some other technique. When opening files, that may be accessed by multiple web server processes launching a perl script, I flock() the file for safety.

    Regards, Marburg (john.keating@may.ie)

      As a matter of fact, I have no idea of why I put die() instead of return(0)... The way I call this sub is by saying
      FileDownload('/myfiles','ball.tar.gz') or Errors('Could not start download.');
      so your comment on die() makes perfect sense...

      thanks!
RE: Download, don't redirect.
by KM (Priest) on May 20, 2000 at 03:06 UTC
    One flaw I see here is you are not in taint mode. I'm not completely clear why you think that way is any better or safer (on its own) than using Location (or CGI.pm's redirect() method). In any case, without taint checking in your example, you are left open to ../../../../../etc/passwd being passed as the filename.

    You mentioned that you attempt to do this when _you_ parse QUERY_STRING, but I wonder why you are doing this instead of using CGI.pm or CGI_Lite.

      You're absolutely right. Why re-invent the wheel, right? Well, at the time I wrote the above, the wheel in question (CGI.pm) hadn't been written yet! ;)

      I guess that just goes to show how old this snippet is! But, I've found use for it on more that one occasion... The way I used to parse the query string used to be (and please, please, please don't try this at home kids!) was:
      sub ReadParse { read(STDIN, $buf, $ENV{'CONTENT_LENGTH'}); @li = (split(/&/, $buf), split(/&/, $ENV{'QUERY_STRING'})); foreach my $input (@li) { $input =~ tr/+/ /; $input =~ s/%(..)/pack("C", hex($1))/eg; $input =~ s/\.\.\///g; ($name, $val) = split(/=/, $input); $name =~ tr/A-Z/a-z/; $in{$name}=$val; } }
      Someone might even find THAT snippet of some interest... But as you can see from the above, the security risk of getting ../ in your file name was caught. Only God knows what other security risks could be involved nowadays!

      Thanks for the words of advice.
RE: Download, don't redirect.
by turnstep (Parson) on Apr 27, 2000 at 06:08 UTC
    What do you mean by people "plucking your files" away? What exactly does this protect against? I don't understand...
      With the location method, your files' urls are as good as public. Supposing that you only want users with a registration code (for a private distribution site perhaps?) to be able to access the file, or more simply, you want to maintain track of what files are being download via an application (not the webserver log) this would be a way of keeping files absolutely OUT of user direct access.

      I had this method running on Oracle's support site in Brazil where only users that pay for the service can download patches, docs, etc... Once the user has been authenticated, cookies get set, and the permission level on a file has to be previously set PER CLIENT so that they can download the file.

      The previous way they had this was by setting the permissions on the webserver via .htaccess (or the equivalent since Oracle Application Server doesn't support .htaccess), but the clients were trading off URLs, so once s/he logged in all he had to do was download the file since authentication was not per file / per client based.

      I hope that was better than example than just "plucking off the server"... :o)

      (In Oracle's specific case, all the files are on the database, being read off as BLOBs, but the snippet I posted is intended for files laying around the filesystem somewhere...)

        I think I understand now. Instead of giving out a URL, you just want to give out the data. So even though you need to be authenticated before recieving the "Location", you can still pass on the URL that is returned to your friends... Still, I would not call them as "good as public" - it depends on the users integrity, no?

RE: Download, don't redirect.
by httptech (Chaplain) on Apr 30, 2000 at 21:06 UTC
    Of course, it goes without saying that you should restrict the file/path if your program obtains the filename from the remote user, so that they can't request silly things like /../../../../etc/passwd
      I usually do that sort of checking when I parse the query_string. It seems to be a bad habbit closing down security later down the line when you can do it right off the bat when you receive your data. Think something looks suspicious? Chop it off regardless of what you'll be doing later...
      $str =~ s/\.\.\///g;