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.
40
def _http_headers_in_from_cgi():
41
"""Returns a dictionary of HTTP headers and their values, reading from the
44
for k in os.environ.keys():
45
if k.startswith("HTTP_"):
46
# Change the case - underscores become - and each word is
48
varname = '-'.join(map(lambda x: x[0:1] + x[1:].lower(),
50
d[varname] = os.environ[k]
54
"""An IVLE request object, built from a CGI script. This is presented to
55
the IVLE apps as a way of interacting with the CGI server.
56
See dispatch.request for a full interface specification.
59
# COPIED from dispatch/request.py
60
# Special code for an OK response.
61
# Do not use HTTP_OK; for some reason Apache produces an "OK" error
62
# message if you do that.
68
HTTP_SWITCHING_PROTOCOLS = 101
73
HTTP_NON_AUTHORITATIVE = 203
75
HTTP_RESET_CONTENT = 205
76
HTTP_PARTIAL_CONTENT = 206
77
HTTP_MULTI_STATUS = 207
78
HTTP_MULTIPLE_CHOICES = 300
79
HTTP_MOVED_PERMANENTLY = 301
80
HTTP_MOVED_TEMPORARILY = 302
82
HTTP_NOT_MODIFIED = 304
84
HTTP_TEMPORARY_REDIRECT = 307
85
HTTP_BAD_REQUEST = 400
86
HTTP_UNAUTHORIZED = 401
87
HTTP_PAYMENT_REQUIRED = 402
90
HTTP_METHOD_NOT_ALLOWED = 405
91
HTTP_NOT_ACCEPTABLE = 406
92
HTTP_PROXY_AUTHENTICATION_REQUIRED= 407
93
HTTP_REQUEST_TIME_OUT = 408
96
HTTP_LENGTH_REQUIRED = 411
97
HTTP_PRECONDITION_FAILED = 412
98
HTTP_REQUEST_ENTITY_TOO_LARGE = 413
99
HTTP_REQUEST_URI_TOO_LARGE = 414
100
HTTP_UNSUPPORTED_MEDIA_TYPE = 415
101
HTTP_RANGE_NOT_SATISFIABLE = 416
102
HTTP_EXPECTATION_FAILED = 417
103
HTTP_UNPROCESSABLE_ENTITY = 422
105
HTTP_FAILED_DEPENDENCY = 424
106
HTTP_INTERNAL_SERVER_ERROR = 500
107
HTTP_NOT_IMPLEMENTED = 501
108
HTTP_BAD_GATEWAY = 502
109
HTTP_SERVICE_UNAVAILABLE = 503
110
HTTP_GATEWAY_TIME_OUT = 504
111
HTTP_VERSION_NOT_SUPPORTED = 505
112
HTTP_VARIANT_ALSO_VARIES = 506
113
HTTP_INSUFFICIENT_STORAGE = 507
114
HTTP_NOT_EXTENDED = 510
117
"""Builds an CGI Request object from the current CGI environment.
118
This results in an object with all of the necessary methods and
121
self.headers_written = False
123
if ('SERVER_NAME' not in os.environ or
124
'REQUEST_METHOD' not in os.environ or
125
'SCRIPT_NAME' not in os.environ or
126
'PATH_INFO' not in os.environ):
127
raise Exception("No CGI environment found")
129
# Determine if the browser used the public host name to make the
130
# request (in which case we are in "public mode")
131
if os.environ['SERVER_NAME'] == ivle.conf.public_host:
132
self.publicmode = True
134
self.publicmode = False
136
# Inherit values for the input members
137
self.method = os.environ['REQUEST_METHOD']
138
self.uri = os.environ['SCRIPT_NAME'] + os.environ['PATH_INFO']
139
# Split the given path into the app (top-level dir) and sub-path
140
# (after first stripping away the root directory)
141
path = self.unmake_path(self.uri)
144
(_, self.path) = (ivle.util.split_path(path))
146
(self.app, self.path) = (ivle.util.split_path(path))
148
self.hostname = os.environ['SERVER_NAME']
149
self.headers_in = _http_headers_in_from_cgi()
150
self.headers_out = {}
152
# Default values for the output members
153
self.status = CGIRequest.HTTP_OK
154
self.content_type = None # Use Apache's default
158
self.got_common_vars = False
161
def install_error_handler(self):
162
'''Install our exception handler as the default.'''
163
sys.excepthook = self.handle_unknown_exception
165
def __writeheaders(self):
166
"""Writes out the HTTP and HTML headers before any real data is
168
self.headers_written = True
169
if 'Content-Type' in self.headers_out:
170
self.content_type = self.headers_out['Content-Type']
171
if 'Location' in self.headers_out:
172
self.location = self.headers_out['Location']
174
# CGI allows for four response types: Document, Local Redirect, Client
175
# Redirect, and Client Redirect w/ Document
176
# XXX We do not allow Local Redirect
177
if self.location != None:
178
# This is a Client Redirect
179
print "Location: %s" % self.location
180
if self.content_type == None:
183
# Else: This is a Client Redirect with Document
184
print "Status: %d" % self.status
185
print "Content-Type: %s" % self.content_type
187
# This is a Document response
188
print "Content-Type: %s" % self.content_type
189
print "Status: %d" % self.status
191
# Print the other headers
192
for k,v in self.headers_out.items():
193
if k != 'Content-Type' and k != 'Location':
194
print "%s: %s" % (k, v)
196
# Print a blank line to signal the start of output
199
def ensure_headers_written(self):
200
"""Writes out the HTTP and HTML headers if they haven't already been
202
if not self.headers_written:
203
self.__writeheaders()
205
def write(self, string, flush=1):
206
"""Writes string directly to the client, then flushes the buffer,
207
unless flush is 0."""
209
if not self.headers_written:
210
self.__writeheaders()
211
if isinstance(string, unicode):
212
# Encode unicode strings as UTF-8
213
# (Otherwise cannot handle being written to a bytestream)
214
sys.stdout.write(string.encode('utf8'))
216
# 8-bit clean strings just get written directly.
217
# This includes binary strings.
218
sys.stdout.write(string)
221
"""Flushes the output buffer."""
224
def sendfile(self, filename):
225
"""Sends the named file directly to the client."""
226
if not self.headers_written:
227
self.__writeheaders()
231
sys.stdout.write(buf)
236
def read(self, len=None):
237
"""Reads at most len bytes directly from the client. (See mod_python
240
return sys.stdin.read()
242
return sys.stdin.read(len)
244
def handle_unknown_exception(self, exc_type, exc_value, exc_tb):
245
if exc_type is ivle.util.IVLEError:
246
self.headers_out['X-IVLE-Error-Code'] = exc_value.httpcode
248
self.headers_out['X-IVLE-Error-Type'] = exc_type.__name__
251
self.headers_out['X-IVLE-Error-Message'] = exc_value.message
252
except AttributeError:
255
self.headers_out['X-IVLE-Error-Info'] = urllib.quote(''.join(
256
traceback.format_exception(exc_type, exc_value, exc_tb)))
258
self.ensure_headers_written()
259
self.write('An internal IVLE error has occurred.')
261
sys.exit(self.status)
263
def throw_redirect(self, location):
264
"""Writes out an HTTP redirect to the specified URL. Exits the
265
process, so any code following this call will not be executed.
267
httpcode: An HTTP response status code. Pass a constant from the
270
self.status = CGIRequest.HTTP_MOVED_TEMPORARILY
271
self.location = location
272
self.ensure_headers_written()
274
sys.exit(self.status)
276
def unmake_path(self, path):
277
"""Strip the IVLE URL prefix from the given path, if present.
279
Also normalises the path.
281
path = os.path.normpath(path)
282
root = os.path.normpath(ivle.conf.root_dir)
284
if path.startswith(root):
285
path = path[len(root):]
286
# Take out the slash as well
287
if len(path) > 0 and path[0] == os.sep:
292
def get_session(self):
293
"""Returns a mod_python Session object for this request.
294
Note that this is dependent on mod_python and may need to change
295
interface if porting away from mod_python."""
296
# Cache the session object
297
if not hasattr(self, 'session'):
298
#self.session = Session.FileSession(self.apache_req)
300
# FIXME: How to get session?
303
def get_fieldstorage(self):
304
"""Returns a mod_python FieldStorage object for this request.
305
Note that this is dependent on mod_python and may need to change
306
interface if porting away from mod_python."""
307
# Cache the fieldstorage object
308
if not hasattr(self, 'fields'):
309
self.fields = cgi.FieldStorage()
312
def get_cgi_environ(self):
313
"""Returns the CGI environment emulation for this request. (Calls
314
add_common_vars). The environment is returned as a mapping
315
compatible with os.environ."""