# -*- Mode: Python -*-

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


"""
Medusa was once distributed under a 'free for non-commercial use'
license, but in May of 2000 Sam Rushing changed the license to be
identical to the standard Python license at the time.  The standard
Python license has always applied to the core components of Medusa,
this change just frees up the rest of the system, including the http
server, ftp server, utilities, etc.  Medusa is therefore under the
following license:

==============================
Permission to use, copy, modify, and distribute this software and
its documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and
that both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Sam Rushing not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
==============================

Sam would like to take this opportunity to thank all of the folks who
supported Medusa over the years by purchasing commercial licenses.
"""

# 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)
#
#    Modified by RAC to use pyehtml_parser.py
#

import rexec
import re
import string
import StringIO
import sys

import counter
import default_handler
import producers

# added by rac
import os
import pyehtml_parser
import traceback
get_header = default_handler.get_header
HOST = default_handler.HOST

unquote    = default_handler.unquote

class script_handler:

    extension = 'py'
    restricted = 0

    script_regex = re.compile (
            r'.*/([^/]+\.%s)' % extension,
            re.IGNORECASE
            )

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

    def match (self, request):
        [path, params, query, fragment] = request.split_uri()
        m = self.script_regex.match (path)
        return (m and (m.end() == len(path)))



    def handle_request (self, request):
        """
        Traditional Apache cgi script; run as an embedded python script
        """
        self.dir = os.getcwd()
        
        [path, params, query, fragment] = request.split_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


        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:
            stdin = StringIO.StringIO()
            stdin.write(query)
            stdin.seek(0)
            self.continue_request (
                request,
                stdin
                )

    def continue_request (self, request, stdin):

        """
        print dir(request)
        print request.reply_headers
        request.push('')
        request.done()
        return
        """
        
        [path, params, query, fragment] = request.split_uri()
        userpath = self.filesystem.root
        filepath = request.script_filename
        host = get_header (HOST, request.header)

        namespace = {'request': request,
                     '__path__': userpath,
                     '__file__': filepath,
                     '__host__': host}

        python_path = string.join(sys.path, os.pathsep)
        serverName = host
        
        env = {"SERVER_SOFTWARE":   'Medusa/1.5',
               "SERVER_NAME":       host,
               "GATEWAY_INTERFACE": "CGI/1.1",
               "SERVER_PROTOCOL":   'HTTP/' + request.version,
               "SERVER_PORT":       `request.channel.socket.getsockname()[1]`,
               "REQUEST_METHOD":    request.command,
               "SCRIPT_NAME":       path,
               "SCRIPT_FILENAME":   request.script_filename,
               "REQUEST_URI":       request.uri,
               "PYTHONPATH" :       python_path
        }

        env['USER_DIR'] = userpath

        #client = request.getClient()
        #if client is not None:
        #    env['REMOTE_HOST'] = client

        ip = request.channel.socket.getpeername()[0]
        if ip is not None:
            env['REMOTE_ADDR'] = ip

        if query:
            env['QUERY_STRING'] = query[1:]
        else:
            env['QUERY_STRING'] = ''

        # Propogate HTTP headers
        for header in request.header:
            title = string.lower(header[:string.find(header,':')])
            envname = string.upper(string.replace(title, '-', '_'))
            if title not in ('content-type', 'content-length'):
                envname = "HTTP_" + envname
            env[envname] = header

        os.environ.update(env)


        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, namespace, namespace)
                else:
                    execfile (request.script_filename, namespace, namespace)
                request.reply_code = 200
            except:
                traceback.print_exc(file=sys.stderr)
                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:
            print e.getvalue()

        s = o.getvalue()

        lines = s.split('\n')
        found = 0
        parse = 0
        while lines:
            line = string.strip(lines[0])
            if line.find(':') == -1:
                if found:
                    # remove the trailing newline that
                    # separates the header and body
                    del lines[0]
                break
            found = 1
            del lines[0]
            cmd, val = line.split(': ')
            request.reply_headers[cmd] = val
            if string.lower(cmd) == 'content-type':
                if string.lower(val) == 'text/html':
                    parse = 1
        s = string.join(lines, '\n')


        if parse:
            fakefile = StringIO.StringIO()
            fakefile.write(s)
            fakefile.seek(0)
            s = pyehtml_parser.parse_file (request, userpath, fakefile).getvalue()

        request['Content-Length'] = len(s)
        request.push (s)
        os.chdir(self.dir)
        request.done()



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



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
                )
