""" * ==================================================================== * Copyright (c) 2001 Jon Travis. All rights reserved. * See the file COPYING for information on usage and redistribution. * ==================================================================== """ import mod_snake import os import rfc822 import stat import string import sys import StringIO import time import traceback import threading from posix import chdir from request_file import RequestFile _myname = "SnakeCGI" Global_Lock = threading.Lock() class SnakeCGIPseudoFile(RequestFile): "A file which handles communication between our Python CGIs and \ the connection request object. This allows us to buffer up data \ which we think are headers, set them in the headers_out, and use \ the correct Apache API calls to send the response" def __init__(self, request): RequestFile.__init__(self, request) self.request = request self.inheaders = 1 self.headerstr = "" def write(self, str): # Since these are CGIs, we want to ensure that we write everything, # similar to how regular Python does it with files. # The catch here is that at the beginning, we want to filter things # which appear to be header information. When it looks like headers # have stopped, we send data regular stylee. if self.inheaders: self.headerstr = self.headerstr + str # Are we done with header-fetching? idx = string.find(self.headerstr, '\n\n') if(idx == -1): return dummyfile = StringIO.StringIO(self.headerstr[0:idx]) str = self.headerstr[idx + 2:] headers = rfc822.Message(dummyfile) headers_out = self.request.headers_out # Possibly merge headers? XXX for key in headers.keys(): headers_out[key] = headers[key] # Special case for a few headers, as we need to set data # in the request_rec based on these if string.lower(key) == 'content-type': self.request.content_type = headers[key] elif string.lower(key) == 'status': # Take the number, not the string rep self.request.status = int(string.split(headers[key])[0]) # XXX Also should allow status-line modifications? Yech self.inheaders = 0 self.request.send_http_header() RequestFile.write(self, str) class SnakeCGIObject: "An object which deals with the actual checking && loading of data \ from disk, and the namespace maintanence" def load(self, mtime): "load(): Load the CGIObject on disk into this object. The data \ is compiled and the file-mtime is saved for future usage" fil = open(self.filename, 'r') x = fil.read() self.compiledata = compile(x, self.filename, 'exec') self.mtime = mtime self.namespace = {} def is_outdated(self, mtime): "is_outdated(): Returns true of the object has a version more \ recent on disk, false if not." if mtime != self.mtime: return 1 return 0 def __init__(self, filename, mtime): self.filename = filename self.load(mtime) def run(self, request_rec): "run(request_rec): Runs the CGI, sending the output to the \ connection request. Returns exception info on failure, \ None on success" # Set the stdout of the CGI, and save ours pseudofile = SnakeCGIPseudoFile(request_rec) Global_Lock.acquire() start_dir = os.getcwd() chdir(os.path.dirname(self.filename)) saveout, savein = sys.stdout, sys.stdin sys.stdout, sys.stdin = pseudofile, pseudofile try: eval(self.compiledata, self.namespace, self.namespace) except: chdir(start_dir) sys.stdout, sys.stdin = saveout, savein Global_Lock.release() etype, value, tb = sys.exc_info() return traceback.format_exception(etype, value, tb, None) chdir(start_dir) sys.stdout, sys.stdin = saveout, savein Global_Lock.release() if pseudofile.inheaders: return "Malformed header section" return None class SnakeCGI: def cmd_SnakeCGILogFile(self, per_dir, per_svr, path): "Specify a LogFile for output errors from CGI scripts" per_svr['logfile'] = mod_snake.ap_server_root_relative(path) def content_handler(self, per_dir, per_svr, r): if r.handler != 'snakecgi': return mod_snake.DECLINED if not r.allow_options() & mod_snake.OPT_EXECCGI: self.write_log(per_svr, mod_snake.APLOG_CRIT, "OPT_EXECCGI off, when trying to execute %s" % ( r.uri)) return mod_snake.HTTP_FORBIDDEN filename = r.filename cgis = per_svr['cgis'] mtime = r.finfo[stat.ST_MTIME] if not cgis.has_key(filename) or cgis[filename].is_outdated(mtime): try: cgis[filename] = SnakeCGIObject(filename, mtime) except IOError: return mod_snake.HTTP_NOT_FOUND except: etype, value, tb = sys.exc_info() tback = traceback.format_exception(etype, value, tb, None) tot = string.join(tback[4:], '') self.write_log(per_svr, mod_snake.APLOG_CRIT, "Error loading script %s\n%s" % (filename, tot)) return mod_snake.HTTP_INTERNAL_SERVER_ERROR xres = r.setup_client_block(mod_snake.REQUEST_CHUNKED_ERROR) if xres: return xres # Allow apache to do client block determination. We allow # reads through the stdin read mechanism r.should_client_block() r.add_common_vars() r.add_cgi_vars() os.environ = {} for key, val in r.subprocess_env.items(): os.environ[key] = val os.environ['GATEWAY_INTERFACE'] = mod_snake.get_version() exc = cgis[filename].run(r) if exc != None: if type(exc) == type(""): self.write_log(per_svr, mod_snake.APLOG_CRIT, "Error running script %s: %s" % (filename, exc)) else: tot = string.join(exc[2:],'') self.write_log(per_svr, mod_snake.APLOG_CRIT, "Error running script %s\n%s" % (filename, tot)) return mod_snake.OK def write_log(self, svr_cfg, level, logstr): if svr_cfg['logfd']: svr_cfg['logfd'].write("[%s] - %s\n" % (time.ctime(time.time()), logstr)) svr_cfg['logfd'].flush() else: mod_snake.ap_log_error(level, svr_cfg['server'], logstr) def open_logs(self, svr_cfg, moduledata): if svr_cfg['logfile']: svr_cfg['logfd'] = open(svr_cfg['logfile'], "a") def create_svr_config(self, svr): return { 'server' : svr, 'cgis' : {}, 'logfile' : None, 'logfd' : None } def __init__(self, module): # Configuration directives that we support directives = { "SnakeCGILogFile" :(mod_snake.RSRC_CONF,mod_snake.TAKE1, self.cmd_SnakeCGILogFile), } module.add_directives(directives) hooks = { "open_logs" : self.open_logs, "create_svr_config" : self.create_svr_config, "content_handler" : self.content_handler, } for hook in hooks.keys(): module.add_hook(hook, hooks[hook])