~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 01:07:48 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:244
interpret.py: Major refactor of CGI interpreter code. Still missing some
functionality but now buffers input correctly and can handle binary files.
Also code much more nicely separated.
    Note: May not present error message correctly any more (temp).

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
 
29
29
import pwd
30
30
import subprocess
31
31
 
 
32
CGI_BLOCK_SIZE = 65535
 
33
 
32
34
def interpret_file(req, owner, filename, interpreter):
33
35
    """Serves a file by interpreting it using one of IVLE's builtin
34
36
    interpreters. All interpreters are intended to run in the user's jail. The
71
73
 
72
74
    return interpreter(uid, jail_dir, working_dir, path, req)
73
75
 
74
 
# Used to store mutable data
75
 
class Dummy:
76
 
    pass
 
76
class CGIFlags:
 
77
    """Stores flags regarding the state of reading CGI output."""
 
78
    def __init__(self):
 
79
        self.got_cgi_header = False
 
80
        self.started_cgi_body = False
 
81
        self.wrote_html_warning = False
 
82
        self.linebuf = ""
77
83
 
78
84
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
79
85
                script_path, req):
115
121
    # process_cgi_line: Reads a single line of CGI output and processes it.
116
122
    # Prints to req, and also does fancy HTML warnings if Content-Type
117
123
    # 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)
127
 
        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")
144
 
                pass
145
 
            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"
 
124
    cgiflags = CGIFlags()
 
125
 
 
126
    # Read from the process's stdout into req
 
127
    data = pid.stdout.read(CGI_BLOCK_SIZE)
 
128
    while len(data) > 0:
 
129
        process_cgi_output(req, data, cgiflags)
 
130
        data = pid.stdout.read(CGI_BLOCK_SIZE)
 
131
 
 
132
    # If we wrote an HTML warning header, write the footer
 
133
    if cgiflags.wrote_html_warning:
 
134
        req.write("""</pre>
 
135
  </div>
 
136
</body>
 
137
</html>""")
 
138
 
 
139
def process_cgi_output(req, data, cgiflags):
 
140
    """Processes a chunk of CGI output. data is a string of arbitrary length;
 
141
    some arbitrary chunk of output written by the CGI script."""
 
142
    if cgiflags.started_cgi_body:
 
143
        # FIXME: HTML escape text if wrote_html_warning
 
144
        req.write(data)
 
145
    else:
 
146
        # Break data into lines of CGI header data. 
 
147
        linebuf = cgiflags.linebuf + data
 
148
        # First see if we can split all header data
 
149
        split = linebuf.split('\r\n\r\n', 1)
 
150
        if len(split) == 1:
 
151
            # Allow UNIX newlines instead
 
152
            split = linebuf.split('\n\n', 1)
 
153
        if len(split) == 1:
 
154
            # Haven't seen all headers yet. Buffer and come back later.
 
155
            cgiflags.linebuf = linebuf
 
156
            return
 
157
 
 
158
        headers = split[0]
 
159
        data = split[1]
 
160
        cgiflags.linebuf = ""
 
161
        cgiflags.started_cgi_body = True
 
162
        # Process all the header lines
 
163
        split = headers.split('\r\n', 1)
 
164
        if len(split) == 1:
 
165
            split = headers.split('\n', 1)
 
166
        while True:
 
167
            process_cgi_header_line(req, split[0], cgiflags)
 
168
            if len(split) == 1: break
 
169
            headers = split[1]
 
170
            split = headers.split('\r\n', 1)
 
171
            if len(split) == 1:
 
172
                split = headers.split('\n', 1)
 
173
 
 
174
        # Call myself to flush out the extra bit of data we read
 
175
        process_cgi_output(req, data, cgiflags)
 
176
 
 
177
def process_cgi_header_line(req, line, cgiflags):
 
178
    """Process a line of CGI header data. line is a string representing a
 
179
    complete line of text, stripped and without the newline.
 
180
    Will set cgiflags.started_cgi_body when it detects an empty line, so the
 
181
    caller should realise this and change to byte stream output mode.
 
182
    """
 
183
    # Read CGI headers
 
184
    if line.strip() == "" and cgiflags.got_cgi_header:
 
185
        cgiflags.started_cgi_body = True
 
186
    elif line.startswith("Content-Type:"):
 
187
        req.content_type = line[13:].strip()
 
188
        cgiflags.got_cgi_header = True
 
189
    elif line.startswith("Location:"):
 
190
        # TODO
 
191
        cgiflags.got_cgi_header = True
 
192
    elif line.startswith("Status:"):
 
193
        # TODO
 
194
        cgiflags.got_cgi_header = True
 
195
    elif cgiflags.got_cgi_header:
 
196
        # Invalid header
 
197
        # TODO
 
198
        req.write("Invalid header")
 
199
        pass
 
200
    else:
 
201
        # Assume the user is not printing headers and give a warning
 
202
        # about that.
 
203
        # User program did not print header.
 
204
        # Make a fancy HTML warning for them.
 
205
        req.content_type = "text/html"
 
206
        write_html_warning(req,
 
207
"""You did not print a "Content-Type" header.
 
208
CGI requires you to print some content type. You may wish to try:</p>
 
209
<pre style="margin-left: 1em">Content-Type: text/html</pre>""", cgiflags)
 
210
        cgiflags.got_cgi_header = True
 
211
        cgiflags.started_cgi_body = True
 
212
        req.write(line)
 
213
 
 
214
def write_html_warning(req, text, cgiflags):
 
215
    """Prints an HTML warning about invalid CGI interaction on the part of the
 
216
    user. text may contain HTML markup."""
 
217
    req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
152
218
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
153
219
<html xmlns="http://www.w3.org/1999/xhtml">
154
220
<head>
158
224
<body style="margin: 0; padding: 0; font-family: sans-serif;">
159
225
  <div style="background-color: #faa; border-bottom: 1px solid black;
160
226
    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>
 
227
    <p><strong>Warning</strong>: %s
164
228
  </div>
165
229
  <div style="margin: 8px;">
166
230
    <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
 
231
""" % text)
 
232
    cgiflags.wrote_html_warning = True
 
233
    
 
234
 
185
235
location_cgi_python = os.path.join(conf.ivle_install_dir,
186
236
    "bin/trampoline")
187
237