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

Hi Monks,

I'm writing a script that sits on a webserver, and does all it's output through CGI. When a user selects an option to download a particular file (call it file.txt), I want a "Save As" dialog box to come up in their browser, rather than having their browser simply display the text. I've tried the following:
print "Content-Disposition:attachment;filename=file.txt\n"; print "Content-type: text/plain\n\n";
But that simply display the text in the browser. I've also tried:
print "Content-Disposition:attachment;filename=file.txt\n"; print "Content-type: application/octet-stream\n\n";
But that seems to force the Windows2000 users to save the file as file.txt.exe (they can modify the file.txt part, but not the .exe part). Not having the most patient users in the world, I'd really like to have it saved simply as file.txt. I Suppose I could let the browser display the text and have them do "file"->"save", but here again, they're impatient and want things to go more smoothly.

Any suggestions? Thanks!

Replies are listed 'Best First'.
(wil) Re: File Download from CGI Script
by wil (Priest) on Jun 27, 2002 at 15:18 UTC
    You can not force a browser to download your file. The Web protocols were designed to identify, via MIME Content-Type headers, what sort of content a data stream has, but not specify exactly what to do with it. Various HTML constructs ware designed to suggest, but not to force.

    The only thing you can do is send an application/octet-stream; and hope that their browser will interpret this as a file for download. Many people configure their browsers in many different ways so there is no sure method. After all, the end decision rests with the browser/user - and thank goodnes for that.

    If you really need to make sure that the file gets downloaded, you can try compressing/archiving the file in some way. Or you can try other methods; mainly a scripting language that might be able to do this for you.

    You can always encourage the user to make use of browser features to save a file to disk, such as right-clicking in most browsers.

    Cheers

    - wil
Re: File Download from CGI Script
by emilford (Friar) on Jun 27, 2002 at 15:12 UTC
    I'm not sure how to get a Save As dialog box for a .txt file, but you could change everything to .zip and just have the user unzip the file to get to the .txt file. Browsers won't be able to display the .zip file, so they'll more than likely just get a Save As dialog box. That's what I do on my site. Just a suggestion.
Re: File Download from CGI Script
by gryphon (Abbot) on Jun 27, 2002 at 16:57 UTC

    Greetings Anonymous,

    It's fairly impossible to make sure that every browser "downloads" a file off the Web rather than rendering it since by design the browser is expected to be smart about such things. (Of course, in reality, everything is downloaded. So we're talking about displaying the file in the browser or presenting the user with the option to save the file.) What we can do is set it up for the most likely successful situation and hope the users have smart browsers.

    First off, you're dealing with CGI via Perl, so therefore you should always use the CGI module. At this point, it's just a matter of picking desired behaviors. I've noticed that I tend to get better results if I've been specific about the type of data I want to transmit. For example, here's how I push out Excel documents.

    use CGI; my $query = new CGI; print $query->header( -type => 'application/vnd.ms-excel', -Content_Disposition => 'attachment' );

    This seems to work in both Navigator, IE, and Mozilla. In IE, the browser tries to be super smart and render the content inside the browser. Netscape and Mozilla simply ask the user what /s{0,1}he/ wants to do (save or export to other application).

    For your specific situation, I'd use "octet-stream" without any mention of the filename. Perhaps not even have the content disposition included.

    print $query->header( -type => 'application/octet-stream', -Content_Disposition => "attachment" );

    However, keep in mind that a text file is about as basic to the Web as an HTML file. So there will be browsers out there that do completely unexpected things based on your code. Be sure to always test in all browsers you want to support.

    -gryphon
    code('Perl') || die;

Re: File Download from CGI Script
by Aristotle (Chancellor) on Jun 27, 2002 at 17:42 UTC
    There is no surefire way to do this. It is the browser that decides; when it thinks it can display a file, it will do so. From the server's end, there's no way you can enforce this. What you can tell your users is to "Right-click the link, then click Save As...".

    Makeshifts last the longest.

Re: File Download from CGI Script
by Joost (Canon) on Jul 09, 2002 at 14:37 UTC
    print "Content-Disposition:attachment;filename=file.txt\n"; print "Content-type: application/octet-stream\n\n";

    Should work on at least some recent browsers. How about also appending the filename to the script url? Something like this:

    <a href="script.cgi/filename.txt?downloadid=1">download the txt file</ +a>

    Any rate, these kind of tricks are not really very well supported or standard, so expect to disappoint some users.

    -- Joost downtime n. The period during which a system is error-free and immune from user input.
Re: File Download from CGI Script
by TorontoJim (Beadle) on Mar 19, 2016 at 21:35 UTC

    I know this is a really old post, but figured I'd post anyways. I've always had good luck with the following. It works for binary and for ASCII files.

    File to be downloaded is passed to the script as part of the query str +ing: $qs{filename} my $docspath = "/home/acctname/public_html/userdata/user_" . $cust +omer{myid} . '/dlfiles'; if(-d "$docspath") { opendir(READ, "$docspath"); my @files = readdir(READ); closedir(READ); my %relative_mimetype = ( 'gif' => 'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'png' => 'image/png', 'bmp' => 'image/bmp', 'doc' => 'application/msword', 'docx' => 'application/x-msword', 'pps' => 'application/mspowerpoint', 'ppt' => 'application/powerpoint', 'pdf' => 'application/pdf', 'txt' => 'text/plain' ); for my $dlfile (@files) { unless($dlfile =~ /^(\.|\..)$/) { if($dlfile =~ /^ **regex to test file name to make s +ure they are downloading what they are allowed do** /) { my @nameparts = split(/\_/, $dlfile); my $marker = shift @nameparts; my $dl_name = join('_', @nameparts); my ($filename, $ext) = split(/\./, $dl_name); if($dl_name eq $qs{filename}) { my @extension = split(/\./, $nameparts[$#namep +arts]); my $mimetype = $relative_mimetype{ lc($extensi +on[$#extension]) }; $mimetype ||= "application/octet-stream"; print "Pragma: no-cache\nContent-Type: $mimety +pe\nContent-Disposition: attachment; filename=$dl_name;\n\n"; if(open(FILE, "<$docspath/$dlfile")) { if(-B "<$docspath/$dlfile") { binmode(STDOUT); } while(<FILE>) { print $_; } close FILE; exit; } else { print "Pragma: no-cache\nContent-type: tex +t/html\n\Unable to open file. Please contact the Site Adminsitrator. +(ErrNo.3)\n\n"; } } } } } }