1 #!/usr/bin/env python 2 3 """ 4 CGI classes. 5 """ 6 7 import Generic 8 import os, sys 9 from Helpers.Request import MessageBodyStream 10 from Helpers.Auth import UserInfo 11 from Helpers import Environment 12 from cgi import parse_qs, FieldStorage 13 import Cookie 14 from StringIO import StringIO 15 16 class Transaction(Generic.Transaction): 17 18 """ 19 CGI transaction interface. 20 """ 21 22 def __init__(self, input=None, output=None, env=None): 23 24 """ 25 Initialise the transaction using the CGI 'input' and 'output' streams. 26 These streams are optional and default to standard input and standard 27 output respectively. 28 """ 29 30 self.input = input or sys.stdin 31 self.output = output or sys.stdout 32 self.env = env or os.environ 33 34 # Other attributes of interest in instances of this class. 35 36 self.content_type = None 37 self.response_code = 200 38 self.content = StringIO() 39 self.headers_out = {} 40 self.cookies_out = Cookie.SimpleCookie() 41 42 # Define the incoming cookies. 43 44 self.cookies_in = Cookie.SimpleCookie(self.env.get("HTTP_COOKIE")) 45 46 def commit(self): 47 48 """ 49 A special method, synchronising the transaction with framework-specific 50 objects. 51 52 See draft-coar-cgi-v11-03, section 7. 53 """ 54 55 # NOTE: Provide sensible messages. 56 57 self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status")) 58 if self.content_type is not None: 59 self.output.write("Content-type: %s\n" % self.format_content_type(self.content_type)) 60 for header, value in self.headers_out.items(): 61 self.output.write("%s: %s\n" % 62 (self.format_header_value(header), self.format_header_value(value)) 63 ) 64 self.output.write(str(self.cookies_out)) 65 self.output.write("\n") 66 67 self.content.seek(0) 68 self.output.write(self.content.read()) 69 70 # Request-related methods. 71 72 def get_request_stream(self): 73 74 """ 75 A framework-specific method which returns the request stream for 76 the transaction. 77 """ 78 79 return self.input 80 81 def get_request_method(self): 82 83 """ 84 A framework-specific method which gets the request method. 85 """ 86 87 return self.env.get("REQUEST_METHOD") 88 89 def get_headers(self): 90 91 """ 92 A framework-specific method which returns all request headers as a 93 dictionary-like object mapping header names to values. 94 """ 95 96 return Environment.get_headers(self.env) 97 98 def get_header_values(self, key): 99 100 """ 101 A framework-specific method which returns a list of all request header 102 values associated with the given 'key'. Note that according to RFC 2616, 103 'key' is treated as a case-insensitive string. 104 """ 105 106 return self.convert_to_list(self.get_headers().get(key)) 107 108 def get_content_type(self): 109 110 """ 111 A framework-specific method which gets the content type specified on the 112 request, along with the charset employed. 113 """ 114 115 return self.parse_content_type(self.env.get("CONTENT_TYPE")) 116 117 def get_content_charsets(self): 118 119 """ 120 Returns the character set preferences. 121 """ 122 123 return self.parse_content_preferences(None) 124 125 def get_content_languages(self): 126 127 """ 128 A framework-specific method which extracts language information from 129 the transaction. 130 """ 131 132 return self.parse_content_preferences(None) 133 134 def get_path(self): 135 136 """ 137 A framework-specific method which gets the entire path from the request. 138 """ 139 140 path = self.env.get("SCRIPT_NAME") or "" 141 if self.env.has_key("PATH_INFO"): 142 path += self.env["PATH_INFO"] 143 qs = self.env.get("QUERY_STRING") 144 if qs: 145 path += "?" 146 path += qs 147 return path 148 149 def get_path_info(self): 150 151 """ 152 A framework-specific method which gets the "path info" (the part of the 153 URL after the resource name handling the current request) from the 154 request. 155 """ 156 157 return self.env.get("PATH_INFO") or "" 158 159 def get_query_string(self): 160 161 """ 162 A framework-specific method which gets the query string from the path in 163 the request. 164 """ 165 166 return self.env.get("QUERY_STRING") or "" 167 168 # Higher level request-related methods. 169 170 def get_fields_from_path(self): 171 172 """ 173 A framework-specific method which extracts the form fields from the 174 path specified in the transaction. The underlying framework may refuse 175 to supply fields from the path if handling a POST transaction. 176 177 Returns a dictionary mapping field names to lists of values (even if a 178 single value is associated with any given field name). 179 """ 180 181 return parse_qs(self.get_query_string(), keep_blank_values=1) 182 183 def get_fields_from_body(self): 184 185 """ 186 A framework-specific method which extracts the form fields from the 187 message body in the transaction. 188 189 Returns a dictionary mapping field names to lists of values (even if a 190 single value is associated with any given field name). 191 """ 192 193 storage = FieldStorage(fp=self.get_request_stream(), keep_blank_values=1) 194 195 # Avoid strange design issues with FieldStorage by checking the internal 196 # field list directly. 197 198 fields = {} 199 if storage.list is not None: 200 201 # Traverse the storage, finding each field value. 202 203 for field_name in storage.keys(): 204 fields[field_name] = storage.getlist(field_name) 205 return fields 206 207 def get_user(self): 208 209 """ 210 A framework-specific method which extracts user information from the 211 transaction. 212 213 Returns a username as a string or None if no user is defined. 214 """ 215 216 return self.env.get("REMOTE_USER") 217 218 def get_cookies(self): 219 220 """ 221 A framework-specific method which obtains cookie information from the 222 request. 223 224 Returns a dictionary mapping cookie names to cookie objects. 225 """ 226 227 return self.cookies_in 228 229 def get_cookie(self, cookie_name): 230 231 """ 232 A framework-specific method which obtains cookie information from the 233 request. 234 235 Returns a cookie object for the given 'cookie_name' or None if no such 236 cookie exists. 237 """ 238 239 return self.cookies_in.get(cookie_name) 240 241 # Response-related methods. 242 243 def get_response_stream(self): 244 245 """ 246 A framework-specific method which returns the response stream for 247 the transaction. 248 """ 249 250 # Return a stream which is later emptied into the real stream. 251 252 return self.content 253 254 def get_response_code(self): 255 256 """ 257 Get the response code associated with the transaction. If no response 258 code is defined, None is returned. 259 """ 260 261 return self.response_code 262 263 def set_response_code(self, response_code): 264 265 """ 266 Set the 'response_code' using a numeric constant defined in the HTTP 267 specification. 268 """ 269 270 self.response_code = response_code 271 272 def set_header_value(self, header, value): 273 274 """ 275 Set the HTTP 'header' with the given 'value'. 276 """ 277 278 # The header is not written out immediately due to the buffering in use. 279 280 self.headers_out[header] = value 281 282 def set_content_type(self, content_type): 283 284 """ 285 A framework-specific method which sets the 'content_type' for the 286 response. 287 """ 288 289 # The content type has to be written as a header, before actual content, 290 # but after the response line. This means that some kind of buffering is 291 # required. Hence, we don't write the header out immediately. 292 293 self.content_type = content_type 294 295 # Higher level response-related methods. 296 297 def set_cookie(self, cookie): 298 299 """ 300 A framework-specific method which stores the given 'cookie' object in 301 the response. 302 """ 303 304 # NOTE: If multiple cookies of the same name could be specified, this 305 # NOTE: could need changing. 306 307 self.cookies_out[cookie.name] = cookie.value 308 309 def set_cookie_value(self, name, value, path=None, expires=None): 310 311 """ 312 A framework-specific method which stores a cookie with the given 'name' 313 and 'value' in the response. 314 315 The optional 'path' is a string which specifies the scope of the cookie, 316 and the optional 'expires' parameter is a value compatible with the 317 time.time function, and indicates the expiry date/time of the cookie. 318 """ 319 320 self.cookies_out[name] = value 321 if path is not None: 322 self.cookies_out[name]["path"] = path 323 if expires is not None: 324 self.cookies_out[name]["expires"] = expires 325 326 def delete_cookie(self, cookie_name): 327 328 """ 329 A framework-specific method which adds to the response a request that 330 the cookie with the given 'cookie_name' be deleted/discarded by the 331 client. 332 """ 333 334 # Create a special cookie, given that we do not know whether the browser 335 # has been sent the cookie or not. 336 # NOTE: Magic discovered in Webware. 337 338 self.cookies_out[cookie_name] = "" 339 self.cookies_out[cookie_name]["path"] = "/" 340 self.cookies_out[cookie_name]["expires"] = 0 341 self.cookies_out[cookie_name]["max-age"] = 0 342 343 # vim: tabstop=4 expandtab shiftwidth=4