[Last Call] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 663
  • Last Modified:

URGENT!! Perl CGI file to serve requested files to clients without caching....250 points!!!

I am writing a cgi script in perl that will run on a unix server running ibm websphere's apache 1.3.  The script would be done except for this one error.  Everytime i try to download a file (for example, a windows application binary) with the script, i save it to my desktop and try to run it and i get an error saying it is not a valid win32 application!!!  If i attempt to download a text file or some sort, i get a blank file.  I've been troubleshooting this for several days and frankly I cannot find a solution!  CAN SOMEBODY HELP ME, PLEASE????

The URL i would use to fetch patchapp.exe is say:  http://server42/cgi-bin/fileserv.cgi?file=/patch/patchapp.exe

Also, if anyone knows how to call the cgi without using a "?" in the url, such as http://server42/cgi-bin/fileserv.cgi/patch/patchapp.exe, it would be nice to know :)

Here is my script:
-----------------------------------------------------------------------------------------------------

#!/usr/bin/perl -w

use CGI qw(:standard -debug);
#use CGI::Carp qw(fatalsToBrowser);
use File::Basename;
use strict;

my ($filedir, @fileholder, $filename, $basename, @suffixlist, $errmsg);
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);

$filename = param('file');
@suffixlist = (".class", ".dll", ".java", ".jar", ".exe", ".bat", ".ini");

###Location of files to patch
$filedir = "/vol/src";

if ($filename eq '') {  
  $errmsg = "You must specify a file to download.";
  printError();  
}
else
{
  # Get file details
  ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
 
  # Get file
  open(DLFILE, "$filedir$filename") || Error('open', 'file');
  binmode DLFILE, ":raw";
  @fileholder = <DLFILE>;  
 
  # Get filename  
  $basename = basename($filename, @suffixlist);

  # Download non-cached file
  print "Content-Type:application/x-download\n";
  print "Content-Length:$size\n";
  print "Content-Disposition:attachment;filename=$basename\n";
  print "Pragma: no-cache\n\n";
  close (DLFILE) || Error ('close', 'file');
  print @fileholder;
}

sub printError() {
  print "Content-type: text/html\n";
  print "Pragma: no-cache\n\n";  
  print "<h2>Custom Script Error!</h2>\n";
  #print "File: '" . __FILE__ . "'<br />\n";
  #print "Line: " . __LINE__ . "<br />\n";
  print "<hr /><br />\n";
  print $errmsg . "<br />\n";
  print "Mode = " . $mode . "<br />\n";
  print "UID = " . $uid . "<br />\n";
  print "GID = " . $gid . "<br />\n";
  print "Size = " . $size . " bytes<br />\n";
  exit;
}

sub Error {
   print "Content-type: text/html\n";
   print "Pragma: no-cache\n\n";
   #print "The server can't $_[0] the $_[1]: $! <br /><br />\n";
   print "$! <br /><br />\n";
   print "File = $filedir$filename<br />\n";
   print "Mode = " . $mode . "<br />\n";
   print "UID = " . $uid . "<br />\n";
   print "GID = " . $gid . "<br />\n";
   print "Size = " . $size . " bytes<br />\n";
   exit;
 }
0
rmaz410
Asked:
rmaz410
  • 7
  • 5
  • 4
  • +3
1 Solution
 
TintinCommented:
Try changing your MIME type to:

application/octet-stream
0
 
rmaz410Author Commented:
This still does not allow the complete and unharmed executables to be downloaded.  The reason I used application/x-download is so that the file is forced to be downloaded and not streamed.
0
 
rmaz410Author Commented:
NOTE:  I've tried the same script under Microsoft's IIS and it seems to work fine.  It's a different story under apache, apparently.
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
The--CaptainCommented:
>Also, if anyone knows how to call the cgi without using a "?" in the url...

Ummm, I think you would use the POST method rather than the GET method...

>such as http://server42/cgi-bin/fileserv.cgi/patch/patchapp.exe

Using the POST method, you'd have to change that to:

http://server42/cgi-bin/fileserv.cgi 

and put

/patch/patchapp.exe

as a submitted value in the form (maybe using a "hidden" field), AFAIK...

Cheers,
-Jon

0
 
mishagaleCommented:
I don't know exactly what your problem is, but I do know you can do exactly the same thing without a CGI wrapper, by using Apache directives something like this [untested]:

<Directory /vol/src>
Order deny,allow
Deny all
<Files ~ "\.(class|dll|java|jar|exe|bat|ini)$">
  Allow All
  Header add Content-Type application/x-download
  Header add Pragma: no-cache
</Files>
</Directory>

As for the problem with your CGI, if you are getting a blank text file, maybe your *.exe file are also empty, meaning that the script it failing to read the input file at all, and just returning no data.

Oops.

Having just worked all that out, I suddenly notice what is wrong with your code:

 
  # Get file details
  ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); #oops...
 
  # Get file
  open(DLFILE, "$filedir$filename") || Error('open', 'file'); #oops...
  binmode DLFILE, ":raw";
  @fileholder = <DLFILE>;  
 
  # Get filename  
####Hang on, isn't it a little late to be working out the filename before that we have been using for the previous 4 lines?####
$basename = basename($filename, @suffixlist);

0
 
FataquiCommented:
Hi

I was just looking over this script and it really is not well designed. Now granted it may do what you want, but it will not help you become a better programmer! Now you might say to your self this girl is being mean! Well that is not my motive for telling you this! What is my motive, is that I want to help you understand that Perl is a language that allows you to pretty much do a single thing over a hundred ways, but out of those one hundred ways there will be maybe ten that are really great to use.

The key to any program is the logic that the program uses to due the task at hand. If the logic is bad, then the design is truly flawed. I know when I was just learning dos, Perl, PHP, java, asp I would make some very bad design flaws and without others helping me understand what I was doing wrong I would probably still make those same type of errors today. Understand me I still make mistakes and will probably always make mistakes, but a lesson learned will make you all the more wiser next time!

Now let me explain some of the logic errors in this script!

# declaring variables ( lexically scoped)
my ($filedir, @fileholder, $filename, $basename, @suffixlist, $errmsg);
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);

Declaring or defining variables is great if you are going to use them, but declaring them before an action that might return false will only clutter the scope of your script! If the script was large this would cause problems....

Now lets look at what I mean....

# declaring variables ( before the if() block that could return false)

my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);


if ($filename eq '') {  
  $errmsg = "You must specify a file to download.";
  printError();  
}
else
{

Notice the if() block is below the declaration of the stat() -> 'list', so if the if ( $file eq undef ), in this case 'true', printError() is called, so if printError() is called there will not be any file stat() info, so why declare the stats() -> variable list, and also display stats -> info in the printError() function, when there want be any stats() -> info to begin with.


}
else
{


The ( } else { ) is not needed, because if ( $file eq undef ) returns true the printError() function will be called, and it ends with a exit ();, which makes the  ( } else { ) truly not needed!


  # Get file details
  ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

After the ( } else { )  the script converts the stats array into a list. I don't know why you don't just leave at an array @stats = stat($filename); and access the values that way. less script overhead!


Anyway the stat() -> 'stats array into a list', is still in the wrong place. It should be placed below the open () file function or put an error handler on it, because if you can not stat() the file because of a permission error you surely will not be able to open the file!

If the open() fails then the stat() will also fail, so calling Error(), and trying to display  stat -> info in the Error() function, will again be a waste of coding, because the stat() -> info, will not be available.

Last thing.....

binmode <- purely a windows thing, you should put a OS test, to only include it when the script is run under windows/os2....

example..........

Add after (below use strict;)....

require Config;

Then after the open();
if ( $Config::Config { 'osname' } =~ m/^(WINDOWS|DOS|OS2|MSWin|CYGWIN)/ )
{

      binmode ( DLFILE );

}





Fataqui!



0
 
mishagaleCommented:
Fataqui,
I actually have to take issue with some of your advice. Notably, regarding binmode, executing the function on an OS which is binary anyway has no ill effects, and so adding a lengthy check for OS is pointless, and clutters the code.
As for declaring variables, sticking my() statements all over the code is a pain to read, because you can't easily find declarations of variables. Declarations belong at the top of functions.

Just a little constructive criticism of your constructive criticism :)
0
 
rmaz410Author Commented:
>Ummm, I think you would use the POST method rather than the GET method...
First off, this does not change whether you use a "?" in the url or not.  These are methods of form processing essentially determing whether you show the details in the url path or not.

Fataqui, thanks for all the syntactical assistance.

Yet, this still does not aid me.  This is on an enterprise unix server, where I do not have access (nor the permission) to modify the apache.conf, therefore i cannot change directory settings in the conf.  When i regain access to my dev environment tomorrow, I will see if some of the suggestions will work.
0
 
mishagaleCommented:
Ahem.
You obviously didn't read the second part of my post, about how your code is in the wrong order.
0
 
FataquiCommented:
Hi mishagale

reply to... binmode

As it will not have any effect, is not the point! Many workshops I have been, I have heard Larry say "setting conditions is good a coding standard, because you clearly mark a function or action that is specific to a certain system, which allows the person using the script the ability to easily remove the function that may not be needed on their type of system, it also shows you have put every effect in to making your script not call unnecessary functions, per system usage!"

Final Note... ( using condition statements )

Look at some of the best Perl Packages (i.e.: CGI.PM) and you will see this done through the package, because it is a good coding standard to follow!


As for the unnecessary declaring of variables, and where they should be placed...

You can do it any way you like, but declaring variables that may never be used based on another condition is wasteful memory usage!


Hi rmaz410

Please forgive if you think I was being syntactical, for I never try to hurt anyone's pride....

0
 
cjmosCommented:
rmaz410
>Also, if anyone knows how to call the cgi without using a "?" in the url
I'm confused as to what you mean. How are you calling the script? (a link? another script?)

As for you're main problem, have you tried comparing the downloaded file contents (that don't work) with the those of the original (that does work, say the one you got with IIS)?

If there are any differences, can you post them here for us to take a look at?

Fataqui and mishagale, the discussion of the best perl syntax could last some time. Nations would rise and empires would crumble by the time your finished...
0
 
mishagaleCommented:
Thats what I love about syntax wars :)
0
 
rmaz410Author Commented:
cjmos,

Sorry, I should have said that more clearly.  I want to know if you can pass parameters to a cgi without using the "?" in the url.

For example, normally I would go http://servername/cgi-bin/patchapp.cgi?file=/yada/yada.exe
Whereas, I wonder if you can go http://servername/cgi-bin/patchapp.cgi/yada/yada.exe
0
 
cjmosCommented:
Right,

http://servername/cgi-bin/patchapp.cgi/yada/yada.exe 
definetly will never work, the browser will look for a "directory" called patchapp.cgi

Of course, in HTML you can display a link as whatever you want so:

<a href='http://servername/cgi-bin/patchapp.cgi?file=/yada/yada.exe'>http://servername/cgi-bin/patchapp.cgi/yada/yada.exe</a>
or
<a href='http://servername/cgi-bin/patchapp.cgi?file=/yada/yada.exe'>whatever</a>

(I apologise if you already know this and i've still got the wrong end of the stick)
0
 
mishagaleCommented:
rmaz,
Regarding the use of the '?' character, I know it can be done, but I'm afraid it involves modifying the apache configuration files.
0
 
rmaz410Author Commented:
Thanks, misgale.  I didn't have a chance to test the httpd.conf changes earlier as I did not have access to it.  It worked perfectly and much simpler!
0
 
The--CaptainCommented:
>Regarding the use of the '?' character, I know it can be done, but I'm afraid it involves modifying the apache configuration
>files

Can you enlighten us?  I poured through the CGI chapter in my "Apache: The Definitive Guide" and couldn't find a thing (of course, it's after work on a Friday, so I am perhaps likely to overlook it)

Cheers,
-Jon

0
 
mishagaleCommented:
I don't exactly know how this is implemented, I only know that i've seen it done; sourceforge uses it so that you can type something like http://www.sourceforge.net/project/gaim instead of http://www.sourceforge.net/project.php?project=gaim

I expect it is something to do with the <Directory> and/or AddHandler directives. If you need to pursue this issue, I suggest you open a new question.
0
 
The--CaptainCommented:
> I only know that i've seen it done; sourceforge uses it so that you can type something like
>http://www.sourceforge.net/project/gaim instead of http://www.sourceforge.net/project.php?project=gaim

OK, but do you have an example that actually works?  To my knowledge, sourceforge uses example.sourceforge.net if you want to access packge "example" instead of what you're talking about.  And guess what - if you go to gaim.sourceforge.net, it works.

>If you need to pursue this issue, I suggest you open a new question

LOL!  As an EE page editor (over in Networking), I think I have some understanding of the EE AUP - thanks for reminding me, tho.

In any case, this was asked in the original post - I know, it's supposed to be one question per post, but I didn't want to be a post nazi, and I thought the questions were at least related somewhat...

If you don't know the answer to the second question off the top of your head, it's OK.  I think we can all live with that.

Cheers,
-Jon

0
 
mishagaleCommented:
The usage of this feature in SF I was thinking of was the old (very old) 2.5 version of the code, which was/is public domain. I don't know whether they still use it in the spiffy new version of the site they run now. I don't work with that code anymore, since people in New Delhi can do it cheaper, but I will e-mail my old boss in the morning, and see if he knows.
0
 
TintinCommented:
To use a URL like:

http://www.example.com/cgi-bin/script.cgi/some/path

The /some/path info is contained in the PATH_INFO environment variable.
0
 
The--CaptainCommented:
>The /some/path info is contained in the PATH_INFO environment variable

That's ringing a (dusty) bell...  I think the poster wanted to know how to implement that behaviour on the server, not how to reference the arguments from a CGI (although you would eventually want to know that also).

>I don't work with that code anymore, since people in New Delhi can do it cheaper

Bummer - it's hard for me to get too mad at sourceforge because they provide a valuable service for free (which means constantly looking for ways to cut costs), but I think most companies that (overseas technical service outsourcing) do that are b@$!@rds.  It's as bad as those corporations whose "headquarters" are in the Bahamas or Caymans so that they pay no tax.  Needless to say, I try not to do business with such folk.  Gee, thanks, WTO.

Back on topic, I'm not arguing that this cannot be done - I could swear I used to see it quite a bit, but now I'm having problems finding (working) examples out there on the net.  

In any case, don't worry about it - I assume the lack of response from the original poster means s/he's happy.

Cheers,
-Jon

0

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

  • 7
  • 5
  • 4
  • +3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now