#!/usr/bin/perl -w ############################################################################### # Meteor # An HTTP server for the 2.0 web # Copyright (c) 2006 contributing authors # # Subscriber.pm # # Description: # Cache and serve static documents # ############################################################################### # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # For more information visit www.meteorserver.org # ############################################################################### package Meteor::Document; ############################################################################### # Configuration ############################################################################### use strict; our %Documents=(); ############################################################################### # Class methods ############################################################################### sub serveFileToClient { my $class=shift; my $relPath=shift; my $client=shift; &::syslog('debug',"Meteor::Document: Request received for '%s'",$relPath); my $doc=$class->documentForPath($relPath); unless(defined($doc)) { $class->emitHeaderToClient($client,'404 Not Found'); $::Statistics->{'documents_not_found'}++; &::syslog('info','', 'document', $relPath, 0, 404 ); return undef; } $doc->serveTo($client); $::Statistics->{'documents_served'}++; &::syslog('info','', 'document', $relPath, $doc->{'size'}, 200 ); $doc; } sub emitHeaderToClient { my $self=shift; my $client=shift; my $status=shift; my $length=shift; my $contenttype=shift; $length = 0 unless ($length); $contenttype = "text/html" unless ($contenttype); my $header="HTTP/1.1 ".$status."\r\nServer: ".$::PGM."\r\nContent-Type: ".$contenttype."; charset=utf-8\r\nPragma: no-cache\r\nCache-Control: no-cache, no-store, must-revalidate\r\nExpires: Thu, 1 Jan 1970 00:00:00 GMT\r\nContent-length: ".$length."\r\n\r\n"; $client->write($header); } sub documentForPath { my $class=shift; my $relPath=shift; unless(exists($Documents{$relPath})) { my $path=$class->pathToAbsolute($relPath); return undef unless(defined($path)); my $doc=$class->newDocument($path); return undef unless(defined($doc)); $Documents{$relPath}=$doc; } $Documents{$relPath}; } sub clearDocuments { %Documents=(); } sub pathToAbsolute { my $class=shift; my $relPath=shift; # Don't serve documents unless SubscriberDocumentRoot is set unless(exists($::CONF{'SubscriberDocumentRoot'}) && $::CONF{'SubscriberDocumentRoot'} ne '' && $::CONF{'SubscriberDocumentRoot'} ne '/' ) { return undef; } # # Verify if name is legal # # Strip leading and trailing slashes $relPath=~s/^[\/]*//; $relPath=~s/[\/]*$//; # NOTE: With the right strings the code below triggers a bug in # perl (5.8.6 currently) that will result in messages like # # Attempt to free unreferenced scalar # # and an eventual crash. # # So it was replaced with the more naive code following this # commented out code. # # # split into path components # my @pathComponents=split(/[\/]+/,$relPath); # # # Check components # foreach (@pathComponents) # { # # Very strict: We only allow alphanumeric characters, dash and # # underscore, followed by any number of extensions that also # # only allow the above characters. # unless(/^[a-z0-9\-\_][a-z0-9\-\_\.]*$/i) # { # &::syslog('debug', # "Meteor::Document: Rejecting path '%s' due to invalid component '%s'", # $relPath,$_ # ); # # return undef; # } # } # #my $path=$::CONF{'SubscriberDocumentRoot'}.'/'.join('/',@pathComponents); # # Check for all alphanumeric or dash, underscore, dot and slash # unless($relPath=~/^[a-z0-9\-\_\.\/]*$/i) { &::syslog('debug', "Meteor::Document: Rejecting path '%s' due to invalid characters", $relPath ); return undef; } # # Don't allow '..' # if(index($relPath,'..')>=0) { &::syslog('debug', "Meteor::Document: Rejecting path '%s' due to invalid sequence '..'", $relPath ); return undef; } my $path=$::CONF{'SubscriberDocumentRoot'}.'/'.$relPath; # If it is a directory, append DirectoryIndex config value $path.='/'.$::CONF{'DirectoryIndex'} if(-d $path); # Verify file is readable return undef unless(-r $path); $path; } ############################################################################### # Factory methods ############################################################################### sub new { # # Create a new empty instance # my $class=shift; my $obj={}; bless($obj,$class); } sub newDocument { # # new instance from new server connection # my $self=shift->new(); my $path=shift; $self->{'path'}=$path; # Read file { local $/; # enable localized slurp mode open(IN,$path) or return undef; $self->{'document'}=; close(IN); } $self->{'size'}=length($self->{'document'}); $self; } ############################################################################### # Instance methods ############################################################################### sub serveTo { my $self=shift; my $client=shift; my $ct = "text/html"; if ($self->{'path'} =~/\.(js)$/) { $ct = "text/javascript"; } $self->emitHeaderToClient($client,'200 OK',$self->{'size'}, $ct); $client->write($self->{'document'}); } sub path { shift->{'path'}; } 1; ############################################################################EOF