Commit | Line | Data |
---|---|---|
6d94a5f6 FT |
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 += "<" | |
16 | elif c == '>': | |
17 | ret += ">" | |
18 | elif c == '"': | |
19 | ret += """ | |
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: | |
d4331238 FT |
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. | |
6d94a5f6 | 172 | sys.exit(1) |