#!/usr/bin/perl
# fs2web - make your filesystem accessible from the web
# (C) 2002-2004 by Amir Malik. All Rights Reserved.
# web: http://www.unoc.net/a/fs2web/
# email: a@unoc.net
# Copyright (c) 2002-2004 Amir Malik.
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# If you are using Apache, here is a sample configuration to make / point
# directly to the fs2web script so that all paths can be handled by it.
#
# Alias /images/ /home/webfs/www/images/
# ScriptAliasMatch ^/(.*) /home/webfs/cgi-bin/fs2web/$1
# ServerName webfs.us.unoc.net
#
#################### CONFIGURATION start ###############
# url to this script; no slash / at the end
$url = "http://webfs.us.unoc.net";
# stream the multimedia file (eg. RealServer), leave blank if none
$streamurl = "http://streaming-webfs.us.unoc.net:8000";
# allow downloads, 0 if no, but dir browsing is still possible
$download = 0;
# redirect to $baseurl if set to 1 to d/l file, 0 to just pipe it out
$redirect = 0;
# allow deletion of files (ANY file; BE CAREFUL HERE!!!)
$deletion = 0;
# allow uploads to ANY directory (BE CAREFUL HERE!!!)
$upload = 0;
# enable dynamic playlist generation (M3U format)
$dynamicm3u = 1;
# if $redirect is 1, then where are the files located locally
$basedir = "/storage/webfs";
# url of the path containing the downloadable files (required if redirect=1)
$baseurl = "http://yoursite/ftp";
# url to the images
$imageurl = "/images";
# file types we have images for
@filetypes = ("avi", "bmp", "gif", "html", "jpg", "mov", "mpeg", "swf", "txt", "zip");
# streamable file types
%streamtypes = qw(
avi 1
mpg 1
mpeg 1
mp3 1
m3u 0
pls 1
mp2 1
mov 1
rm 1
swf 1
ogg 1
);
# extensions that will always be allowed to download inline
%allowdown = qw(
txt 1
nfo 1
jpg 1
jpeg 1
gif 1
png 1
);
#################### CONFIGURATION end #################
# CGI.pm is EXTREMELY slow! comment out the following block to speed things up
# you could actually have 2 script; one main one and another for uploads...
if($upload && $ENV{'REQUEST_METHOD'} eq 'POST') {
use CGI qw/:standard/;
$CGI::DISABLE_UPLOADS = 0;
$CGI::POST_MAX = 1048576 * 100; # max 100 MB uploads
}
$VERSION = '1.1';
if($^O !~ /win32/i) {
$SIG{'ALRM'} = sub { exit(0) };
alarm(600); # max 10 min on Linux/UNIX systems, then exit script
}
$ENV{'HTTP_REFERER'} = $url if(!$ENV{'HTTP_REFERER'});
$ENV{'PATH_INFO'} =~ s/(\\\/|\/\\|\\\\|\/\/)//g;
$ENV{'PATH_INFO'} =~ s/\/$//;
$ENV{'PATH_INFO'} =~ s/^\///;
$vir_path = $ENV{'PATH_INFO'};
$vir_path =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$vir_path = '' if(length($vir_path) > 512);
my @in = split(/[&;]/,$ENV{'QUERY_STRING'});
foreach $i (0 .. $#in) {
$in[$i] =~ s/\+/ /g;
($key, $val) = split(/=/,$in[$i],2); # splits on the first =.
# Convert %XX from hex numbers to alphanumeric
$key =~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge;
$val =~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge;
# Associate key and value
$in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator
$in{$key} .= $val;
}
if($vir_path =~ /\.\./) {
print "Content-type: text/html\r\n\r\n";
print <<_EOF;
fs2web - Error
Error
The requested resource /$vir_path does not exist.
You are not allowed to access a higher level directory with ..
Return to $ENV{HTTP_REFERER}.
_EOF
exit(0);
} else {
$path = "$basedir/$vir_path";
}
# 2004.05.22 - dynamic m3u playlist generation
if( $in{'pls'} eq 'm3u' ) {
print "Content-Type: audio/x-mpegurl\r\nContent-Disposition: attachment; filename=fs2web-playlist.m3u\r\nCache-Control: none\r\n\r\n";
opendir(DIR, $path);
my @files = readdir(DIR);
closedir(DIR);
foreach my $file (@files) {
my $ext = lc(substr($file, rindex($file, "\.")+1));
if($ext ne 'm3u' && $streamtypes{$ext} == 1) {
$file =~ s/ /\%20/g;
$file =~ s/\#/%23/g;
# 2004.08.11 - support space in directory names
# 2004.09.03 - support # in file names
$vir_path =~ s/ /\%20/g;
$vir_path =~ s/\#/%23/g;
print "$streamurl/$vir_path/$file\n";
}
}
exit(0);
}
if(-d $path) {
$action = 'browse';
} elsif(-e $path) {
$action = 'get';
} else {
print "Content-type: text/html\r\nStatus: 404 Not Found\r\n\r\n";
print <<_EOF;
404 Not Found
404 Not Found
The requested resource /$vir_path does not exist.
Return to $ENV{HTTP_REFERER}.
_EOF
exit(0);
}
$thisdir = ($vir_path eq "") ? "" : "$vir_path/";
($action eq "browse") ? &browse : &get;
sub browse {
print "Content-type: text/html\r\n\r\n";
#$thisdir =~ s/ /\%20/g;
$uri = $vir_path;
print <<_EOF;
fs2web - Index of /$uri
Index of /$uri
_EOF
if($deletion && $in{d}) {
$in{d} =~ s/(\\|\/|\.\.)//g;
my $file = "$basedir/$vir_path/$in{d}";
if(unlink($file)) {
print "The file, $in{d} was successfully deleted.
";
} else {
print "
The file, $in{d} could not be deleted!
";
}
}
if($upload && $ENV{'REQUEST_METHOD'} eq 'POST') {
my $file = param('fileupload');
if($file) {
$file =~ m/^.*(\\|\/)(.*)/;
my $name = $2;
$name =~ s/(\\|\/|\.\.)//g;
my $msg;
if(open(X,">$basedir/$vir_path/$name")) {
binmode(X);
while(<$file>) {
print X;
}
close(X);
$msg = 'was successfully saved.';
} else {
$msg = 'could not be saved!';
}
print <<_EOF;
The file, $name, $msg
_EOF
}
}
opendir(BASE, $path);
@names = readdir(BASE);
closedir(BASE);
foreach $name (@names) {
$isdir = opendir(DIR, "$basedir/$thisdir$name");
if ($isdir == 1) {
push(@dirs, $name) unless ($name =~ /^\./);
} else {
push(@files, $name) unless ($name =~ /^\./);
}
closedir(DIR);
# some of the following code is based on CGIHK's ftpex
# see http://www.cgihk.com/ for more info
(undef, undef, undef, undef, undef, undef, undef,
$size, undef, $modified, undef, undef, undef) = stat("$basedir/$thisdir$name");
($min, $hr, $day, $mon, $yr) = (localtime($modified))[1,2,3,4,5];
$min = "0$min" unless $min >= 10;
$hr = "0$hr" unless $hr >= 10;
$day = "0$day" unless $day >= 10;
$mon = "0$mon" unless $mon >= 10;
$yr += 1900;
$mon++; # they are from 0 to 11 (man localtime)
$filesize{$name} = int((1023 + $size) / 1024) . " kb";
if($filesize{$name} > 1024) {
$filesize{$name} = int((1023 + $filesize{$name}) / 1024) . " Mb";
}
# 2004.05.13 - changed to conform to ISO 8601 date format
#$last_mod{$name} = "$day-$mon-$yr $hr:$min";
$last_mod{$name} = "$yr-$mon-$day $hr:$min";
}
# 2004.08.11 - moved here to support directory names with spaces
$thisdir =~ s/ /\%20/g;
print <<_EOF;
| Name |
Options |
Last Modified |
Size |
_EOF
if($vir_path ne "") {
$temp = $vir_path =~ tr#/#/#;
if ($temp > 0) {
$pardir = "$url/browse/".substr($vir_path, 0, rindex($vir_path, "/"));
} else {
$pardir = "";
}
print " Parent Directory\n";
}
print <<_EOF;
|
_EOF
foreach $dir (sort @dirs) {
$dir_url = $dir;
$dir_url =~ s/ /\%20/g;
print <<_EOF;
$dir
|
|
$last_mod{$dir} |
$filesize{$dir} |
_EOF
}
foreach $file (sort @files) {
# 2004.05.22 - hide dot-files
next if(substr($file, 1) eq '.');
$ext = substr($file, rindex($file, "\.")+1);
$type = "file";
foreach $filetype (@filetypes) {
if (lc($ext) eq $filetype) {$type = $filetype;}
}
$file_url = $file;
$file_url =~ s/ /\%20/g;
print <<_EOF;
|
_EOF
my $filetitle = $file;
$filetitle = substr($file, 0, 31) . '...' if(length($file) > 29);
my $delete = " | | ";
if($deletion) {
$delete = "delete | ";
}
if($streamurl && $ext =~ /(m3u|pls|mp3|mpg|mpeg|avi|mov|rm|swf)/i) {
print <<_EOF;
$filetitle (stream it!)
_EOF
} else {
print <<_EOF;
$filetitle
_EOF
}
print <<_EOF;
$delete
$last_mod{$file} |
$filesize{$file} |
_EOF
}
print "
\n";
# 2004.05.22 - playlist link
printf <<_EOF if($dynamicm3u);
Generate a playlist.
_EOF
print <<_EOF if($upload);
_EOF
print <<_EOF;
Powered by fs2web $VERSION
_EOF
}
sub get {
$download = 1 if("/$vir_path" eq '/robots.txt');
my $inline = 0;
my $inlinetype = 'text/plain';
my $inline_ext = substr($vir_path, rindex($vir_path, "\.")+1);
if($allowdown{$inline_ext} == 1) {
$download = 1;
$inline = 1;
if($inline_ext =~ /(gif|jpg|jpeg|png)/i) {
$inlinetype = "image/$1";
}
}
if(!$download) {
print "Content-Type: text/html\r\n\r\n";
print <<_EOF;
fs2web - Error
Error
Sorry, but the administrator has disabled file downloads!
Possible reason(s):
- Requested content not local: This server merely contains a listing of content. The actual
content resides on a dedicated file server.
Return to $ENV{HTTP_REFERER}.
_EOF
exit(0);
}
if($redirect) {
print "Content-Type: application/octet-stream\r\n";
print "Location: $baseurl/$vir_path\r\n";
print "URI: $baseurl/$vir_path\r\n\r\n";
} else {
if($inline) {
print "Content-Disposition: inline\r\nContent-Type: $inlinetype\r\n";
} else {
print "Content-Type: application/octet-stream\r\n";
}
# TODO: handle HTTP's Range parameter so that one can resume a download
#my $rset = int($ENV{'HTTP_RANGE'});
open(FILE,"$basedir/$vir_path");
binmode(FILE);
$size = -s "$basedir/$vir_path";
print "Content-Length: $size\r\n\r\n";
#if($rset && $rset < $size) {
# seek(FILE,$rset,0);
# $size = $size - $rset;
#}
while(!eof(FILE)) {
read(FILE, $x, 16384);
print $x;
}
close(FILE);
}
}