Solved

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

Posted on 2004-04-15
22
650 Views
Last Modified: 2013-12-25
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
Comment
Question by:rmaz410
  • 7
  • 5
  • 4
  • +3
22 Comments
 
LVL 48

Expert Comment

by:Tintin
ID: 10837499
Try changing your MIME type to:

application/octet-stream
0
 
LVL 1

Author Comment

by:rmaz410
ID: 10837628
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
 
LVL 1

Author Comment

by:rmaz410
ID: 10837640
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
 
LVL 16

Expert Comment

by:The--Captain
ID: 10838495
>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
 
LVL 2

Accepted Solution

by:
mishagale earned 250 total points
ID: 10838815
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
 
LVL 2

Expert Comment

by:Fataqui
ID: 10838986
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
 
LVL 2

Expert Comment

by:mishagale
ID: 10839051
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
 
LVL 1

Author Comment

by:rmaz410
ID: 10839052
>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
 
LVL 2

Expert Comment

by:mishagale
ID: 10839121
Ahem.
You obviously didn't read the second part of my post, about how your code is in the wrong order.
0
 
LVL 2

Expert Comment

by:Fataqui
ID: 10841319
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
 
LVL 3

Expert Comment

by:cjmos
ID: 10842872
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
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 2

Expert Comment

by:mishagale
ID: 10843483
Thats what I love about syntax wars :)
0
 
LVL 1

Author Comment

by:rmaz410
ID: 10844689
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
 
LVL 3

Expert Comment

by:cjmos
ID: 10844980
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
 
LVL 2

Expert Comment

by:mishagale
ID: 10844990
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
 
LVL 1

Author Comment

by:rmaz410
ID: 10845575
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
 
LVL 16

Expert Comment

by:The--Captain
ID: 10846427
>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
 
LVL 2

Expert Comment

by:mishagale
ID: 10848588
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
 
LVL 16

Expert Comment

by:The--Captain
ID: 10855481
> 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
 
LVL 2

Expert Comment

by:mishagale
ID: 10863715
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
 
LVL 48

Expert Comment

by:Tintin
ID: 10864928
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
 
LVL 16

Expert Comment

by:The--Captain
ID: 10894469
>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

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Batch, VBS, and scripts in general are incredibly useful for repetitive tasks.  Some tasks can take a while to complete and it can be annoying to check back only to discover that your script finished 5 minutes ago.  Some scripts may complete nearly …
This article will show, step by step, how to integrate R code into a R Sweave document
The viewer will learn the basics of jQuery, including how to invoke it on a web page. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery.: (CODE)
The viewer will learn the basics of jQuery including how to code hide show and toggles. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery…

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now