For Programmers: Free Programming Magazines  


Home > Archive > PERL CGI Beginners > May 2004 > Streaming a file to a remote user









You are viewing an archived Text-only version of the thread. To view this thread in it's original format and/or if you want to reply to this thread please [click here]

 

Author Streaming a file to a remote user
Jason Price

2004-05-22, 11:30 am

I'm trying to figure out a way to stream a file to a remote user, and be =
able to determine that the download completed, so I can delete the file =
being streamed. I may be missing an obvious way to do this, so I'm =
hoping you all can help me out.

Here's the sequence of events I'm after:

1. User clicks URL pointing to perl script.
2. Perl script pulls file out of LDAP, and writes it temporarily to =
disk.
3. File is uudecoded on the disk.
4. Decoded file is streamed to user, who saves the file locally.
5. Once it is determined that the download is complete, the temp file =
on the web server is deleted.


Steps 4 and 5 are what I'm really after - the rest is relatively =
straight forward. For security reasons, we don't want the file to =
remain on the web server filesystem any longer than it has to, which is =
why I need to delete it after being downloaded. Emailing the file is =
not an option, also due to security reasons.

Can anyone help me out with a way to do this?

Thanks.

Jason
Shaun Fryer

2004-05-22, 11:30 am

> 4. Decoded file is streamed to user, who saves the file locally.
> Jason


Below I've provided a starting point to answer your 4th question anyway.
It doesn't have a proper strict style or complete error reporting, but
it does work (last I checked) and should get you started.

--
=====================
Shaun Fryer
=====================
http://sourcery.ca/
ph: 905-529-0591
=====================

#!/usr/bin/perl

########################################
########################################

To download a file via a GET request
http://yourhost.tld/path/to/thisScr.../to/fileYouWant
########################################
########################################


# to restrict which hosts can access this script
# enter your full or partial IP address below
# or comment it out to allow unrestricted access

$remote_ip = '192.168.0.';

########################################
########################################

use CGI;
$q = new CGI;
if ($ENV{REMOTE_ADDR} =~ /^$remote_ip/) {
if ($q->param('f')) {
$file = $q->param('f');
if (!-e "$file") {
print $q->header,
$q->start_html,
$file." doesn't exist!",
$q->end_html;
} elsif (!-r "$file") {
$me = `whoami`;
print $q->header,
$q->start_html,
$file." isn't readable by ".$me."!",
$q->end_html;
} else {
$size = (stat $file)[7];
$file_name = $file;
$file_name =~ s/.*\///;
select STDOUT;
$| = 1;
print "Content-Type: application\/force-download\n",
"Content-Disposition: attachment\; filename=$file_name\n",
"Content-Length: $size\n",
"Content-Description: sfryer's file downloader\n\n";
open(OUT,"<$file");
binmode OUT if (-B "$file");
$block_size = (stat OUT)[11];
$block_size = 16384 unless ($block_size);
while ($length = sysread OUT, $buffer, $block_size) {
unless (defined $length) { next if $! =~ /^Interrupted/; }
$written = 0;
$offset = 0;
while ($length) { $written = syswrite STDOUT, $buffer, $length, $offset; }
$len -= $written;
$offset += $written;
}
close(OUT);
}
}
}
exit 0;
########################################
########################################

Charles K. Clarkson

2004-05-22, 11:31 am

On Friday, March 26, 2004 12:50 AM, Shaun Fryer <sfryer@sourcery.ca> wrote:
:
: > 4. Decoded file is streamed to user, who saves the file locally.
: > Jason

[snip]
: #!/usr/bin/perl
:
: ########################################
######################
: To download a file via a GET request
: http://yourhost.tld/path/to/thisScr.../to/fileYouWant
: ########################################
######################
:
: # to restrict which hosts can access this script
: # enter your full or partial IP address below
: # or comment it out to allow unrestricted access
:
: $remote_ip = '192.168.0.';
:
: ########################################
######################
: use CGI;
: $q = new CGI;
: if ($ENV{REMOTE_ADDR} =~ /^$remote_ip/) {

The periods inside $remote_ip will match any character, not
just periods. Better safe than sorry.

if ($ENV{REMOTE_ADDR} =~ /^\Q$remote_ip\E/) {


: if ($q->param('f')) {
: $file = $q->param('f');
: if (!-e "$file") {

Why is $file in quotes?


: print $q->header,
: $q->start_html,
: $file." doesn't exist!",
: $q->end_html;
: } elsif (!-r "$file") {

Why is $file in quotes?


: $me = `whoami`;
: print $q->header,
: $q->start_html,
: $file." isn't readable by ".$me."!",

None of these variables are in quotes?!?


: $q->end_html;
: } else {
: $size = (stat $file)[7];

Is there a possibility that (stat $file)[7] will return undef?


: $file_name = $file;

Hmmm. $file is not in quotes here ...


: $file_name =~ s/.*\///;
: select STDOUT;
: $| = 1;
: print "Content-Type: application\/force-download\n",
: "Content-Disposition: attachment\;
: filename=$file_name\n",
: "Content-Length: $size\n",
: "Content-Description: sfryer's file
: downloader\n\n";

Shame! Shame! Shame! Shame.

You loaded the darn thing -- use it:

print
$q->header(
-Content_Type => q|application/force-download|,
-Content_Disposition => qq|attachment; filename=$file_name|,
-Content_Length => $size,
-Content_Description => q|sfryer's file downloader|,
);




: open(OUT,"<$file");

Always, always check the status of an open. Always!


open OUT, "<$file" or die qq|Cannot open "$file": $!|;


: binmode OUT if (-B "$file");

Why is $file in quotes. What do you wish to accomplish
that this will not:

binmode OUT if -B $file;



: $block_size = (stat OUT)[11];
: $block_size = 16384 unless ($block_size);

$block_size = (stat OUT)[11] || 13684;


Okay, this is the biggie!


: while ($length = sysread OUT, $buffer, $block_size) {
: unless (defined $length) { next if $! =~
: /^Interrupted/; }
: $written = 0;
: $offset = 0;
: while ($length) { $written = syswrite STDOUT,
: $buffer, $length, $offset; }
: $len -= $written;

$len is only used once. Did you mean $length ?!?

: $offset += $written;

The next time $offset is used, it is always set to 0.
So this statement is meaningless.

: }
: close(OUT);
: }
: }
: }
: exit 0;

Did I mention that you shouldn't quote variables unnecessarily? :)


Here's my (untested) attempt.

#!/usr/bin/perl

use strict;
use warnings;

use CGI;
my $q = new CGI;

my $remote_ip = '192.168.0.';
my $file = $q->param('f');

$q->redirect( 'http://www.some-domain.com/cgi-form.html' )
unless $file && $ENV{REMOTE_ADDR} =~ /^\Q$remote_ip\E/;


unless ( -e $file ) {
print $q->header,
$q->start_html,
qq|$file doesn't exist!|,
$q->end_html;
exit;
}

unless ( -r $file ) {
my $me = `whoami`;
print $q->header,
$q->start_html,
$file, qq| isn't readable by "$me"!|,
$q->end_html;
exit;
}

my $file_name = $file;
$file_name =~ s/.*\///;

select STDOUT;
$| = 1;

print
$q->header(
-Content_Type => q|application/force-download|,
-Content_Disposition => qq|attachment; filename=$file_name|,
-Content_Length => (stat $file)[7],
-Content_Description => q|sfryer's file downloader|,
);

open OUT, "<$file" or die qq|Cannot open "$file": $!|;

binmode OUT if -B $file;

my $block_size = (stat OUT)[11] || 16384;

while ( my $length = sysread OUT, my $buffer, $block_size ) {
next unless defined $length;

my $offset = 0;
while ( $length ) {
my $written = syswrite STDOUT, $buffer, $length, $offset;
$length -= $written;
$offset += $written;
}
}

close OUT;


HTH,

Charles K. Clarkson
--
Mobile Homes Specialist
254 968-8328

Shaun Fryer

2004-05-22, 11:31 am

Hi Jason,

Good advice. Keep in mind that it was just something to get him
started. I don't have time to write the software for him in
entirety unless I'm getting paid, so I just sent off some code
I've had sitting around in partial completion in my dev/ folder
for a couple years now. It works fine for quickly downloading
files to my internal workstation. Certainly there are many
discrepancies. It wasn't presented it as production ready code.

Hopefully between us and the others here, we've provided enough
that he can take it up where we left off. Sound advice and
constructive criticism are always welcome.

Health,
Shaun

> <snip>
> The periods inside $remote_ip will match any character, not
> just periods. Better safe than sorry.
>
> if ($ENV{REMOTE_ADDR} =~ /^\Q$remote_ip\E/) {
> </snip>


> <snip>
> Here's my (untested) attempt.
> </snip>


Glad I could provide you with something to build on. ;)

Jason Price

2004-05-22, 11:32 am

Having progressed further with this project, I've discovered that we'd =
rather not allow the user to save the streamed file locally - just allow =
them to open it via the stream. Is it possible to eliminate or gray-out =
the "Save" button on the "file download" window presented by the =
browser? I'd like to just leave "open", "cancel", and "more info" as =
option. This may not be possible, but thought I'd check...

Thanks.

Jason

-----Original Message-----
From: Shaun Fryer [mailto:sfryer@sourcery.ca]
Sent: Saturday, March 27, 2004 1:12 PM
To: beginners-cgi@perl.org
Subject: Re: Streaming a file to a remote user


> 4. Decoded file is streamed to user, who saves the file locally.
> Jason


Below I've provided a starting point to answer your 4th question anyway.
It doesn't have a proper strict style or complete error reporting, but
it does work (last I checked) and should get you started.

--=20
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
3D=3D=3D=3D=3D=3D=3D=3D
Shaun Fryer
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
3D=3D=3D=3D=3D=3D=3D=3D
http://sourcery.ca/
ph: 905-529-0591
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
3D=3D=3D=3D=3D=3D=3D=3D

#!/usr/bin/perl

########################################
#################################=
#######
To download a file via a GET request
http://yourhost.tld/path/to/thisScr.../to/fileYouWant
########################################
#################################=
#######

# to restrict which hosts can access this script
# enter your full or partial IP address below
# or comment it out to allow unrestricted access

$remote_ip =3D '192.168.0.';=20

########################################
#################################=
#######
use CGI;
$q =3D new CGI;
if ($ENV{REMOTE_ADDR} =3D~ /^$remote_ip/) {
if ($q->param('f')) {
$file =3D $q->param('f');
if (!-e "$file") {
print $q->header,
$q->start_html,
$file." doesn't exist!",
$q->end_html;
} elsif (!-r "$file") {
$me =3D `whoami`;
print $q->header,
$q->start_html,
$file." isn't readable by ".$me."!",
$q->end_html;
} else {
$size =3D (stat $file)[7];
$file_name =3D $file;
$file_name =3D~ s/.*\///;
select STDOUT;
$| =3D 1;
print "Content-Type: application\/force-download\n",
"Content-Disposition: attachment\; =
filename=3D$file_name\n",
"Content-Length: $size\n",
"Content-Description: sfryer's file downloader\n\n";
open(OUT,"<$file");
binmode OUT if (-B "$file");
$block_size =3D (stat OUT)[11];
$block_size =3D 16384 unless ($block_size);
while ($length =3D sysread OUT, $buffer, $block_size) {
unless (defined $length) { next if $! =3D~ =
/^Interrupted/; }
$written =3D 0;
$offset =3D 0;
while ($length) { $written =3D syswrite STDOUT, $buffer, =
$length, $offset; }
$len -=3D $written;
$offset +=3D $written;
}
close(OUT);
}
}
}
exit 0;
########################################
#################################=
#######

--=20
To unsubscribe, e-mail: beginners-cgi-unsubscribe@perl.org
For additional commands, e-mail: beginners-cgi-help@perl.org
<http://learn.perl.org/> <http://learn.perl.org/first-response>


Sponsored Links







Also available: Server administration forum archive | Web Design forum archive | Software forum archive | Hardware reviews archive

Copyright 2008 codecomments.com