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