~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to www/common/interpret.py

  • Committer: mattgiuca
  • Date: 2008-01-18 06:21:32 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:248
Added variable "ivle_version" to conf/__init__.py. This stores the version
number. It will be given to student CGI code by the server, as part of
SERVER_SOFTWARE.
debuginfo prints "ivle_version".

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
# Module: Interpret
19
19
# Author: Matt Giuca
20
 
# Date: 20/12/2007
 
20
# Date: 18/1/2008
21
21
 
22
22
# Runs a student script in a safe execution environment.
23
23
 
28
28
import os
29
29
import pwd
30
30
import subprocess
 
31
import cgi
 
32
 
 
33
# TODO: Make progressive output work
 
34
# Question: Will having a large buffer size stop progressive output from
 
35
# working on smaller output
 
36
 
 
37
CGI_BLOCK_SIZE = 65535
31
38
 
32
39
def interpret_file(req, owner, filename, interpreter):
33
40
    """Serves a file by interpreting it using one of IVLE's builtin
71
78
 
72
79
    return interpreter(uid, jail_dir, working_dir, path, req)
73
80
 
74
 
# Used to store mutable data
75
 
class Dummy:
76
 
    pass
 
81
class CGIFlags:
 
82
    """Stores flags regarding the state of reading CGI output."""
 
83
    def __init__(self):
 
84
        self.started_cgi_body = False
 
85
        self.got_cgi_headers = False
 
86
        self.wrote_html_warning = False
 
87
        self.linebuf = ""
 
88
        self.headers = {}       # Header names : values
77
89
 
78
90
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
79
91
                script_path, req):
105
117
        f.flush()
106
118
        f.seek(0)       # Rewind, for reading
107
119
 
 
120
    # Set up the environment
 
121
    # This automatically asks mod_python to load up the CGI variables into the
 
122
    # environment (which is a good first approximation)
 
123
    old_env = os.environ.copy()
 
124
    for k in os.environ.keys():
 
125
        del os.environ[k]
 
126
    for (k,v) in req.get_cgi_environ().items():
 
127
        os.environ[k] = v
 
128
 
108
129
    # usage: tramp uid jail_dir working_dir script_path
109
130
    pid = subprocess.Popen(
110
131
        [trampoline, str(uid), jail_dir, working_dir, interpreter,
112
133
        stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
113
134
        cwd=tramp_dir)
114
135
 
 
136
    # Restore the environment
 
137
    for k in os.environ.keys():
 
138
        del os.environ[k]
 
139
    for (k,v) in old_env.items():
 
140
        os.environ[k] = v
 
141
 
115
142
    # process_cgi_line: Reads a single line of CGI output and processes it.
116
143
    # Prints to req, and also does fancy HTML warnings if Content-Type
117
144
    # omitted.
118
 
    cgiflags = Dummy()
119
 
    cgiflags.got_cgi_header = False
120
 
    cgiflags.started_cgi_body = False
121
 
    cgiflags.wrote_html_warning = False
122
 
    def process_cgi_line(line):
123
 
        # FIXME? Issue with binary files (processing per-line?)
124
 
        if cgiflags.started_cgi_body:
125
 
            # FIXME: HTML escape text if wrote_html_warning
126
 
            req.write(line)
 
145
    cgiflags = CGIFlags()
 
146
 
 
147
    # Read from the process's stdout into req
 
148
    data = pid.stdout.read(CGI_BLOCK_SIZE)
 
149
    while len(data) > 0:
 
150
        process_cgi_output(req, data, cgiflags)
 
151
        data = pid.stdout.read(CGI_BLOCK_SIZE)
 
152
 
 
153
    # If we haven't processed headers yet, now is a good time
 
154
    if not cgiflags.started_cgi_body:
 
155
        process_cgi_output(req, '\n', cgiflags)
 
156
 
 
157
    # If we wrote an HTML warning header, write the footer
 
158
    if cgiflags.wrote_html_warning:
 
159
        req.write("""</pre>
 
160
  </div>
 
161
</body>
 
162
</html>""")
 
163
 
 
164
def process_cgi_output(req, data, cgiflags):
 
165
    """Processes a chunk of CGI output. data is a string of arbitrary length;
 
166
    some arbitrary chunk of output written by the CGI script."""
 
167
    if cgiflags.started_cgi_body:
 
168
        if cgiflags.wrote_html_warning:
 
169
            # HTML escape text if wrote_html_warning
 
170
            req.write(cgi.escape(data))
127
171
        else:
128
 
            # Read CGI headers
129
 
            if line.strip() == "" and cgiflags.got_cgi_header:
130
 
                cgiflags.started_cgi_body = True
131
 
            elif line.startswith("Content-Type:"):
132
 
                req.content_type = line[13:].strip()
133
 
                cgiflags.got_cgi_header = True
134
 
            elif line.startswith("Location:"):
135
 
                # TODO
136
 
                cgiflags.got_cgi_header = True
137
 
            elif line.startswith("Status:"):
138
 
                # TODO
139
 
                cgiflags.got_cgi_header = True
140
 
            elif cgiflags.got_cgi_header:
141
 
                # Invalid header
142
 
                # TODO
143
 
                req.write("Invalid header")
 
172
            req.write(data)
 
173
    else:
 
174
        # Break data into lines of CGI header data. 
 
175
        linebuf = cgiflags.linebuf + data
 
176
        # First see if we can split all header data
 
177
        split = linebuf.split('\r\n\r\n', 1)
 
178
        if len(split) == 1:
 
179
            # Allow UNIX newlines instead
 
180
            split = linebuf.split('\n\n', 1)
 
181
        if len(split) == 1:
 
182
            # Haven't seen all headers yet. Buffer and come back later.
 
183
            cgiflags.linebuf = linebuf
 
184
            return
 
185
 
 
186
        headers = split[0]
 
187
        data = split[1]
 
188
        cgiflags.linebuf = ""
 
189
        cgiflags.started_cgi_body = True
 
190
        # Process all the header lines
 
191
        split = headers.split('\r\n', 1)
 
192
        if len(split) == 1:
 
193
            split = headers.split('\n', 1)
 
194
        while True:
 
195
            process_cgi_header_line(req, split[0], cgiflags)
 
196
            if len(split) == 1: break
 
197
            headers = split[1]
 
198
            if cgiflags.wrote_html_warning:
 
199
                # We're done with headers. Treat the rest as data.
 
200
                data = headers + '\n' + data
 
201
                break
 
202
            split = headers.split('\r\n', 1)
 
203
            if len(split) == 1:
 
204
                split = headers.split('\n', 1)
 
205
 
 
206
        # Check to make sure the required headers were written
 
207
        if cgiflags.wrote_html_warning:
 
208
            # We already reported an error, that's enough
 
209
            pass
 
210
        elif "Content-Type" in cgiflags.headers:
 
211
            pass
 
212
        elif "Location" in cgiflags.headers:
 
213
            if ("Status" in cgiflags.headers and req.status >= 300
 
214
                and req.status < 400):
144
215
                pass
145
216
            else:
146
 
                # Assume the user is not printing headers and give a warning
147
 
                # about that.
148
 
                # User program did not print header.
149
 
                # Make a fancy HTML warning for them.
150
 
                req.content_type = "text/html"
151
 
                req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 
217
                message = """You did not write a valid status code for
 
218
the given location. To make a redirect, you may wish to try:</p>
 
219
<pre style="margin-left: 1em">Status: 302 Found
 
220
Location: &lt;redirect address&gt;</pre>"""
 
221
                write_html_warning(req, message)
 
222
                cgiflags.wrote_html_warning = True
 
223
        else:
 
224
            message = """You did not print a Content-Type header.
 
225
CGI requires that you print a "Content-Type". You may wish to try:</p>
 
226
<pre style="margin-left: 1em">Content-Type: text/html</pre>"""
 
227
            write_html_warning(req, message)
 
228
            cgiflags.wrote_html_warning = True
 
229
 
 
230
        # Call myself to flush out the extra bit of data we read
 
231
        process_cgi_output(req, data, cgiflags)
 
232
 
 
233
def process_cgi_header_line(req, line, cgiflags):
 
234
    """Process a line of CGI header data. line is a string representing a
 
235
    complete line of text, stripped and without the newline.
 
236
    """
 
237
    try:
 
238
        name, value = line.split(':', 1)
 
239
    except ValueError:
 
240
        # No colon. The user did not write valid headers.
 
241
        if len(cgiflags.headers) == 0:
 
242
            # First line was not a header line. We can assume this is not
 
243
            # a CGI app.
 
244
            message = """You did not print a CGI header.
 
245
CGI requires that you print a "Content-Type". You may wish to try:</p>
 
246
<pre style="margin-left: 1em">Content-Type: text/html</pre>"""
 
247
        else:
 
248
            # They printed some header at least, but there was an invalid
 
249
            # header.
 
250
            message = """You printed an invalid CGI header. You need to leave
 
251
a blank line after the headers, before writing the page contents."""
 
252
        write_html_warning(req, message)
 
253
        cgiflags.wrote_html_warning = True
 
254
        # Handle the rest of this line as normal data
 
255
        process_cgi_output(req, line + '\n', cgiflags)
 
256
        return
 
257
 
 
258
    # Read CGI headers
 
259
    value = value.strip()
 
260
    if name == "Content-Type":
 
261
        req.content_type = value
 
262
    elif name == "Location":
 
263
        req.location = value
 
264
    elif name == "Status":
 
265
        # Must be an integer, followed by a space, and then the status line
 
266
        # which we ignore (seems like Apache has no way to send a custom
 
267
        # status line).
 
268
        try:
 
269
            req.status = int(value.split(' ', 1)[0])
 
270
        except ValueError:
 
271
            message = """The "Status" CGI header was invalid. You need to
 
272
print a number followed by a message, such as "302 Found"."""
 
273
            write_html_warning(req, message)
 
274
            cgiflags.wrote_html_warning = True
 
275
            # Handle the rest of this line as normal data
 
276
            process_cgi_output(req, line + '\n', cgiflags)
 
277
    else:
 
278
        # Generic HTTP header
 
279
        # FIXME: Security risk letting users write arbitrary headers?
 
280
        req.headers_out[name] = value
 
281
    cgiflags.headers[name] = value
 
282
 
 
283
def write_html_warning(req, text):
 
284
    """Prints an HTML warning about invalid CGI interaction on the part of the
 
285
    user. text may contain HTML markup."""
 
286
    req.content_type = "text/html"
 
287
    req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
152
288
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
153
289
<html xmlns="http://www.w3.org/1999/xhtml">
154
290
<head>
155
291
  <meta http-equiv="Content-Type"
156
292
    content="text/html; charset=utf-8" />
157
293
</head>
158
 
<body style="margin: 0; padding: 0; font-family: sans;">
 
294
<body style="margin: 0; padding: 0; font-family: sans-serif;">
159
295
  <div style="background-color: #faa; border-bottom: 1px solid black;
160
296
    padding: 8px;">
161
 
    <p><strong>Warning</strong>: You did not print a "Content-Type" header.
162
 
    CGI requires you to print some content type. You may wish to try:</p>
163
 
    <pre style="margin-left: 1em">Content-Type: text/html</pre>
 
297
    <p><strong>Warning</strong>: %s
164
298
  </div>
165
299
  <div style="margin: 8px;">
166
300
    <pre>
167
 
""")
168
 
                cgiflags.got_cgi_header = True
169
 
                cgiflags.wrote_html_warning = True
170
 
                cgiflags.started_cgi_body = True
171
 
                req.write(line)
172
 
 
173
 
    # Read from the process's stdout into req
174
 
    for line in pid.stdout:
175
 
        process_cgi_line(line)
176
 
 
177
 
    # If we wrote an HTML warning header, write the footer
178
 
    if cgiflags.wrote_html_warning:
179
 
        req.write("""</pre>
180
 
  </div>
181
 
</body>
182
 
</html>""")
183
 
 
184
 
# TODO: Replace mytest with cgi trampoline handler script
 
301
""" % text)
 
302
 
185
303
location_cgi_python = os.path.join(conf.ivle_install_dir,
186
304
    "bin/trampoline")
187
305