1 #!/usr/bin/env python 2 3 """ 4 BaseHTTPRequestHandler classes. 5 6 Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 import Generic 24 from Helpers.Request import MessageBodyStream, get_body_fields, get_storage_items, Cookie 25 from Helpers.Response import ConvertingStream 26 from Helpers.Auth import UserInfo 27 from Helpers.Session import SessionStore 28 from cgi import parse_qs, FieldStorage 29 from Cookie import SimpleCookie 30 from StringIO import StringIO 31 32 class Transaction(Generic.Transaction): 33 34 """ 35 BaseHTTPRequestHandler transaction interface. 36 """ 37 38 def __init__(self, trans): 39 40 """ 41 Initialise the transaction using the BaseHTTPRequestHandler instance 42 'trans'. 43 """ 44 45 self.trans = trans 46 47 # Other attributes of interest in instances of this class. 48 49 self.content_type = None 50 self.response_code = 200 51 self.content = StringIO() 52 self.headers_out = {} 53 self.cookies_out = SimpleCookie() 54 self.user = None 55 self.path_info = None 56 57 # Define the incoming cookies. 58 59 self.cookies_in = SimpleCookie(self.get_headers().get("cookie")) 60 61 # Cached information. 62 63 self.storage_body = None 64 65 # Special objects retained throughout the transaction. 66 67 self.session_store = None 68 69 def commit(self): 70 71 """ 72 A special method, synchronising the transaction with framework-specific 73 objects. 74 """ 75 76 # Close the session store. 77 78 if self.session_store is not None: 79 self.session_store.close() 80 81 # Prepare the response. 82 83 self.trans.send_response(self.response_code) 84 if self.content_type is not None: 85 self.trans.send_header("Content-Type", str(self.content_type)) 86 87 for header, value in self.headers_out.items(): 88 self.trans.send_header(self.format_header_value(header), self.format_header_value(value)) 89 90 # NOTE: May not be using the appropriate method. 91 92 for morsel in self.cookies_out.values(): 93 self.trans.send_header("Set-Cookie", morsel.OutputString()) 94 95 # Add possibly missing content length information. 96 # NOTE: This is really inefficient, but we need to buffer things to 97 # NOTE: permit out of order header setting. 98 99 self.content.seek(0) 100 content = self.content.read() 101 102 if not self.headers_out.has_key("Content-Length"): 103 self.trans.send_header("Content-Length", str(len(content))) 104 105 self.trans.end_headers() 106 self.trans.wfile.write(content) 107 108 # Server-related methods. 109 110 def get_server_name(self): 111 112 "Returns the server name." 113 114 # As ultimately found in SocketServer.BaseServer via SocketServer.BaseRequestHandler. 115 # NOTE: We assume that any usage of "" can be taken to mean "localhost". 116 117 return self.trans.server.server_address[0] or "localhost" 118 119 def get_server_port(self): 120 121 "Returns the server port as a string." 122 123 # As ultimately found in SocketServer.BaseServer via SocketServer.BaseRequestHandler. 124 125 return str(self.trans.server.server_address[1]) 126 127 # Request-related methods. 128 129 def get_request_stream(self): 130 131 """ 132 Returns the request stream for the transaction. 133 """ 134 135 return MessageBodyStream(self.trans.rfile, self.get_headers()) 136 137 def get_request_method(self): 138 139 """ 140 Returns the request method. 141 """ 142 143 return self.trans.command 144 145 def get_headers(self): 146 147 """ 148 Returns all request headers as a dictionary-like object mapping header 149 names to values. 150 151 NOTE: If duplicate header names are permitted, then this interface will 152 NOTE: need to change. 153 """ 154 155 return self.trans.headers 156 157 def get_header_values(self, key): 158 159 """ 160 Returns a list of all request header values associated with the given 161 'key'. Note that according to RFC 2616, 'key' is treated as a 162 case-insensitive string. 163 """ 164 165 return self.convert_to_list(self.trans.headers.get(key)) 166 167 def get_content_type(self): 168 169 """ 170 Returns the content type specified on the request, along with the 171 charset employed. 172 """ 173 174 return self.parse_content_type(self.trans.headers.get("content-type")) 175 176 def get_content_charsets(self): 177 178 """ 179 Returns the character set preferences. 180 """ 181 182 return self.parse_content_preferences(self.trans.headers.get("accept-charset")) 183 184 def get_content_languages(self): 185 186 """ 187 Returns extracted language information from the transaction. 188 """ 189 190 return self.parse_content_preferences(self.trans.headers.get("accept-language")) 191 192 def get_path(self): 193 194 """ 195 Returns the entire path from the request. 196 """ 197 198 return self.trans.path 199 200 def get_path_without_query(self): 201 202 """ 203 Returns the entire path from the request minus the query string. 204 """ 205 206 # Remove the query string from the end of the path. 207 208 return self.trans.path.split("?")[0] 209 210 def get_path_info(self): 211 212 """ 213 Returns the "path info" (the part of the URL after the resource name 214 handling the current request) from the request. 215 """ 216 217 return self.get_path_without_query() 218 219 def get_query_string(self): 220 221 """ 222 Returns the query string from the path in the request. 223 """ 224 225 t = self.trans.path.split("?") 226 if len(t) == 1: 227 return "" 228 else: 229 230 # NOTE: Overlook erroneous usage of "?" characters in the path. 231 232 return "?".join(t[1:]) 233 234 # Higher level request-related methods. 235 236 def get_fields_from_path(self): 237 238 """ 239 Extracts fields (or request parameters) from the path specified in the 240 transaction. The underlying framework may refuse to supply fields from 241 the path if handling a POST transaction. 242 243 Returns a dictionary mapping field names to lists of values (even if a 244 single value is associated with any given field name). 245 """ 246 247 # NOTE: Support at best ISO-8859-1 values. 248 249 fields = {} 250 for name, values in parse_qs(self.get_query_string(), keep_blank_values=1).items(): 251 fields[name] = [] 252 for value in values: 253 fields[name].append(unicode(value, "iso-8859-1")) 254 return fields 255 256 def get_fields_from_body(self, encoding=None): 257 258 """ 259 Extracts fields (or request parameters) from the message body in the 260 transaction. The optional 'encoding' parameter specifies the character 261 encoding of the message body for cases where no such information is 262 available, but where the default encoding is to be overridden. 263 264 Returns a dictionary mapping field names to lists of values (even if a 265 single value is associated with any given field name). Each value is 266 either a Unicode object (representing a simple form field, for example) 267 or a plain string (representing a file upload form field, for example). 268 """ 269 270 encoding = encoding or self.get_content_type().charset or self.default_charset 271 272 if self.storage_body is None: 273 self.storage_body = FieldStorage(fp=self.get_request_stream(), headers=self.get_headers(), 274 environ={"REQUEST_METHOD" : self.get_request_method()}, keep_blank_values=1) 275 276 # Avoid strange design issues with FieldStorage by checking the internal 277 # field list directly. 278 279 fields = {} 280 if self.storage_body.list is not None: 281 282 # Traverse the storage, finding each field value. 283 284 fields = get_body_fields(get_storage_items(self.storage_body), encoding) 285 286 return fields 287 288 def get_fields(self, encoding=None): 289 290 """ 291 Extracts fields (or request parameters) from both the path specified in 292 the transaction as well as the message body. The optional 'encoding' 293 parameter specifies the character encoding of the message body for cases 294 where no such information is available, but where the default encoding 295 is to be overridden. 296 297 Returns a dictionary mapping field names to lists of values (even if a 298 single value is associated with any given field name). Each value is 299 either a Unicode object (representing a simple form field, for example) 300 or a plain string (representing a file upload form field, for example). 301 302 Where a given field name is used in both the path and message body to 303 specify values, the values from both sources will be combined into a 304 single list associated with that field name. 305 """ 306 307 # Combine the two sources. 308 309 fields = {} 310 fields.update(self.get_fields_from_path()) 311 for name, values in self.get_fields_from_body(encoding).items(): 312 if not fields.has_key(name): 313 fields[name] = values 314 else: 315 fields[name] += values 316 return fields 317 318 def get_user(self): 319 320 """ 321 Extracts user information from the transaction. 322 323 Returns a username as a string or None if no user is defined. 324 """ 325 326 if self.user is not None: 327 return self.user 328 329 auth_header = self.get_headers().get("authorization") 330 if auth_header: 331 return UserInfo(auth_header).username 332 else: 333 return None 334 335 def get_cookies(self): 336 337 """ 338 Obtains cookie information from the request. 339 340 Returns a dictionary mapping cookie names to cookie objects. 341 """ 342 343 return self.process_cookies(self.cookies_in) 344 345 def get_cookie(self, cookie_name): 346 347 """ 348 Obtains cookie information from the request. 349 350 Returns a cookie object for the given 'cookie_name' or None if no such 351 cookie exists. 352 """ 353 354 cookie = self.cookies_in.get(self.encode_cookie_value(cookie_name)) 355 if cookie is not None: 356 return Cookie(cookie_name, self.decode_cookie_value(cookie.value)) 357 else: 358 return None 359 360 # Response-related methods. 361 362 def get_response_stream(self): 363 364 """ 365 Returns the response stream for the transaction. 366 """ 367 368 # Return a stream which is later emptied into the real stream. 369 # Unicode can upset this operation. Using either the specified charset 370 # or a default encoding. 371 372 encoding = self.get_response_stream_encoding() 373 return ConvertingStream(self.content, encoding) 374 375 def get_response_stream_encoding(self): 376 377 """ 378 Returns the response stream encoding. 379 """ 380 381 if self.content_type: 382 encoding = self.content_type.charset 383 else: 384 encoding = None 385 return encoding or self.default_charset 386 387 def get_response_code(self): 388 389 """ 390 Get the response code associated with the transaction. If no response 391 code is defined, None is returned. 392 """ 393 394 return self.response_code 395 396 def set_response_code(self, response_code): 397 398 """ 399 Set the 'response_code' using a numeric constant defined in the HTTP 400 specification. 401 """ 402 403 self.response_code = response_code 404 405 def set_header_value(self, header, value): 406 407 """ 408 Set the HTTP 'header' with the given 'value'. 409 """ 410 411 # The header is not written out immediately due to the buffering in use. 412 413 self.headers_out[header] = value 414 415 def set_content_type(self, content_type): 416 417 """ 418 Sets the 'content_type' for the response. 419 """ 420 421 # The content type has to be written as a header, before actual content, 422 # but after the response line. This means that some kind of buffering is 423 # required. Hence, we don't write the header out immediately. 424 425 self.content_type = content_type 426 427 # Higher level response-related methods. 428 429 def set_cookie(self, cookie): 430 431 """ 432 Stores the given 'cookie' object in the response. 433 """ 434 435 # NOTE: If multiple cookies of the same name could be specified, this 436 # NOTE: could need changing. 437 438 self.set_cookie_value(cookie.name, cookie.value) 439 440 def set_cookie_value(self, name, value, path=None, expires=None): 441 442 """ 443 Stores a cookie with the given 'name' and 'value' in the response. 444 445 The optional 'path' is a string which specifies the scope of the cookie, 446 and the optional 'expires' parameter is a value compatible with the 447 time.time function, and indicates the expiry date/time of the cookie. 448 """ 449 450 name = self.encode_cookie_value(name) 451 self.cookies_out[name] = self.encode_cookie_value(value) 452 if path is not None: 453 self.cookies_out[name]["path"] = path 454 if expires is not None: 455 self.cookies_out[name]["expires"] = expires 456 457 def delete_cookie(self, cookie_name): 458 459 """ 460 Adds to the response a request that the cookie with the given 461 'cookie_name' be deleted/discarded by the client. 462 """ 463 464 # Create a special cookie, given that we do not know whether the browser 465 # has been sent the cookie or not. 466 # NOTE: Magic discovered in Webware. 467 468 name = self.encode_cookie_value(cookie_name) 469 self.cookies_out[name] = "" 470 self.cookies_out[name]["path"] = "/" 471 self.cookies_out[name]["expires"] = 0 472 self.cookies_out[name]["max-age"] = 0 473 474 # Session-related methods. 475 476 def get_session(self, create=1): 477 478 """ 479 Gets a session corresponding to an identifier supplied in the 480 transaction. 481 482 If no session has yet been established according to information 483 provided in the transaction then the optional 'create' parameter 484 determines whether a new session will be established. 485 486 Where no session has been established and where 'create' is set to 0 487 then None is returned. In all other cases, a session object is created 488 (where appropriate) and returned. 489 """ 490 491 # NOTE: Requires configuration. 492 493 if self.session_store is None: 494 self.session_store = SessionStore(self, "WebStack-sessions") 495 return self.session_store.get_session(create) 496 497 def expire_session(self): 498 499 """ 500 Expires any session established according to information provided in the 501 transaction. 502 """ 503 504 # NOTE: Requires configuration. 505 506 if self.session_store is None: 507 self.session_store = SessionStore(self, "WebStack-sessions") 508 self.session_store.expire_session() 509 510 # vim: tabstop=4 expandtab shiftwidth=4