python: Cleaned up dispatching in wsgidir.
[ashd.git] / python / serve-ssi
1 #!/usr/bin/python
2
3 # This program is quite incomplete. I might complete it with more
4 # features as I need them. It will probably never be entirely
5 # compliant with Apache's version due to architectural differences.
6
7 import sys, os, time
8
9 def htmlquote(text):
10     ret = ""
11     for c in text:
12         if c == '&':
13             ret += "&"
14         elif c == '<':
15             ret += "&lt;"
16         elif c == '>':
17             ret += "&gt;"
18         elif c == '"':
19             ret += "&quot;"
20         else:
21             ret += c
22     return ret
23
24 def simpleerror(out, code, title, msg):
25     html = """<?xml version="1.0" encoding="US-ASCII"?>
26 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
27 <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
28 <head>
29 <title>%s</title>
30 </head>
31 <body>
32 <h1>%s</h1>
33 <p>%s</p>
34 </body>
35 </html>
36 """ % (title, title, htmlquote(msg))
37     out.write("HTTP/1.1 %d %s\n" % (code, title))
38     out.write("Content-Type: text/html\n")
39     out.write("Content-Length: %d\n" % len(html))
40     out.write("\n")
41     out.write(html)
42
43 ssivars = {}
44
45 def parsecmd(line, p):
46     try:
47         while line[p].isspace(): p += 1
48         cmd = ""
49         while not line[p].isspace():
50             cmd += line[p]
51             p += 1
52         pars = {}
53         while True:
54             while line[p].isspace(): p += 1
55             if line[p:p + 3] == "-->":
56                 return cmd, pars, p + 3
57             key = ""
58             while line[p].isalnum():
59                 key += line[p]
60                 p += 1
61             if key == "":
62                 return None, {}, p
63             while line[p].isspace(): p += 1
64             if line[p] != '=':
65                 continue
66             p += 1
67             while line[p].isspace(): p += 1
68             q = line[p]
69             if q != '"' and q != "'" and q != '`':
70                 continue
71             val = ""
72             p += 1
73             while line[p] != q:
74                 val += line[p]
75                 p += 1
76             p += 1
77             pars[key] = val
78     except IndexError:
79         return None, {}, len(line)
80
81 class ssifile(object):
82     def __init__(self, s, url, path):
83         self.s = s
84         self.url = url
85         self.path = path
86
87     def close(self):
88         self.s.close();
89
90     def initvars(self, vars):
91         now = time.time()
92         vars["DOCUMENT_NAME"] = os.path.basename(self.path)
93         vars["DATE_GMT"] = time.asctime(time.gmtime(now))
94         vars["DATE_LOCAL"] = time.asctime(time.localtime(now))
95         vars["LAST_MODIFIED"] = time.asctime(time.localtime(os.stat(self.path).st_mtime))
96
97     def includefile(self, path):
98         path = os.path.join(os.path.dirname(self.path), path)
99         try:
100             f = ssifile(open(path), url, path)
101         except Exception:
102             sys.stderr.write("serve-ssi: included file not found: %s\n" % path)
103             return
104         try:
105             f.process()
106         finally:
107             f.close
108
109     def docmd(self, cmd, pars):
110         if cmd == "include":
111             if "file" in pars:
112                 self.includefile(pars["file"])
113             elif "virtual" in pars:
114                 # XXX: For now, just include the file as-is. Change
115                 # when necessary.
116                 self.includefile(pars["virtual"])
117         elif cmd == "echo":
118             enc = htmlquote
119             if "encoding" in pars:
120                 if pars["encoding"] == "entity":
121                     enc = htmlquote
122             if "var" in pars:
123                 if pars["var"] in ssivars:
124                     sys.stdout.write(enc(ssivars[pars["var"]]))
125         else:
126             sys.stderr.write("serve-ssi: unknown SSI command: %s\n" % cmd)
127
128     def process(self):
129         for line in self.s:
130             p = 0
131             while True:
132                 p2 = line.find("<!--#", p)
133                 if p2 < 0:
134                     sys.stdout.write(line[p:])
135                     break
136                 sys.stdout.write(line[p:p2])
137                 cmd, pars, p = parsecmd(line, p2 + 5)
138                 if cmd is not None:
139                     self.docmd(cmd, pars)
140
141 if len(sys.argv) < 4:
142     sys.stderr.write("usage: serve-ssi METHOD URL REST\n")
143     sys.exit(1)
144 method, url, rest = sys.argv[1:]
145 path = os.getenv("REQ_X_ASH_FILE")
146 if path is None:
147     sys.stderr.write("serve-ssi: must be called with the X-Ash-File header\n")
148     sys.exit(1)
149 if rest != "":
150     simpleerror(sys.stdout, 404, "Not Found", "The resource specified by the URL does not exist.")
151     sys.exit(0)
152
153 try:
154     try:
155         f = ssifile(open(path), url, path)
156     except Exception:
157         simpleerror(sys.stdout, 500, "Server Error", "The server could not access its data.")
158         sys.exit(1)
159     try:
160         sys.stdout.write("HTTP/1.1 200 OK\n")
161         sys.stdout.write("Content-Type: text/html\n")
162         sys.stdout.write("\n")
163         f.initvars(ssivars)
164         f.process()
165     finally:
166         f.close()
167 except IOError:
168     # This is for catching EPIPE, when the client has closed the
169     # connection. This shouldn't *really* be necessary since the
170     # process should terminate with SIGPIPE, but apparently Python
171     # ignores that.
172     sys.exit(1)