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

Esteemed Monks, I once more seek your illumination of the dark recesses of my scatty little brain.

I have a script which is to handle a 'download once' mechanism for delivering a file. Everything else is working fine, but I need to have the script deliver the file anonymously. The file is about 24mb. I have this snippet (based on CGI Programming with Perl, 2nd ed p 314):

use constant BUFFER_SIZE => 4096; my $file_directory = '/home/goldcal/www/files/'; my $buffer = ""; my $file = $file_directory . '/wlmlm401.exe'; print $cgi->header( -type => "application/exe", -expires => "-1d" +); #local *FILE; open (FILE, "<$file") or die "Cannot open file $file: $!"; binmode ( STDOUT ); binmode ( MY_FILE ); while( read( MY_FILE, $buffer, BUFFER_SIZE ) ) { print $buffer; } close FILE;
When I get to this point in my script the browser window goes blank, but I do not get the usual "Open or Save" dialogue fomr the browser. Any help greatly appreciated.

jdtoronto

Replies are listed 'Best First'.
Re: Getting CGI script to send a binary file
by saskaqueer (Friar) on Feb 08, 2005 at 06:26 UTC

    You've got mangled code, that's why. You're opening the file to the FILE handle, then binmoding and reading from MY_FILE, which, in this script, does not exist. As for sending an 'anonymous' file, follow the advice given by another monk below and just move the files out of the web server's document root.

    use CGI; my $q = CGI->new(); my $buf_size = 4096; my $file_root = '/home/goldcal/files'; # not in /www! open( my $fh, '<', "$file_root/wlmlm401.exe" ) or die("open failed: $!"); binmode($fh); print $q->header( -type => 'application/exe', -expires => '-1d' ); binmode(STDOUT); my $buf; while ( read($fh, $buf, $buf_size) ) { print $buf; }
Re: Getting CGI script to send a binary file
by prowler (Friar) on Feb 08, 2005 at 04:03 UTC

    Hi,

    In the CGI module (the 'CREATING A STANDARD HTTP HEADER' section) there is mention of the -attachment parameter:

    The -attachment parameter can be used to turn the page into an attachment. Instead of displaying the page, some browsers will prompt the user to save it to disk. The value of the argument is the suggested name for the saved file. In order for this to work, you may have to set the -type to "application/octet-stream".

    I hope this helps.

    Prowler
     - Spelling is a demanding task that requies you full attention.

Re: Getting CGI script to send a binary file
by jdtoronto (Prior) on Feb 08, 2005 at 04:57 UTC
    I have tried this (from cgi-multi-download):
    my $boundary_string = "\n" . "--End" . "\n"; my $end_of_data = "\n" . "--End--" . "\n"; my @file_list = ("wlmlm401.exe"); print <<EOH; Content-type: multipart/x-mixed\;boundary=End EOH foreach my $file ( @file_list ) { &send_file( $file ); print $boundary_string; } print $end_of_data; exit(0); sub send_file { my $file = $_[0]; if ( open (FILE, "< $file")) { print <<EOF; Content-type: application/octet-stream Content-disposition: attachment\; filename=$file EOF binmode FILE; print <FILE>; close (FILE); } else { print "Cannot open file $file!"} }
    With the file in the cgi-bin directory I click on the link to the script and I get a blank browser page with no 'save as' dialogue.

    So I tried merlyn's suggestion:

    $/ = undef; @ARGV = ("/home/goldcal/www/files/wlmlm401.exe"); print multipart_init(); while(<>) { print multipart_start("application/octet-stream"); print $_; print multipart_end(); } print multiplart_final(); ## UPDATE - THis part should not have been here, it is finger trouble +;) #print $cgi->header( -type => "application/octet-stream", # -expires => "-1d", # -attachment => "wlmlm401.exe", # );
    Which is as singularly unsuccesful.

    I am using Firefox 1.0 as my browser, but I did try it in IE and it works equally unwell.

    jdotoronto

      Um, in your first attempt, it looks like you forgot to do "binmode STDOUT"?

      And in the code you showed for trying merlyn's suggestion, it looks like you're printing the content header after printing the file (and you're not even doing "binmode" on the input, let alone on STDOUT).

      Was that just a flaky copy/paste into the post?

      update: Sorry, I'm probably missing the point completely here. In the first snippet, it's unclear to me why zentara (the original author) would do binmode on input and not on output -- I assumed that if you need binmode at all, you need it on both. As for the second snippet, merlyn was apparently assuming a OS environment where binmode was not necessary at all, and was using CGI.pm's functions for doing the content headers, boundaries, etc (so you shouldn't have been printing a header after that loop).

Re: Getting CGI script to send a binary file
by dws (Chancellor) on Feb 08, 2005 at 04:15 UTC

    ... but I need to have the script deliver the file anonymously.

    Could you clarify what you mean by "anonymously"?

      I don't want the user to see where the file is actually resident ( so they can't go back and download it again! ).

      jdotornto

        It should be sufficient if the file resides in a directory path that is not open to browsing by the web server (ie. lies outside the webroot path) or by an ftp server. So long as the outside path has permissions set such that the webserver user account can read files in that directory, the cgi script can fetch the file and send it to the client, but the client won't be able to get at the file any other way except through the cgi script.