1 """Module for handling server-side-include formatted files
3 This module is quite incomplete. I might complete it with more
4 features as I need them. It will probably never be entirely compliant
5 with Apache's version due to architectural differences.
8 import sys, os, io, time, logging, functools
11 log = logging.getLogger("ssi")
13 def parsecmd(text, p):
15 while text[p].isspace(): p += 1
17 while not text[p].isspace():
22 while text[p].isspace(): p += 1
23 if text[p:p + 3] == "-->":
24 return cmd, pars, p + 3
26 while text[p].isalnum():
31 while text[p].isspace(): p += 1
35 while text[p].isspace(): p += 1
37 if q != '"' and q != "'" and q != '`':
47 return None, {}, len(text)
49 class context(object):
50 def __init__(self, out, root):
54 self.vars["DOCUMENT_NAME"] = os.path.basename(root.path)
55 self.vars["DATE_GMT"] = time.asctime(time.gmtime(now))
56 self.vars["DATE_LOCAL"] = time.asctime(time.localtime(now))
57 self.vars["LAST_MODIFIED"] = time.asctime(time.localtime(root.mtime))
59 class ssifile(object):
60 def __init__(self, path):
62 self.mtime = os.stat(self.path).st_mtime
63 with open(path) as fp:
64 self.parts = self.parse(fp.read())
66 def text(self, text, ctx):
69 def echo(self, var, enc, ctx):
71 ctx.out.write(enc(ctx.vars[var]))
73 def include(self, path, ctx):
75 nest = getfile(os.path.join(os.path.dirname(self.path), path))
77 log.warning("%s: could not find included file %s" % (self.path, path))
81 def process(self, ctx):
82 for part in self.parts:
85 def resolvecmd(self, cmd, pars):
88 return functools.partial(self.include, pars["file"])
89 elif "virtual" in pars:
90 # XXX: For now, just include the file as-is. Change
92 return functools.partial(self.include, pars["virtual"])
94 log.warning("%s: invalid `include' directive" % self.path)
98 log.warning("%s: invalid `echo' directive" % self.path)
100 enc = wsgiutil.htmlquote
101 if "encoding" in pars:
102 if pars["encoding"] == "entity":
103 enc = wsgiutil.htmlquote
104 return functools.partial(self.echo, pars["var"], enc)
106 log.warning("%s: unknown SSI command `%s'" % (self.path, cmd))
109 def parse(self, text):
113 p2 = text.find("<!--#", p)
115 ret.append(functools.partial(self.text, text[p:]))
117 ret.append(functools.partial(self.text, text[p:p2]))
118 cmd, pars, p = parsecmd(text, p2 + 5)
120 cmd = self.resolvecmd(cmd, pars)
127 path = os.path.normpath(path)
128 cf = filecache.get(path)
130 cf = filecache[path] = ssifile(path)
131 elif os.stat(path).st_mtime != cf.mtime:
132 cf = filecache[path] = ssifile(path)
135 def wsgi(env, startreq):
137 if env["PATH_INFO"] != "":
138 return wsgiutil.simpleerror(env, startreq, 404, "Not Found", "The resource specified by the URL does not exist.")
139 root = getfile(env["SCRIPT_FILENAME"])
141 root.process(context(buf, root))
143 return wsgituil.simpleerror(env, startreq, 500, "Internal Error", "The server encountered an unpexpected error while handling SSI.")
144 ret = buf.getvalue().encode("utf8")
145 startreq("200 OK", [("Content-Type", "text/html; charset=UTF-8"), ("Content-Length", str(len(ret)))])