1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/WebStack/CGI.py Sat Apr 24 20:33:31 2004 +0000
1.3 @@ -0,0 +1,343 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +CGI classes.
1.8 +"""
1.9 +
1.10 +import Generic
1.11 +import os, sys
1.12 +from Helpers.Request import MessageBodyStream
1.13 +from Helpers.Auth import UserInfo
1.14 +from Helpers import Environment
1.15 +from cgi import parse_qs, FieldStorage
1.16 +import Cookie
1.17 +from StringIO import StringIO
1.18 +
1.19 +class Transaction(Generic.Transaction):
1.20 +
1.21 + """
1.22 + CGI transaction interface.
1.23 + """
1.24 +
1.25 + def __init__(self, input=None, output=None, env=None):
1.26 +
1.27 + """
1.28 + Initialise the transaction using the CGI 'input' and 'output' streams.
1.29 + These streams are optional and default to standard input and standard
1.30 + output respectively.
1.31 + """
1.32 +
1.33 + self.input = input or sys.stdin
1.34 + self.output = output or sys.stdout
1.35 + self.env = env or os.environ
1.36 +
1.37 + # Other attributes of interest in instances of this class.
1.38 +
1.39 + self.content_type = None
1.40 + self.response_code = 200
1.41 + self.content = StringIO()
1.42 + self.headers_out = {}
1.43 + self.cookies_out = Cookie.SimpleCookie()
1.44 +
1.45 + # Define the incoming cookies.
1.46 +
1.47 + self.cookies_in = Cookie.SimpleCookie(self.env.get("HTTP_COOKIE"))
1.48 +
1.49 + def commit(self):
1.50 +
1.51 + """
1.52 + A special method, synchronising the transaction with framework-specific
1.53 + objects.
1.54 +
1.55 + See draft-coar-cgi-v11-03, section 7.
1.56 + """
1.57 +
1.58 + # NOTE: Provide sensible messages.
1.59 +
1.60 + self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status"))
1.61 + if self.content_type is not None:
1.62 + self.output.write("Content-type: %s\n" % self.format_content_type(self.content_type))
1.63 + for header, value in self.headers_out.items():
1.64 + self.output.write("%s: %s\n" %
1.65 + (self.format_header_value(header), self.format_header_value(value))
1.66 + )
1.67 + self.output.write(str(self.cookies_out))
1.68 + self.output.write("\n")
1.69 +
1.70 + self.content.seek(0)
1.71 + self.output.write(self.content.read())
1.72 +
1.73 + # Request-related methods.
1.74 +
1.75 + def get_request_stream(self):
1.76 +
1.77 + """
1.78 + A framework-specific method which returns the request stream for
1.79 + the transaction.
1.80 + """
1.81 +
1.82 + return self.input
1.83 +
1.84 + def get_request_method(self):
1.85 +
1.86 + """
1.87 + A framework-specific method which gets the request method.
1.88 + """
1.89 +
1.90 + return self.env.get("REQUEST_METHOD")
1.91 +
1.92 + def get_headers(self):
1.93 +
1.94 + """
1.95 + A framework-specific method which returns all request headers as a
1.96 + dictionary-like object mapping header names to values.
1.97 + """
1.98 +
1.99 + return Environment.get_headers(self.env)
1.100 +
1.101 + def get_header_values(self, key):
1.102 +
1.103 + """
1.104 + A framework-specific method which returns a list of all request header
1.105 + values associated with the given 'key'. Note that according to RFC 2616,
1.106 + 'key' is treated as a case-insensitive string.
1.107 + """
1.108 +
1.109 + return self.convert_to_list(self.get_headers().get(key))
1.110 +
1.111 + def get_content_type(self):
1.112 +
1.113 + """
1.114 + A framework-specific method which gets the content type specified on the
1.115 + request, along with the charset employed.
1.116 + """
1.117 +
1.118 + return self.parse_content_type(self.env.get("CONTENT_TYPE"))
1.119 +
1.120 + def get_content_charsets(self):
1.121 +
1.122 + """
1.123 + Returns the character set preferences.
1.124 + """
1.125 +
1.126 + return self.parse_content_preferences(None)
1.127 +
1.128 + def get_content_languages(self):
1.129 +
1.130 + """
1.131 + A framework-specific method which extracts language information from
1.132 + the transaction.
1.133 + """
1.134 +
1.135 + return self.parse_content_preferences(None)
1.136 +
1.137 + def get_path(self):
1.138 +
1.139 + """
1.140 + A framework-specific method which gets the entire path from the request.
1.141 + """
1.142 +
1.143 + path = self.env.get("SCRIPT_NAME") or ""
1.144 + if self.env.has_key("PATH_INFO"):
1.145 + path += self.env["PATH_INFO"]
1.146 + qs = self.env.get("QUERY_STRING")
1.147 + if qs:
1.148 + path += "?"
1.149 + path += qs
1.150 + return path
1.151 +
1.152 + def get_path_info(self):
1.153 +
1.154 + """
1.155 + A framework-specific method which gets the "path info" (the part of the
1.156 + URL after the resource name handling the current request) from the
1.157 + request.
1.158 + """
1.159 +
1.160 + return self.env.get("PATH_INFO") or ""
1.161 +
1.162 + def get_query_string(self):
1.163 +
1.164 + """
1.165 + A framework-specific method which gets the query string from the path in
1.166 + the request.
1.167 + """
1.168 +
1.169 + return self.env.get("QUERY_STRING") or ""
1.170 +
1.171 + # Higher level request-related methods.
1.172 +
1.173 + def get_fields_from_path(self):
1.174 +
1.175 + """
1.176 + A framework-specific method which extracts the form fields from the
1.177 + path specified in the transaction. The underlying framework may refuse
1.178 + to supply fields from the path if handling a POST transaction.
1.179 +
1.180 + Returns a dictionary mapping field names to lists of values (even if a
1.181 + single value is associated with any given field name).
1.182 + """
1.183 +
1.184 + return parse_qs(self.get_query_string(), keep_blank_values=1)
1.185 +
1.186 + def get_fields_from_body(self):
1.187 +
1.188 + """
1.189 + A framework-specific method which extracts the form fields from the
1.190 + message body in the transaction.
1.191 +
1.192 + Returns a dictionary mapping field names to lists of values (even if a
1.193 + single value is associated with any given field name).
1.194 + """
1.195 +
1.196 + storage = FieldStorage(fp=self.get_request_stream(), keep_blank_values=1)
1.197 +
1.198 + # Avoid strange design issues with FieldStorage by checking the internal
1.199 + # field list directly.
1.200 +
1.201 + fields = {}
1.202 + if storage.list is not None:
1.203 +
1.204 + # Traverse the storage, finding each field value.
1.205 +
1.206 + for field_name in storage.keys():
1.207 + fields[field_name] = storage.getlist(field_name)
1.208 + return fields
1.209 +
1.210 + def get_user(self):
1.211 +
1.212 + """
1.213 + A framework-specific method which extracts user information from the
1.214 + transaction.
1.215 +
1.216 + Returns a username as a string or None if no user is defined.
1.217 + """
1.218 +
1.219 + return self.env.get("REMOTE_USER")
1.220 +
1.221 + def get_cookies(self):
1.222 +
1.223 + """
1.224 + A framework-specific method which obtains cookie information from the
1.225 + request.
1.226 +
1.227 + Returns a dictionary mapping cookie names to cookie objects.
1.228 + """
1.229 +
1.230 + return self.cookies_in
1.231 +
1.232 + def get_cookie(self, cookie_name):
1.233 +
1.234 + """
1.235 + A framework-specific method which obtains cookie information from the
1.236 + request.
1.237 +
1.238 + Returns a cookie object for the given 'cookie_name' or None if no such
1.239 + cookie exists.
1.240 + """
1.241 +
1.242 + return self.cookies_in.get(cookie_name)
1.243 +
1.244 + # Response-related methods.
1.245 +
1.246 + def get_response_stream(self):
1.247 +
1.248 + """
1.249 + A framework-specific method which returns the response stream for
1.250 + the transaction.
1.251 + """
1.252 +
1.253 + # Return a stream which is later emptied into the real stream.
1.254 +
1.255 + return self.content
1.256 +
1.257 + def get_response_code(self):
1.258 +
1.259 + """
1.260 + Get the response code associated with the transaction. If no response
1.261 + code is defined, None is returned.
1.262 + """
1.263 +
1.264 + return self.response_code
1.265 +
1.266 + def set_response_code(self, response_code):
1.267 +
1.268 + """
1.269 + Set the 'response_code' using a numeric constant defined in the HTTP
1.270 + specification.
1.271 + """
1.272 +
1.273 + self.response_code = response_code
1.274 +
1.275 + def set_header_value(self, header, value):
1.276 +
1.277 + """
1.278 + Set the HTTP 'header' with the given 'value'.
1.279 + """
1.280 +
1.281 + # The header is not written out immediately due to the buffering in use.
1.282 +
1.283 + self.headers_out[header] = value
1.284 +
1.285 + def set_content_type(self, content_type):
1.286 +
1.287 + """
1.288 + A framework-specific method which sets the 'content_type' for the
1.289 + response.
1.290 + """
1.291 +
1.292 + # The content type has to be written as a header, before actual content,
1.293 + # but after the response line. This means that some kind of buffering is
1.294 + # required. Hence, we don't write the header out immediately.
1.295 +
1.296 + self.content_type = content_type
1.297 +
1.298 + # Higher level response-related methods.
1.299 +
1.300 + def set_cookie(self, cookie):
1.301 +
1.302 + """
1.303 + A framework-specific method which stores the given 'cookie' object in
1.304 + the response.
1.305 + """
1.306 +
1.307 + # NOTE: If multiple cookies of the same name could be specified, this
1.308 + # NOTE: could need changing.
1.309 +
1.310 + self.cookies_out[cookie.name] = cookie.value
1.311 +
1.312 + def set_cookie_value(self, name, value, path=None, expires=None):
1.313 +
1.314 + """
1.315 + A framework-specific method which stores a cookie with the given 'name'
1.316 + and 'value' in the response.
1.317 +
1.318 + The optional 'path' is a string which specifies the scope of the cookie,
1.319 + and the optional 'expires' parameter is a value compatible with the
1.320 + time.time function, and indicates the expiry date/time of the cookie.
1.321 + """
1.322 +
1.323 + self.cookies_out[name] = value
1.324 + if path is not None:
1.325 + self.cookies_out[name]["path"] = path
1.326 + if expires is not None:
1.327 + self.cookies_out[name]["expires"] = expires
1.328 +
1.329 + def delete_cookie(self, cookie_name):
1.330 +
1.331 + """
1.332 + A framework-specific method which adds to the response a request that
1.333 + the cookie with the given 'cookie_name' be deleted/discarded by the
1.334 + client.
1.335 + """
1.336 +
1.337 + # Create a special cookie, given that we do not know whether the browser
1.338 + # has been sent the cookie or not.
1.339 + # NOTE: Magic discovered in Webware.
1.340 +
1.341 + self.cookies_out[cookie_name] = ""
1.342 + self.cookies_out[cookie_name]["path"] = "/"
1.343 + self.cookies_out[cookie_name]["expires"] = 0
1.344 + self.cookies_out[cookie_name]["max-age"] = 0
1.345 +
1.346 +# vim: tabstop=4 expandtab shiftwidth=4