# -*- Mode: Python; tab-width: 4 -*-

# This is a simple python server-side script handler.

# A note about performance: This is really only suited for 'fast'
# scripts: The script should generate its output quickly, since the
# whole web server will stall otherwise.  This doesn't mean you have
# to write 'fast code' or anything, it simply means that you shouldn't
# call any long-running code, [like say something that opens up an
# internet connection, or a database query that will hold up the
# server].  If you need this sort of feature, you can support it using
# the asynchronous I/O 'api' that the rest of medusa is built on.  [or
# you could probably use threads]

# Put your script into your web docs directory (like a cgi-bin
# script), make sure it has the correct extension [see the overridable
# script_handler.extension member below].
# 
# There's lots of things that can be done to tweak the restricted
# execution model.  Also, of course you could just use 'execfile'
# instead (this is now the default, see class variable
# script_handler.restricted)

import rexec
import regex
import string
import StringIO
import sys

import counter
import default_handler
import producers

split_path = default_handler.split_path
unquote    = default_handler.unquote

class script_handler:

	extension = 'mpy'
	restricted = 0

	script_regex = regex.compile (
		'.*/\([^/]+\.%s\)' % extension,
		regex.casefold
		)

	def __init__ (self, filesystem):
		self.filesystem = filesystem
		self.hits = counter.counter()
		self.exceptions = counter.counter()

	def match (self, request):
		[path, params, query, fragment] = request.split_uri()
		if self.script_regex.match (path) == len(path):
			return 1
		else:
			return 0

	def handle_request (self, request):
		
		[path, params, query, fragment] = split_path (request.uri)

		while path and path[0] == '/':
			path = path[1:]

		if '%' in path:
			path = unquote (path)

		if not self.filesystem.isfile (path):
			request.error (404)
			return
		else:

			self.hits.increment()

			request.script_filename = self.filesystem.translate (path)

			if request.command in ('put', 'post'):
				# look for a Content-Length header.
				cl = request.get_header ('content-length')
				length = int(cl)
				if not cl:
					request.error (411)
				else:
					collector (self, length, request)
			else:
				self.continue_request (
					request,
					StringIO.StringIO() # empty stdin
					)

	def continue_request (self, request, stdin):
		temp_files = stdin, StringIO.StringIO(), StringIO.StringIO()
		old_files = sys.stdin, sys.stdout, sys.stderr

		if self.restricted:
			r = rexec.RExec()

		try:
			sys.request = request
			sys.stdin, sys.stdout, sys.stderr = temp_files
			try:
				if self.restricted:
					r.s_execfile (request.script_filename)
				else:
					execfile (request.script_filename)
				request.reply_code = 200
			except:
				request.reply_code = 500
				self.exceptions.increment()
		finally:
			sys.stdin, sys.stdout, sys.stderr = old_files
			del sys.request

		i,o,e = temp_files

		if request.reply_code != 200:
			s = e.getvalue()
		else:
			s = o.getvalue()

		request['Content-Length'] = len(s)
		request.push (s)
		request.done()

	def status (self):
		return producer.simple_producer (
			'<li>Server-Side Script Handler'
			+ '<ul>'
			+ '  <li><b>Hits:</b> %s' % self.hits
			+ '  <li><b>Exceptions:</b> %s' % self.exceptions
			+ '</ul>'
			)


class persistent_script_handler:

	def __init__ (self):
		self.modules = {}
		self.hits = counter.counter()
		self.exceptions = counter.counter()

	def add_module (self, name, module):
		self.modules[name] = module

	def del_module (self, name):
		del self.modules[name]

	def match (self, request):
		[path, params, query, fragment] = request.split_uri()
		parts = string.split (path, '/')
		if (len(parts)>1) and self.modules.has_key (parts[1]):
			module = self.modules[parts[1]]
			request.module = module
			return 1
		else:
			return 0

	def handle_request (self, request):
		if request.command in ('put', 'post'):
			# look for a Content-Length header.
			cl = request.get_header ('content-length')
			length = int(cl)
			if not cl:
				request.error (411)
			else:
				collector (self, length, request)
		else:
			self.continue_request (request, StringIO.StringIO())

	def continue_request (self, request, input_data):
		temp_files = input_data, StringIO.StringIO(), StringIO.StringIO()
		old_files = sys.stdin, sys.stdout, sys.stderr

		try:
			sys.stdin, sys.stdout, sys.stderr = temp_files
			# provide a default
			request['Content-Type'] = 'text/html'
			try:
				request.module.main (request)
				request.reply_code = 200
			except:
				request.reply_code = 500
				self.exceptions.increment()
		finally:
			sys.stdin, sys.stdout, sys.stderr = old_files

		i,o,e = temp_files

		if request.reply_code != 200:
			s = e.getvalue()
		else:
			s = o.getvalue()

		request['Content-Length'] = len(s)
		request.push (s)
		request.done()

class collector:

	def __init__ (self, handler, length, request):
		self.handler = handler
		self.request = request
		self.request.collector = self
		self.request.channel.set_terminator (length)
		self.buffer = StringIO.StringIO()

	def collect_incoming_data (self, data):
		self.buffer.write (data)

	def found_terminator (self):
		self.buffer.seek(0)
		self.request.collector = None
		self.request.channel.set_terminator ('\r\n\r\n')
		self.handler.continue_request (
			self.request,
			self.buffer
			)