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 Returns the request stream for the transaction. 83 """ 84 85 return self.input 86 87 def get_request_method(self): 88 89 """ 90 Returns the request method. 91 """ 92 93 return self.env.get("REQUEST_METHOD") 94 95 def get_headers(self): 96 97 """ 98 Returns all request headers as a dictionary-like object mapping header 99 names to values. 100 """ 101 102 return Environment.get_headers(self.env) 103 104 def get_header_values(self, key): 105 106 """ 107 Returns a list of all request header values associated with the given 108 'key'. Note that according to RFC 2616, 'key' is treated as a 109 case-insensitive string. 110 """ 111 112 return self.convert_to_list(self.get_headers().get(key)) 113 114 def get_content_type(self): 115 116 """ 117 Returns the content type specified on the request, along with the 118 charset employed. 119 """ 120 121 return self.parse_content_type(self.env.get("CONTENT_TYPE")) 122 123 def get_content_charsets(self): 124 125 """ 126 Returns the character set preferences. 127 """ 128 129 return self.parse_content_preferences(None) 130 131 def get_content_languages(self): 132 133 """ 134 Returns extracted language information from the transaction. 135 """ 136 137 return self.parse_content_preferences(None) 138 139 def get_path(self): 140 141 """ 142 Returns the entire path from the request. 143 """ 144 145 path = self.get_path_without_query() 146 qs = self.get_query_string() 147 if qs: 148 path += "?" 149 path += qs 150 return path 151 152 def get_path_without_query(self): 153 154 """ 155 Returns the entire path from the request minus the query string. 156 """ 157 158 path = self.env.get("SCRIPT_NAME") or "" 159 if self.env.has_key("PATH_INFO"): 160 path += self.env["PATH_INFO"] 161 return path 162 163 def get_path_info(self): 164 165 """ 166 Returns the "path info" (the part of the URL after the resource name 167 handling the current request) from the request. 168 """ 169 170 return self.env.get("PATH_INFO") or "" 171 172 def get_query_string(self): 173 174 """ 175 Returns the query string from the path in the request. 176 """ 177 178 return self.env.get("QUERY_STRING") or "" 179 180 # Higher level request-related methods. 181 182 def get_fields_from_path(self): 183 184 """ 185 Extracts the form fields from the path specified in the transaction. The 186 underlying framework may refuse to supply fields from the path if 187 handling a POST 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 return parse_qs(self.get_query_string(), keep_blank_values=1) 194 195 def get_fields_from_body(self, encoding=None): 196 197 """ 198 Extracts the form fields from the message body in the transaction. The 199 optional 'encoding' parameter specifies the character encoding of the 200 message body for cases where no such information is available, but where 201 the default encoding is to be overridden. 202 203 Returns a dictionary mapping field names to lists of values (even if a 204 single value is associated with any given field name). Each value is 205 either a Unicode object (representing a simple form field, for example) 206 or a file-like object (representing a file upload form field, for 207 example). 208 """ 209 210 encoding = self.get_content_type().charset or encoding or "iso-8859-1" 211 212 if self.storage_body is None: 213 self.storage_body = FieldStorage(fp=self.get_request_stream(), keep_blank_values=1) 214 215 # Avoid strange design issues with FieldStorage by checking the internal 216 # field list directly. 217 218 fields = {} 219 if self.storage_body.list is not None: 220 221 # Traverse the storage, finding each field value. 222 223 for field_name in self.storage_body.keys(): 224 fields[field_name] = [] 225 for field_value in self.storage_body.getlist(field_name): 226 if hasattr(field_value, "file"): 227 fields[field_name].append(field_value.file) 228 else: 229 fields[field_name].append(unicode(field_value, encoding)) 230 return fields 231 232 def get_user(self): 233 234 """ 235 Extracts user information from the transaction. 236 237 Returns a username as a string or None if no user is defined. 238 """ 239 240 if self.user is not None: 241 return self.user 242 else: 243 return self.env.get("REMOTE_USER") 244 245 def get_cookies(self): 246 247 """ 248 Obtains cookie information from the request. 249 250 Returns a dictionary mapping cookie names to cookie objects. 251 """ 252 253 return self.cookies_in 254 255 def get_cookie(self, cookie_name): 256 257 """ 258 Obtains cookie information from the request. 259 260 Returns a cookie object for the given 'cookie_name' or None if no such 261 cookie exists. 262 """ 263 264 return self.cookies_in.get(cookie_name) 265 266 # Response-related methods. 267 268 def get_response_stream(self): 269 270 """ 271 Returns the response stream for the transaction. 272 """ 273 274 # Return a stream which is later emptied into the real stream. 275 # Unicode can upset this operation. Using either the specified charset, 276 # the same charset as that used in the request, or a default encoding. 277 278 encoding = self.get_content_type().charset or "utf-8" 279 if self.content_type: 280 encoding = self.content_type.charset or encoding 281 return ConvertingStream(self.content, encoding) 282 283 def get_response_code(self): 284 285 """ 286 Get the response code associated with the transaction. If no response 287 code is defined, None is returned. 288 """ 289 290 return self.response_code 291 292 def set_response_code(self, response_code): 293 294 """ 295 Set the 'response_code' using a numeric constant defined in the HTTP 296 specification. 297 """ 298 299 self.response_code = response_code 300 301 def set_header_value(self, header, value): 302 303 """ 304 Set the HTTP 'header' with the given 'value'. 305 """ 306 307 # The header is not written out immediately due to the buffering in use. 308 309 self.headers_out[header] = value 310 311 def set_content_type(self, content_type): 312 313 """ 314 Sets the 'content_type' for the response. 315 """ 316 317 # The content type has to be written as a header, before actual content, 318 # but after the response line. This means that some kind of buffering is 319 # required. Hence, we don't write the header out immediately. 320 321 self.content_type = content_type 322 323 # Higher level response-related methods. 324 325 def set_cookie(self, cookie): 326 327 """ 328 Stores the given 'cookie' object in the response. 329 """ 330 331 # NOTE: If multiple cookies of the same name could be specified, this 332 # NOTE: could need changing. 333 334 self.cookies_out[cookie.name] = cookie.value 335 336 def set_cookie_value(self, name, value, path=None, expires=None): 337 338 """ 339 Stores a cookie with the given 'name' and 'value' in the response. 340 341 The optional 'path' is a string which specifies the scope of the cookie, 342 and the optional 'expires' parameter is a value compatible with the 343 time.time function, and indicates the expiry date/time of the cookie. 344 """ 345 346 self.cookies_out[name] = value 347 if path is not None: 348 self.cookies_out[name]["path"] = path 349 if expires is not None: 350 self.cookies_out[name]["expires"] = expires 351 352 def delete_cookie(self, cookie_name): 353 354 """ 355 Adds to the response a request that the cookie with the given 356 'cookie_name' be deleted/discarded by the client. 357 """ 358 359 # Create a special cookie, given that we do not know whether the browser 360 # has been sent the cookie or not. 361 # NOTE: Magic discovered in Webware. 362 363 self.cookies_out[cookie_name] = "" 364 self.cookies_out[cookie_name]["path"] = "/" 365 self.cookies_out[cookie_name]["expires"] = 0 366 self.cookies_out[cookie_name]["max-age"] = 0 367 368 # Application-specific methods. 369 370 def set_user(self, username): 371 372 """ 373 An application-specific method which sets the user information with 374 'username' in the transaction. This affects subsequent calls to 375 'get_user'. 376 """ 377 378 self.user = username 379 380 # vim: tabstop=4 expandtab shiftwidth=4