#!/usr/bin/python -u
""" Embed Python code in HTML, parse and execute it. """
# Aquarius <aquarius@kryogenix.org>
# http://www.kryogenix.org/code/castalian/
# Some code and ideas taken from PMZ, http://pmz.sf.net/
# v1.0
import sys,cgi,string,re,traceback,os,Cookie,urllib
from UserDict import UserDict
# Classes
class server_obj:
def __init__(self):
pass
def urlencode(self,s):
return urllib.quote(s)
class QueryStringDict(UserDict):
"A dictionary-a-like to be the QueryString"
def has_keys(self,*keys):
"has_key() for multiple keys"
for key in keys:
if not self.has_key(key): return 0
return 1
class request_obj:
def __init__(self):
self.servervariables = os.environ
self.querystring = QueryStringDict()
try:
qs = self.servervariables['QUERY_STRING']
for key,value in cgi.parse_qsl(qs,1):
# Write over any previous version of this
self.querystring[key] = value
except KeyError:
self.querystring = QueryStringDict()
self.form = QueryStringDict()
formparse = cgi.FieldStorage()
for f in formparse.keys():
self.form[f] = formparse[f].value
for q in self.querystring.keys():
if self.form.has_key(q):
del self.form[q]
self.__cookies = {}
if self.servervariables.has_key("HTTP_COOKIE"):
c = Cookie.SmartCookie()
c.load(self.servervariables["HTTP_COOKIE"])
self.__cookies = c
def cookies(self,name):
if self.__cookies.has_key(name):
return self.__cookies[name].value
class ResponseCookies(UserDict):
def __setitem__(self,key,item):
if not self.data.has_key(key):
self.data[key] = {}
self.data[key]["value"] = item
class response_obj:
def __init__(self):
self.headers_written = 0
self.__content_type = "Content-type: text/html"
self.__additional_headers = []
self.cookies = ResponseCookies()
def write(self,*args):
s = []
for i in args: s.append(str(i))
toWrite = string.join(s)
if string.strip(toWrite) == '': return
if not self.headers_written:
self.headers_written = 1
sys.stdout.write(self.__content_type)
sys.stdout.write('\n')
C = Cookie.SmartCookie()
for cookie in self.cookies.keys():
C[cookie] = self.cookies[cookie]["value"]
for k in self.cookies[cookie].keys():
if k <> "value":
C[cookie][k] = self.cookies[cookie][k]
if self.cookies:
sys.stdout.write(str(C) + '\n')
for h in self.__additional_headers:
sys.stdout.write(h)
sys.stdout.write('\n')
sys.stdout.write('\n')
s = []
for i in args: s.append(str(i))
sys.stdout.write(toWrite)
sys.stdout.flush()
def writeln(self,str):
self.write('%s\r\n' % str)
def content_type(self,ct):
if self.headers_written:
raise "Can't change content type once headers are written"
return
self.__content_type = "Content-type: %s" % ct
def addheader(self,name,value):
self.__additional_headers.append('%s: %s' % (name,value))
def redirect(self,url):
self.addheader('Status','302')
self.addheader('Location',url)
response.write("Redirect to ",url)
response.end
def end(self):
os._exit(0)
def parse_cas(fn):
# parse input file
try:
data = open(fn,'r').read()
except:
response.addheader('Status','404')
response.write('File not found (%s)' % fn)
return
# parse the file for SSI include file statements, and replace them
incFile = re.compile(r'<!--\s#include\sfile="(?P<filename>[^"]*)"\s-->')
wholeFile = re.split(incFile,data)
incFilePath = os.path.dirname(os.environ["PATH_TRANSLATED"])
for idx in range(2,len(wholeFile),2):
try:
wholeFile[idx-1] = open(os.path.join(incFilePath,wholeFile[idx-1])).read()
except:
print "Content-type: text/html\n\n"
print "Include file '%s' not found." % wholeFile[idx-1]
sys.exit(1)
data = string.join(wholeFile,'\n')
# split file into HTML and Python
reg = re.compile('(<\?cas=|<\?cas|\?>)')
fields = reg.split(data)
fields = filter(lambda x: len(x) <> 0, fields)
in_code = in_var = 0
line = 0
for f in fields:
if f == '<?cas': in_code = 1
elif f == '<?cas=': in_var = 1
elif f == '?>': in_code = in_var = 0
else:
# remove CDATA section
f = re.sub('(<!\[CDATA\[)','',(f))
f = re.sub(']]>$','',f)
if in_code:
try:
exec(f) in globals()
except:
from StringIO import StringIO
err_type, err_value, err_traceback = sys.exc_info()
response.write("\n<br>Castalian error: %s (%s)<br>\n" % (err_type,err_value))
response.write("<pre>")
fp = StringIO()
x = traceback.print_exc(file=fp)
for tline in string.split(fp.getvalue(),'\012'):
if string.find(tline,'Traceback (innermost last):') <> -1:
pass
elif string.find(tline,'in parse_cas') <> -1:
pass
elif string.find(tline,'exec(f)') <> -1:
pass
else:
response.write(cgi.escape(tline) + '\n')
response.write("</pre><br>\n")
del(err_traceback)
return
elif in_var:
if locals().has_key(string.strip(f)):
response.write(locals()[string.strip(f)])
elif globals().has_key(string.strip(f)):
response.write(globals()[string.strip(f)])
else:
response.write(f)
line = line + string.count(f,'\012')
return
# Create builtin objects
request = request_obj()
response = response_obj()
server = server_obj()
def Main(argv):
fn = None
try:
fn = os.environ['PATH_TRANSLATED']
os.environ["CASTALIAN_FILENAME"] = os.environ["PATH_INFO"]
except:
print "Content-type: text/plain\n\n"
print """Your webserver does not seem to be supplying
PATH_TRANSLATED. Castalian won't work without it."""
sys.exit(0)
outfile = sys.stdout
# Change directory to the directory containing the file
(d,f) = os.path.split(fn)
os.chdir(d)
sys.path.append('.')
parse_cas(fn)
return 0
if __name__=='__main__':
sys.exit(Main(sys.argv))