View Full Version : Change directory in CGI script
Tiradientes
02-11-2010, 08:30 PM
I have what I think should be a simple question (for those who know more than I):
My cgi is in a directory called "bin". I want it to point to directory "Count" which is located here:
. . bin
. . data
Count
. . htdocs
I gather that it means rewriting the line
open(A, "<$ab[1]");
but I am not sure how to do this. The in-line code on the HTML pages is this:
<SCRIPT language="JavaScript"
SRC="[homepage]/bin/gcount.cgi?0=Site"></SCRIPT>
The full script is at the end of this message. I hope someone cal be of assistance.
--------------------------------------
FULL SCRIPT
--------------------------------------
[CODE]
#!/usr/local/bin/perl
# Change the line abve to match the path to perl on your server
#=========================================
# Copyright July 2002 All Rights Reserved
# By Brent Maurer.
#=========================================
# DON'T CHANGE ANYTHING AFTER THIS LINE
#=========================================
# INSTRUCTIONS FOR USE
# See accompanying README.TXT file.
use CGI;
$Arguments = $ENV{'QUERY_STRING'};
$LOCK_EX = 2;
$LOCK_UN = 8;
#print "Content-type: text/html\n\n";
print "Content-type: application/x-javascript\n";
print "Pragma: no-cache\n\n";
@ab = split(/=/, $Arguments);
$value=0;
unless (open(A, $ab[1]))
{
print "document.write(\'err\')\;";
die ("File not found $ab[1]");
}
close(A);
open(A, "<$ab[1]");
$value = <A>;
chomp($value);
$value += 1;
if ($value < $ab[0]) { $value = $ab[0]; }
#--------------------------------------------------------------------
sub comify{
my $text = reverse $_[0];
$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
return scalar reverse $text;
}
#-----------------------------------------------------------
my $val = comify($value);
print "document.write(\'$val\')\;";
close(A);
open(A, ">$ab[1]");
flock (A, $LOCK_EX);
print A $value;
flock (A, $LOCK_UN);
close(A);
alienspaces
02-22-2010, 03:58 PM
Hi,
What you are attempting to do here with this borrowed script is very insecure.
You are basically allowing whoever calls your script to open a file on the host machine of their choosing.
The CGI script would be better written to not take arguments that allow a user to open any file on your system that has global read permissions:
Here is an example script you can drop in your /bin directory:
#!/usr/bin/perl
use strict;
use warnings;
use Carp qw(croak);
use English qw( -no_match_vars );
#
# CGI package for handling header output / html etc
#
use CGI;
my $cgi = new CGI;
#
# relative directory location
#
my $data_directory = '../count';
#
# counter filename
#
my $data_filename = 'site.txt';
#
# flock constants (file locking / not failsafe but better than nothing when not using a database)
#
my $LOCK_EX = 2;
my $LOCK_UN = 8;
#
# file filename of counter file to look for
#
my $full_filename = $data_directory . '/' . $data_filename;
#
# if the counter file exists
#
my ($fh, $count);
if ( -e $full_filename) {
#
# get current count
#
open $fh, '<', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for read: ' . $OS_ERROR;
$count = <$fh>;
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
#
# dump any newline from end of string
#
chomp $count;
#
# increment the count
#
$count++;
#
# if the counter file doesn't exist we'll start at 1
#
} else {
$count = 1;
}
#
# update count
#
open $fh, '>', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for write: ' . $OS_ERROR;
flock $fh, $LOCK_EX; # lock
print {$fh} $count;
flock $fh, $LOCK_UN; # unlock
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
print $cgi->header( -content_type => 'application/x-javascript') .
'document.write("' . $count . '");';
It assumes you have a directory called /count at the same level as your /bin directory. If you want your data directory to be somewhere else change this in the script.
Ensure the CGI has permissions 755 (rwxr-xr-x)
Ensure the /count directory has permissions (rwxrwxr-x) and has group permission s set to the same as the user your web server is running as, otherwise ensure it has (rwx) for all.
Your HTML changes to this
<script type="text/javascript" src="/bin/gcount.cgi"></script>
Notice it requires no arguments.
There are probably further improvements that could be made.
I just wanted to demonstrate a cleaner CGI script that wasn't opening a vulnerability to your web server :)
Tiradientes
02-22-2010, 08:43 PM
Thanks for the script. It works like a charm! There is only one small addition, and it is beyond my abilities to make it:
Instead of calling up the file "site.txt" I would like the filename to be a "variable", as it was in the former script where the cipher "0" marked the variable (bin/gcount.cgi?0=Site). This way, the same script can call up different files from the "../count" subdirectory (about 502 of them in all), without having to make a separate script for each page we want to count.
I hope this is not too complicated to do. Once again, I appreciate your kindness.
alienspaces
02-22-2010, 09:03 PM
No worries, here is a revised version.
The insecure part of the initial script you were using was that there was the ability to pass a filename as a query parameter. That is bad news :)
This version of the script uses the environment variable HTTP_REFERER which is the full URL of the page requesting the script.
The domain name etc is then removed leaving just the HTML / PHP whatever document name.
The datafile name will have this same name which will also make it easier for you to collect count stats from the file system if you want.
Just replace the script code with the code below and you are good to go.
Let me know if I can offer any other assistance.
#!/usr/bin/perl
use strict;
use warnings;
use Carp qw(croak);
use English qw( -no_match_vars );
#
# CGI package for handling header output / html etc
#
use CGI;
my $cgi = new CGI;
#
# relative directory location
#
my $data_directory = '../count';
#
# counter filename
#
my $data_filename = $ENV{'HTTP_REFERER'};
if ( $data_filename =~ /.*\/(.*?)$/sxmg ) {
$data_filename = $1;
}
else {
$data_filename = 'unknown_referer.txt';
}
#
# flock constants (file locking / not failsafe but better than nothing when not using a database)
#
my $LOCK_EX = 2;
my $LOCK_UN = 8;
#
# file filename of counter file to look for
#
my $full_filename = $data_directory . '/' . $data_filename;
#
# if the counter file exists
#
my ( $fh, $count );
if ( -e $full_filename ) {
#
# get current count
#
open $fh, '<', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for read: ' . $OS_ERROR;
$count = <$fh>;
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
#
# dump any newline from end of string
#
chomp $count;
#
# increment the count
#
$count++;
#
# if the counter file doesn't exist we'll start at 1
#
}
else {
$count = 1;
}
#
# update count
#
open $fh, '>', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for write: ' . $OS_ERROR;
flock $fh, $LOCK_EX; # lock
print {$fh} $count;
flock $fh, $LOCK_UN; # unlock
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
print $cgi->header( -content_type => 'application/x-javascript' ) . 'document.write("' . $count . '");';
edit: Sorry, pasted wrong script initially, make sure you have the new pasted version :/
Tiradientes
02-23-2010, 01:36 AM
Thanks for the quick response. What a speedy turn-around. But I am not sure what the HTML line is that calls up the program. I am guessing the one sent before will not work, namely:
[Code]
<script type="text/javascript" src="/bin/gcount.cgi"></script>
or will it?
alienspaces
02-23-2010, 02:05 AM
If all of your documents are unique individual documents, that is, they all have a different URL, the script will work with no changes to the HTML markup.
If your documents are all being delivered from the same URL I have a different suggestion.
Let me know which is your case.
(500+ documents is a lot of content!)
Tiradientes
02-23-2010, 02:55 AM
I think I was not clear. Each page has 2 counter HTML lines: (1) for "Site" which is present on all pages and gives the running count of the entire site. And (2) one for that particular page. These are all coded (e.g., "PB27" refers to "Pulications 27"). The sample lines I sent had PB6 on them.
So the file needs not just to add to the count of the whole site, but to create a small file for each individual page. I have 502 such files in the "data" subdirectory (what you called "../count"), but don't know how to access them.
The earlier program picked up the information at the end of the HTML, called up that file, added "1", and then closed it. Your rewriting it allows me to call up the file "Site" from the subdirectory and it works perfectly. But I wonder if we can add a variable that picks up the number in BOTH HTML lines and writes to BOTH the files, the Site and the one particular to that page.
I am afraid I explain myself badly, or at least note technically. Unfortunately, the new page is now yet up in the public domain, so I cannot have you check it, but if you go to a current page (http://www.nanzan-u.ac.jp/SHUBUNKEN/publications/) and drag your mouse over the space between WHAT'S NEW? and the bottom links, you will see 2 numbers, separated by a vertical line. The left is the "site" file and the right one is the page file, in thie case "PB0". The old script has the CGI and the data in the same subdirectory, but for security purposes, the computer department asked me to make a parallel subdirectory to the "bin". That was where you came in to save the day. Or at least half of it.
I hope this makes sense.
alienspaces
02-23-2010, 03:13 AM
With this version you will no longer need two SCRIPT lines.
Just include the single script line for the publication number as follows:
<script type="text/javascript" src="/cgi-bin/count.pl?0=PB1"></script>
Change PB1 to be whatever publication number it is (IE PB2, PB3 etc)
The script will check the format of the 0=PB??? parameter to check it is of the format PB followed by numbers.
This is a check to ensure no-one is trying to hack other files on your site.
I have set the data file directory to be "../data" as I am assuming you have a directory called data that has over 500 count files in it.
Review the script and comments.
Here is the new script:
#!/usr/bin/perl
use strict;
use warnings;
use Carp qw(croak);
use English qw( -no_match_vars );
#
# CGI package for handling header output / html etc
#
use CGI;
my $cgi = new CGI;
#
# relative directory location
#
my $data_directory = '../data';
#
# site count data file
#
my $site_data_file = 'Site';
#
# page count data file
#
# checks that parameter 0=???? is of the format PB999
#
my $page_data_file = $cgi->param( -name => '0' );
if ( $page_data_file !~ /PB\d+$/sxmg ) {
croak 'Illegal page parameter ' . $page_data_file;
}
#
# flock constants (file locking / not failsafe but better than nothing when not using a database)
#
my $LOCK_EX = 2;
my $LOCK_UN = 8;
#
# grab and update counts from site and page data files
#
my @counts = ();
foreach my $filename ( $site_data_file, $page_data_file ) {
my $full_filename = $data_directory . '/' . $filename;
#
# if the counter file exists
#
my ( $fh, $count );
if ( -e $full_filename ) {
#
# get current count
#
open $fh, '<', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for read: ' . $OS_ERROR;
$count = <$fh>;
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
#
# dump any newline from end of string
#
chomp $count;
#
# increment the count
#
$count++;
#
# if the counter file doesn't exist we'll start at 1
#
}
else {
$count = 1;
}
#
# update count
#
open $fh, '>', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for write: ' . $OS_ERROR;
flock $fh, $LOCK_EX; # lock
print {$fh} $count;
flock $fh, $LOCK_UN; # unlock
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
#
# comify
#
$count = reverse $count;
$count =~ s/(\d\d\d)(?=\d)/$1,/g;
$count = reverse $count;
push @counts, $count;
} ## end foreach my $filename ( $site_data_file...
#
# join site / page counts together
#
my $output_string = join '|', @counts;
#
# output javascript document write
#
print $cgi->header( -content_type => 'application/x-javascript' ) . 'document.write("' . $output_string . '");';
exit(0);
If you really need two SCRIPT lines I can change this quickly to cater for that, otherwise this script will update and return a count from the data file name "Site" and the data file name "PB???"
No worries on the confusion, I undertook the challenge initially because of the quality and safety of the initial script you posted :)
Let me know if this works for you now.
Tiradientes
02-23-2010, 04:04 AM
I have the feeling we are almost there. Right now, I am at home and cannot get into the university lan to check it out, but I see one small problem--once again, because I was not specific enough.
The names of the data files do not all begin with PB. They begin with a variety of things, like A, Sh, Wm, Bd, etc. So I suppose the ling would have to be adjusted. The longest name is 5 letters and ciphers.
I really do appreciate your patience. In another 12 hours, when I am back to the office, I will try it out right away.
By the way, including the "Site" right in the script and thus eliminating the second line was a stroke of genius.
alienspaces
02-23-2010, 04:07 AM
Change the line:
if ( $page_data_file !~ /PB\d+$/sxmg ) {
To:
if ( $page_data_file !~ /[A-Za-z]+\d+$/sxmg ) {
And you are good to go.
This will ensure the 0=???? parameter is "one or more uppercase or lowercase characters" followed by "one or more numbers"
Will that work for you?
Edit: Edited to add lowercase as well us uppercase initial letters.. :)
Tiradientes
02-23-2010, 07:47 PM
Well, I think you did it! There is, however, one small thing, the .1% left. What if I want ONLY the "Site" on a given page? If I simply add "Site" after the ?0, or if I leave the ?0 out altogether, nothing shows up. No big deal though. You have done a bang-up job!
alienspaces
02-23-2010, 08:29 PM
Updated script, will handle just sending back count from "Site" when no 0=???? parameter is specified.
#!/usr/bin/perl
use strict;
use warnings;
use Carp qw(croak);
use English qw( -no_match_vars );
#
# CGI package for handling header output / html etc
#
use CGI;
my $cgi = new CGI;
#
# relative directory location
#
my $data_directory = '../data';
#
# filenames we'll look for counts in
#
my @filenames;
#
# site count data file
#
my $site_data_file = 'Site';
push @filenames, $site_data_file;
#
# page count data file
#
# checks that parameter 0=???? is of the format PB999
#
my $page_data_file = $cgi->param( -name => '0' );
if (defined $page_data_file) {
if ( $page_data_file !~ /[a-zA-Z]+\d+$/sxmg ) {
croak 'Illegal page parameter ' . $page_data_file;
}
push @filenames, $page_data_file;
}
#
# flock constants (file locking / not failsafe but better than nothing when not using a database)
#
my $LOCK_EX = 2;
my $LOCK_UN = 8;
#
# grab and update counts from site and page data files
#
my @counts = ();
foreach my $filename (@filenames) {
my $full_filename = $data_directory . '/' . $filename;
#
# if the counter file exists
#
my ( $fh, $count );
if ( -e $full_filename ) {
#
# get current count
#
open $fh, '<', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for read: ' . $OS_ERROR;
$count = <$fh>;
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
#
# dump any newline from end of string
#
chomp $count;
#
# increment the count
#
$count++;
#
# if the counter file doesn't exist we'll start at 1
#
}
else {
$count = 1;
}
#
# update count
#
open $fh, '>', $full_filename or croak 'Failed to open counter file [' . $full_filename . '] for write: ' . $OS_ERROR;
flock $fh, $LOCK_EX; # lock
print {$fh} $count;
flock $fh, $LOCK_UN; # unlock
close $fh or croak 'Failed to close counter file ;' . $full_filename . ']: ' . $OS_ERROR;
#
# comify
#
$count = reverse $count;
$count =~ s/(\d\d\d)(?=\d)/$1,/g;
$count = reverse $count;
push @counts, $count;
} ## end foreach my $filename (@filenames)
#
# join site / page counts together
#
my $output_string = join '|', @counts;
#
# output javascript document write
#
print $cgi->header( -content_type => 'application/x-javascript' ) . 'document.write("' . $output_string . '");';
exit(0);
:)
Tiradientes
02-23-2010, 08:59 PM
When I leave out the "parameter" I get this, which returns nothing.
<SCRIPT language="JavaScript" SRC="http://nirc.nanzan-u.ac.jp/bin/gcount.cgi"></SCRIPT>
As soon as I add ?0=P6 (or any other file), it works. Have I left something out?
Tiradientes
02-23-2010, 09:17 PM
You told me to leave out the "parameters". When I type the following line, nothing happens. When I add something afterwards (say ?0=P6), it works fine for both counts. Have I left something out?
====================================
[Code]
<SCRIPT language="JavaScript" SRC="http://[domain]/bin/gcount.cgi"></SCRIPT>
alienspaces
02-24-2010, 12:40 AM
You know, the only thing I can think of right now is that you don't actually have the version of the script that I have pasted in the above post.
Tiradientes
02-25-2010, 10:44 PM
OK. I only had to clear the cache and everything works fine. Thanks so much.
We have one remaining problem before we put the new site on line: the computer office tells us we have to set up an SMTP site to receive mail from users on-line. I can find oddles of information on how to set up an SMTP on my computer, but what we really need is something on the server's end (/bin/?). The cgi file itself is very simple, and only one line is out of whack: $mailto = "usr/ucb/mail ". $mymail;
If you have better things to do, I will understand. You have been a godsend as it is...
...I just discovered that we have Net::SMTP already installed for use at "smtp.[our.domain]". The old cgi is this (we need to remove everything having to do with jcode.pl, since this is redunant with our Unicode utf-8 encoding.
[code]
#!/usr/local/bin/perl
#print "Content-type: text/html\n\n";
require 'jcode.pl';
read(STDIN, $str, $ENV{'CONTENT_LENGTH'});
@part = split(/&/,$str);
foreach $i (@part) {
($variable, $value) = split(/=/, $i);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
&jcode'convert(*value, 'jis');
$value =~ s/</</g;
$value =~ s/>/>/g;
$value =~ s/\r\n/\n/g;
$value =~ s/\r/\n/g;
$cgi{$variable} = $value;
}
$mymail = "nirc-afs\@nanzan-u.ac.jp";
$mailto = "/usr/ucb/mail " . $mymail;
$Title = $cgi{'Title'};
$personalname = $cgi{'PersonalName'};
$familyname = $cgi{'FamilyName'};
$oldaddress = $cgi{'OldAddress'};
$newaddress = $cgi{'NewAddress'};
$eMail = $cgi{'eMail'};
$change = $cgi{'Change'};
$remarks = $cgi{'Remarks'};
$mylocation = join("","Location: ",$cgi{'redirect'},"\n\n");
print (<<EOF);
EOF
open(MAIL, "| $mailto");
print MAIL (<<EOF);
*************** AFS change of address ***************
Name: $Title $personalname $familyname
e-Mail Address: $eMail
-----------------------------------------------------
Change address for: $change
Old Address: $oldaddress
New Address: $newaddress
-----------------------------------------------------
Remarks: $remarks
EOF
close(MAIL);
print $mylocation;
exit;
__________________________________________________ ____
I am thinking that, although setting up a Net::SMTP program for gatering data from fields may be simple fare for you, it is not for me. And therefore, I would like to offer to pay you something if you can see me through this. (We mave many forms, but only the FIELDS change, and that I can handle.)
Tiradientes
02-27-2010, 08:48 PM
__________________________________________________ ____
I am thinking that, although setting up a Net::SMTP program for gatering data from fields may be simple fare for you, it is not for me. And therefore, I would like to offer to pay you something if you can see me through this. (We mave many forms, but only the FIELDS change, and that I can handle.)
Powered by vBulletin™ Version 4.0.6 Copyright © 2010 vBulletin Solutions, Inc. All rights reserved.