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)
143
(_, self.path) = (ivle.util.split_path(self.uri))
145
(self.app, self.path) = (ivle.util.split_path(self.uri))
147
self.hostname = os.environ['SERVER_NAME']
148
self.headers_in = _http_headers_in_from_cgi()
149
self.headers_out = {}
151
# Default values for the output members
152
self.status = CGIRequest.HTTP_OK
153
self.content_type = None # Use Apache's default
157
self.got_common_vars = False
160
def install_error_handler(self):
161
'''Install our exception handler as the default.'''
162
sys.excepthook = self.handle_unknown_exception
164
def __writeheaders(self):
165
"""Writes out the HTTP and HTML headers before any real data is
167
self.headers_written = True
168
if 'Content-Type' in self.headers_out:
169
self.content_type = self.headers_out['Content-Type']
170
if 'Location' in self.headers_out:
171
self.location = self.headers_out['Location']
173
# CGI allows for four response types: Document, Local Redirect, Client
174
# Redirect, and Client Redirect w/ Document
175
# XXX We do not allow Local Redirect
176
if self.location != None:
177
# This is a Client Redirect
178
print "Location: %s" % self.location
179
if self.content_type == None:
182
# Else: This is a Client Redirect with Document
183
print "Status: %d" % self.status
184
print "Content-Type: %s" % self.content_type
186
# This is a Document response
187
print "Content-Type: %s" % self.content_type
188
print "Status: %d" % self.status
190
# Print the other headers
191
for k,v in self.headers_out.items():
192
if k != 'Content-Type' and k != 'Location':
193
print "%s: %s" % (k, v)
195
# Print a blank line to signal the start of output
198
def ensure_headers_written(self):
199
"""Writes out the HTTP and HTML headers if they haven't already been
201
if not self.headers_written:
202
self.__writeheaders()
204
def write(self, string, flush=1):
205
"""Writes string directly to the client, then flushes the buffer,
206
unless flush is 0."""
208
if not self.headers_written:
209
self.__writeheaders()
210
if isinstance(string, unicode):
211
# Encode unicode strings as UTF-8
212
# (Otherwise cannot handle being written to a bytestream)
213
sys.stdout.write(string.encode('utf8'))
215
# 8-bit clean strings just get written directly.
216
# This includes binary strings.
217
sys.stdout.write(string)
220
"""Flushes the output buffer."""
223
def sendfile(self, filename):
224
"""Sends the named file directly to the client."""
225
if not self.headers_written:
226
self.__writeheaders()
230
sys.stdout.write(buf)
235
def read(self, len=None):
236
"""Reads at most len bytes directly from the client. (See mod_python
239
return sys.stdin.read()
241
return sys.stdin.read(len)
243
def handle_unknown_exception(self, exc_type, exc_value, exc_tb):
244
if exc_type is ivle.util.IVLEError:
245
self.headers_out['X-IVLE-Error-Code'] = exc_value.httpcode
247
self.headers_out['X-IVLE-Error-Type'] = exc_type.__name__
250
self.headers_out['X-IVLE-Error-Message'] = exc_value.message
251
except AttributeError:
254
self.headers_out['X-IVLE-Error-Info'] = urllib.quote(''.join(
255
traceback.format_exception(exc_type, exc_value, exc_tb)))
257
self.ensure_headers_written()
258
self.write('An internal IVLE error has occurred.')
260
sys.exit(self.status)
262
def throw_redirect(self, location):
263
"""Writes out an HTTP redirect to the specified URL. Exits the
264
process, so any code following this call will not be executed.
266
httpcode: An HTTP response status code. Pass a constant from the
269
self.status = CGIRequest.HTTP_MOVED_TEMPORARILY
270
self.location = location
271
self.ensure_headers_written()
273
sys.exit(self.status)
275
def get_session(self):
276
"""Returns a mod_python Session object for this request.
277
Note that this is dependent on mod_python and may need to change
278
interface if porting away from mod_python."""
279
# Cache the session object
280
if not hasattr(self, 'session'):
281
#self.session = Session.FileSession(self.apache_req)
283
# FIXME: How to get session?
286
def get_fieldstorage(self):
287
"""Returns a mod_python FieldStorage object for this request.
288
Note that this is dependent on mod_python and may need to change
289
interface if porting away from mod_python."""
290
# Cache the fieldstorage object
291
if not hasattr(self, 'fields'):
292
self.fields = cgi.FieldStorage()
295
def get_cgi_environ(self):
296
"""Returns the CGI environment emulation for this request. (Calls
297
add_common_vars). The environment is returned as a mapping
298
compatible with os.environ."""