Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

CGI - Creating Multipart Form with a File Download

by stefl (Acolyte)
on Mar 13, 2015 at 16:51 UTC ( [id://1119975]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks, I would like some advice on creating a multipart form that, at one part, downloads a csv file. When I run this, the contents of the csv file are displayed on the webpage, but there is no option to download it. I am trying to get a dialog box to enable the file download. I have managed this when the form is not multipart, but can't get it to work in this example. In addition to the code below, I have tried using CGI (not Safe) and I've used a redirect to a separate cgi script where a download box works. I am fairly new to Perl so I am open to suggestions for alternative ways to achieve this.
The code for a stripped-down example is below.
Thanks in advance!

#!/usr/bin/perl -w use strict; use CGI::Safe qw/:standard taint /; # Initial variables my @languages = ('English', 'Francais'); my @colours = ('Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', ' +Violet', 'Black'); my $fileDir = "/home/stef/outputs/"; my $filename = "test.csv"; ################################################################### # Basic Form Config # ################################################################### my $query = new CGI; print $query->header; print $query->start_html("Test Basic Form"); my $step = param('step') || 0; # Subroutines: part1() unless $step; part2() if $step == 2; part3() if $step == 3; print $query->end_html; ################################################################### # Subroutines # ################################################################### sub part1 { print $query->h2("Welcome to this basic form"); print $query->startform; print $query->hidden({-name => 'step', -value => 2, -override => 1}); print $query->popup_menu({-name => 'language', -values => \@languages, -default => \$languages[0], -label => "Please select language"}); print $query->submit({-value => "Continue"}); print $query->endform; } sub part2 { print $query->h2("Part Two"); print $query->startform; print $query->hidden({-name => 'step', -value => 3, -override => 1}); print $query->hidden({-name => 'language'}); print "<p>Please choose your favourite colour: </p>"; print $query->radio_group({-name => 'colour', -values => \@colours, -default => \$colours[0], -linebreak => 'true'}); print $query->submit({-value => "Continue"}); print $query->endform; } sub part3 { print $query->h2("Almost Done!"); print $query->startform; print $query->hidden({-name => 'step', -value => 4, -override => 1}); print $query->hidden({-name => 'language'}); print $query->hidden({-name => 'colour'}); print "<p>Press the Submit button to download csv file</p>"; print $query->submit({-value => "Continue"}); print $query->endform; download($filename) or error('An unknown error has occured.'); } sub download { my $file = $_[0] or return(0); my $path_to_files = "/home/stef/outputs/"; open(my $DLFILE, '<', "$path_to_files/$file") or return(0); print $query->header(-type => 'application/x-download', -attachment => $file, 'Content-length' => -s "$path_to_files/$file", ); binmode $DLFILE; print while <$DLFILE>; undef ($DLFILE); return(1); }

Replies are listed 'Best First'.
Re: CGI - Creating Multipart Form with a File Download
by hippo (Bishop) on Mar 13, 2015 at 17:29 UTC

    AFAIAA your requirement cannot be satisfied without resorting to something like AJAX. A single CGI script can either display a page or return content as a download but not both. This is because the content type is specified by the Content-Type header and this can only be given once per request. In your code above you give this twice (see the two calls of the header() method) and so the second one has little effect which is why your file contents appear in the web page.

    Either resort to AJAX or consider a different way of delivering the content to the user.

      Thanks very much for your reply! This might be a stupid question but is there any way to use more than one CGI script for a form? I've tried using a redirect (instead of calling the download subroutine) but that hasn't worked, but I'm wondering if there is some way of doing it.
      Otherwise I'll start learning about AJAX as you suggested.

        Those answers from off-brand monk are bonkers. Meta refresh has nothing to do with what you want. You’re trying to do a simple download with some prep work, not watch a long running process. If I have time I’ll try to fix up something for you but I’m swamped this weekend. Ajax could make it nicer and cleaner but isn’t necessary. Two CGIs might be a good way to go as well… Your original code looks like it is pretty close to workable/working but I haven’t tested it.

      *cough* meta-refresh is plain html
Re: CGI - Creating Multipart Form with a File Download
by Your Mother (Archbishop) on Mar 14, 2015 at 21:07 UTC

    Sorry, to leave no explanation but this is fully working with your original flow though I swapped your if/else tree for a dispatch hash of actions and subs. The form contents at the end are getting written to the download as well so you can verify what’s there.

    This kind of thing is okay but I wouldn’t call it best practices. Form state is often better saved in sessions (too complicated to do securely for this example) and the best forms redirect on successful POSTs… but that’s a big kettle of fish. What you have below does what you were trying to do and you seem to be thinking about security already so please always do and get a review before you put anything on a production box. Almost all beginner CGI/webcode is insecure on some level and sometimes—depending on what it touches and what the server allows—dangerously so .

    #!/usr/bin/env perl use strict; use warnings; no warnings "uninitialized"; use CGI ":standard"; my $csv_file = "/tmp/test.csv"; # Initial variables my @languages = ("English", "Francais"); my @colours = ("Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet", "Black"); my %dispatch = ( default => \&select_language, select_color => \&select_color, select_download => \&select_download, download => \&download ); my $action = param("action"); my $execute = $dispatch{$action} || $dispatch{default}; $execute->(); # print CGI::Dump(); # Uncomment for debugging help. exit; sub select_language { print header(), start_html(), h2("Welcome to this basic form"), startform(), hidden({ -name => "action", -value => "select_color", -override => 1 }), popup_menu({ -name => "language", -values => \@languages, -default => $languages[0] }), submit({-value => "Continue"}), endform; } sub select_color { print header(), start_html(), h2("Part Two"), startform, hidden({ -name => "action", -value => "select_download", -override => 1 }), hidden({ -name => "language" }), "<p>Please choose your favourite colour: </p>", radio_group({ -name => "colour", -values => \@colours, -default => $colours[0], -linebreak => "true" }), submit({-value => "Continue"}), endform; } sub select_download { print header(), start_html(), h2("Almost Done!"), startform(), hidden({-name => "action", -value => "download", -override => 1 }), hidden({-name => "language"}), hidden({-name => "colour"}), "<p>Download the CSV file if you dare.</p>", submit({-value => "Download"}), endform; } sub download { my $ok = open my $fh, "<", $csv_file; unless ( $ok ) # Ad hoc error handling, don't do this for real. { print header("text/plain"), "SORRY! $csv_file: $!"; exit 1; } print header(-type => "application/x-download", -attachment => "arbitrary-name.csv" ); for my $param ( param() ) { print join(",", $param, param($param)), $/; } print while <$fh>; }

      Thanks very much! This works perfectly! Really appreciate your help and the effort you've gone to. Brilliant, thanks again!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1119975]
Approved by graff
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (5)
As of 2024-04-24 21:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found