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