#!/usr/bin/env python
# fileserver.py - File Server v1.1
# Copyright (C) 2002 Michael Leonhard
# www.tamale.net

import socket, select, SocketServer, os, os.path, time, md5
from stat import *

# CHANGE THESE OPTIONS
SERVEDIR = "/home/mike/client/files"	# the directory to serve files from
PORT = 12360				# the TCP port to listen on

class RequestHandler( SocketServer.StreamRequestHandler ):
	def log( self, message ):
		print time.time(), self.client_address, message
		return
	
	def neg( self, message ):
		# respond to the client
		self.wfile.write( "- File unavailable\n" )
		self.log( message )
	
	def dofilecheck( self, words ):
		# check that filename exists and there are no directories specified
		(head, tail) = os.path.split( words[1] )
		if len( head ) > 0 or len( tail ) == 0: return self.neg( "sent invalid filename" )
		
		# make full path
		path = os.path.join( self.server.servedir, tail )
		
		# normalize the path (collapse ./ and ../ entries)
		path = os.path.normpath( path )
		
		# double check directory
		if os.path.dirname( path ) != self.server.servedir: return self.neg( "!!path mangling: " + string.join( ["%02X"%ord(x) for x in path ] ) + " : " +string.join( ["%02X"%ord(x) for x in words[1] ] ) )
		
		# stat the file
		self.log( """statting \"%s\" """ % path)
		try: finfo = os.stat( path )
		except OSError: return self.neg( "file does not exist" )
		
		# check file
		if not S_ISREG(finfo[ST_MODE]): return self.neg( "not a file" )
		
		size = finfo[ST_SIZE]
		modtime = finfo[ST_MTIME]
		
		# open the file
		self.log( "opening" )
		try: self.fh = file( path, "r" )
		except IOError: return self.neg( "file cannot be read" )
		
		return (size, modtime)
	
	def STAT( self, words ):
		ret = self.dofilecheck( words )
		if ret == None: return
		else: (size, modtime) = ret
		
		# generate the checksum
		hash = md5.new()
		while 1:
			# read a block
			data = self.fh.read( 65536 )
			
			# no more data
			if len( data ) == 0: break
			
			# hash it
			hash.update( data )
		
		# close the file
		self.fh.close()
		self.fh = None
		
		# respond to the client
		statstring = "+ %s %s %s\n" % (str( size ), str( modtime ), hash.hexdigest() )
		self.wfile.write( statstring )
		return self.log( statstring )
	
	def GET( self, words ):
		ret = self.dofilecheck( words )
		if ret == None: return
		else: (size, modtime) = ret
		
		# respond to the client
		self.wfile.write( "+ %s\n" % str( size ) )
		self.log( "+ " + str( size ) )
		
		# send the file
		while 1:
			# read a block
			data = self.fh.read( 65536 )
			
			# no more data
			if len( data ) == 0: break
			
			# send it
			self.wfile.write( data )
			#time.sleep( 2 )
		
		# close the file
		self.fh.close()
		self.fh = None
		
		return self.log( "sent." )
	
	def QUIT( self ):
		# disable incoming and outgoing data
		self.request.shutdown( 2 )
		# discard all incoming queued data
		while self.request.recv( 65536 ): pass
		# close the socket
		self.request.close()
		# all done
		self.log( "quit" )
	
	def handle( self ):
		self.log( "connected" )
		
		# handle of open file
		self.fh = None
		
		# catch socket errors and close the thread gracefully
		try:
		# greeting
			self.wfile.write( "FILER1 1231047100281\n" )
			
			while 1:
				# read the message
				try: data = self.rfile.readline( 128 )
				except socket.error: return self.log( "disconnected" )
				
				# split message into lines
				lines = data.splitlines()
				
				# check that there is only one line
				if len( lines ) != 1: return self.log( "malformed message" )
				
				# split message into two words
				words = lines[0].split( " ", 2 )
				
				# check that there is one word
				if len( words ) < 1 or len( words[0] ) < 1: return self.log( "no keyword" )
				
				# check keyword of message
				if words[0] == "GET": self.GET( words )
				elif words[0] == "STAT": self.STAT( words )
				elif words[0] == "QUIT": return self.QUIT()
				else: return self.log( "sent unknown keyword" )
		
		# record the client disconnection
		except socket.error:
			# close open file
			if self.fh != None: self.fh.close()
			# done
			return self.log( "disconnected" )

#class ThreadingTCPServer( SocketServer.ThreadingTCPServer ):
#	pass

# the following code is used for debugging
# bind to any port between 12360 and 12370
#for p in range( 12360, 12370 ):
#	try:
#		server = SocketServer.ThreadingTCPServer( ('', p), RequestHandler )
#		print "bound to", p
#		break
#	except socket.error: pass

server = SocketServer.ThreadingTCPServer( ('', PORT), RequestHandler )
server.servedir = SERVEDIR
server.serve_forever()

# end of fileserver.py
