1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2008 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
# Presents a CGIRequest class which creates an object compatible with IVLE
23
# Request objects (the same interface exposed by www.dispatch.request) from a
25
# This allows CGI scripts to create request objects and then pass them to
26
# normal IVLE handlers.
28
# NOTE: This object does not support write_html_head_foot (simply because we
29
# do not need it in its intended application: fileservice).
42
def _http_headers_in_from_cgi():
43
"""Returns a dictionary of HTTP headers and their values, reading from the
46
for k in os.environ.keys():
47
if k.startswith("HTTP_"):
48
# Change the case - underscores become - and each word is
50
varname = '-'.join(map(lambda x: x[0:1] + x[1:].lower(),
52
d[varname] = os.environ[k]
56
"""An IVLE request object, built from a CGI script. This is presented to
57
the IVLE apps as a way of interacting with the CGI server.
58
See dispatch.request for a full interface specification.
61
# COPIED from dispatch/request.py
62
# Special code for an OK response.
63
# Do not use HTTP_OK; for some reason Apache produces an "OK" error
64
# message if you do that.
70
HTTP_SWITCHING_PROTOCOLS = 101
75
HTTP_NON_AUTHORITATIVE = 203
77
HTTP_RESET_CONTENT = 205
78
HTTP_PARTIAL_CONTENT = 206
79
HTTP_MULTI_STATUS = 207
80
HTTP_MULTIPLE_CHOICES = 300
81
HTTP_MOVED_PERMANENTLY = 301
82
HTTP_MOVED_TEMPORARILY = 302
84
HTTP_NOT_MODIFIED = 304
86
HTTP_TEMPORARY_REDIRECT = 307
87
HTTP_BAD_REQUEST = 400
88
HTTP_UNAUTHORIZED = 401
89
HTTP_PAYMENT_REQUIRED = 402
92
HTTP_METHOD_NOT_ALLOWED = 405
93
HTTP_NOT_ACCEPTABLE = 406
94
HTTP_PROXY_AUTHENTICATION_REQUIRED= 407
95
HTTP_REQUEST_TIME_OUT = 408
98
HTTP_LENGTH_REQUIRED = 411
99
HTTP_PRECONDITION_FAILED = 412
100
HTTP_REQUEST_ENTITY_TOO_LARGE = 413
101
HTTP_REQUEST_URI_TOO_LARGE = 414
102
HTTP_UNSUPPORTED_MEDIA_TYPE = 415
103
HTTP_RANGE_NOT_SATISFIABLE = 416
104
HTTP_EXPECTATION_FAILED = 417
105
HTTP_UNPROCESSABLE_ENTITY = 422
107
HTTP_FAILED_DEPENDENCY = 424
108
HTTP_INTERNAL_SERVER_ERROR = 500
109
HTTP_NOT_IMPLEMENTED = 501
110
HTTP_BAD_GATEWAY = 502
111
HTTP_SERVICE_UNAVAILABLE = 503
112
HTTP_GATEWAY_TIME_OUT = 504
113
HTTP_VERSION_NOT_SUPPORTED = 505
114
HTTP_VARIANT_ALSO_VARIES = 506
115
HTTP_INSUFFICIENT_STORAGE = 507
116
HTTP_NOT_EXTENDED = 510
119
"""Builds an CGI Request object from the current CGI environment.
120
This results in an object with all of the necessary methods and
123
self.headers_written = False
125
if ('SERVER_NAME' not in os.environ or
126
'REQUEST_METHOD' not in os.environ or
127
'SCRIPT_NAME' not in os.environ or
128
'PATH_INFO' not in os.environ):
129
raise Exception("No CGI environment found")
131
# Determine if the browser used the public host name to make the
132
# request (in which case we are in "public mode")
133
if os.environ['SERVER_NAME'] == ivle.conf.public_host:
134
self.publicmode = True
136
self.publicmode = False
138
# Inherit values for the input members
139
self.method = os.environ['REQUEST_METHOD']
140
self.uri = os.environ['SCRIPT_NAME'] + os.environ['PATH_INFO']
141
# Split the given path into the app (top-level dir) and sub-path
142
# (after first stripping away the root directory)
143
path = ivle.util.unmake_path(self.uri)
146
(_, self.path) = (ivle.util.split_path(path))
148
(self.app, self.path) = (ivle.util.split_path(path))
150
self.hostname = os.environ['SERVER_NAME']
151
self.headers_in = _http_headers_in_from_cgi()
152
self.headers_out = {}
154
# Default values for the output members
155
self.status = CGIRequest.HTTP_OK
156
self.content_type = None # Use Apache's default
158
self.title = None # Will be set by dispatch before passing to app
161
self.write_html_head_foot = False
162
self.got_common_vars = False
165
def install_error_handler(self):
166
'''Install our exception handler as the default.'''
167
sys.excepthook = self.handle_unknown_exception
169
def __writeheaders(self):
170
"""Writes out the HTTP and HTML headers before any real data is
172
self.headers_written = True
173
if 'Content-Type' in self.headers_out:
174
self.content_type = self.headers_out['Content-Type']
175
if 'Location' in self.headers_out:
176
self.location = self.headers_out['Location']
178
# CGI allows for four response types: Document, Local Redirect, Client
179
# Redirect, and Client Redirect w/ Document
180
# XXX We do not allow Local Redirect
181
if self.location != None:
182
# This is a Client Redirect
183
print "Location: %s" % self.location
184
if self.content_type == None:
187
# Else: This is a Client Redirect with Document
188
print "Status: %d" % self.status
189
print "Content-Type: %s" % self.content_type
191
# This is a Document response
192
print "Content-Type: %s" % self.content_type
193
print "Status: %d" % self.status
195
# Print the other headers
196
for k,v in self.headers_out.items():
197
if k != 'Content-Type' and k != 'Location':
198
print "%s: %s" % (k, v)
200
# XXX write_html_head_foot not supported
201
#if self.write_html_head_foot:
202
# # Write the HTML header, pass "self" (request object)
203
# self.func_write_html_head(self)
204
# Print a blank line to signal the start of output
207
def ensure_headers_written(self):
208
"""Writes out the HTTP and HTML headers if they haven't already been
210
if not self.headers_written:
211
self.__writeheaders()
213
def write(self, string, flush=1):
214
"""Writes string directly to the client, then flushes the buffer,
215
unless flush is 0."""
217
if not self.headers_written:
218
self.__writeheaders()
219
if isinstance(string, unicode):
220
# Encode unicode strings as UTF-8
221
# (Otherwise cannot handle being written to a bytestream)
222
sys.stdout.write(string.encode('utf8'))
224
# 8-bit clean strings just get written directly.
225
# This includes binary strings.
226
sys.stdout.write(string)
229
"""Flushes the output buffer."""
232
def sendfile(self, filename):
233
"""Sends the named file directly to the client."""
234
if not self.headers_written:
235
self.__writeheaders()
239
sys.stdout.write(buf)
244
def read(self, len=None):
245
"""Reads at most len bytes directly from the client. (See mod_python
248
return sys.stdin.read()
250
return sys.stdin.read(len)
252
def throw_error(self, httpcode, message):
253
"""Writes out an HTTP error of the specified code. Exits the process,
254
so any code following this call will not be executed.
256
(This is justified because of the nature of CGI, it is a single-script
257
environment, there is no containing process which needs to catch an
260
httpcode: An HTTP response status code. Pass a constant from the
263
raise ivle.util.IVLEError(httpcode, message)
265
def handle_unknown_exception(self, exc_type, exc_value, exc_tb):
266
if exc_type is ivle.util.IVLEError:
267
self.headers_out['X-IVLE-Error-Code'] = exc_value.httpcode
269
self.headers_out['X-IVLE-Error-Type'] = exc_type.__name__
272
self.headers_out['X-IVLE-Error-Message'] = exc_value.message
273
except AttributeError:
276
self.headers_out['X-IVLE-Error-Info'] = urllib.quote(''.join(
277
traceback.format_exception(exc_type, exc_value, exc_tb)))
279
self.ensure_headers_written()
280
self.write('An internal IVLE error has occurred.')
282
sys.exit(self.status)
284
def throw_redirect(self, location):
285
"""Writes out an HTTP redirect to the specified URL. Exits the
286
process, so any code following this call will not be executed.
288
httpcode: An HTTP response status code. Pass a constant from the
291
self.status = CGIRequest.HTTP_MOVED_TEMPORARILY
292
self.location = location
293
self.ensure_headers_written()
295
sys.exit(self.status)
297
def get_session(self):
298
"""Returns a mod_python Session object for this request.
299
Note that this is dependent on mod_python and may need to change
300
interface if porting away from mod_python."""
301
# Cache the session object
302
if not hasattr(self, 'session'):
303
#self.session = Session.FileSession(self.apache_req)
305
# FIXME: How to get session?
308
def get_fieldstorage(self):
309
"""Returns a mod_python FieldStorage object for this request.
310
Note that this is dependent on mod_python and may need to change
311
interface if porting away from mod_python."""
312
# Cache the fieldstorage object
313
if not hasattr(self, 'fields'):
314
self.fields = cgi.FieldStorage()
317
def get_cgi_environ(self):
318
"""Returns the CGI environment emulation for this request. (Calls
319
add_common_vars). The environment is returned as a mapping
320
compatible with os.environ."""