1 #!/usr/bin/env python 2 3 """ 4 mod_python classes. 5 """ 6 7 import Generic 8 from Helpers.Request import get_body_field, filter_fields, Cookie 9 from Helpers.Response import ConvertingStream 10 from mod_python.util import parse_qs, FieldStorage 11 from mod_python import apache 12 13 # Provide alternative implementations. 14 # The alternative session support requires cookie support of some kind. 15 16 try: 17 from mod_python.Cookie import get_cookies, add_cookie, Cookie as SimpleCookie 18 have_cookies = 1 19 except ImportError: 20 from Cookie import SimpleCookie 21 have_cookies = 0 22 try: 23 from mod_python import Session 24 except ImportError: 25 from Helpers.Session import SessionStore 26 import os 27 Session = None 28 29 class Transaction(Generic.Transaction): 30 31 """ 32 mod_python transaction interface. 33 """ 34 35 def __init__(self, trans): 36 37 "Initialise the transaction using the mod_python transaction 'trans'." 38 39 self.trans = trans 40 self.response_code = apache.OK 41 self.content_type = None 42 self.user = None 43 self.path_info = None 44 45 # Support non-framework cookies. 46 47 if not have_cookies: 48 49 # Define the incoming cookies. 50 51 self.cookies_in = SimpleCookie(self.get_headers().get("cookie")) 52 53 # Cached information. 54 55 self.storage_body = None 56 57 # Special objects retained throughout the transaction. 58 59 self.session_store = None 60 self.session = None # mod_python native session 61 62 def commit(self): 63 64 """ 65 A special method, synchronising the transaction with framework-specific 66 objects. 67 """ 68 69 # Close the session store. 70 71 if self.session_store is not None: 72 self.session_store.close() 73 74 # Commit any native session. 75 76 if self.session is not None: 77 self.session.save() 78 79 # Server-related methods. 80 81 def get_server_name(self): 82 83 "Returns the server name." 84 85 return self.trans.connection.local_addr[0] 86 87 def get_server_port(self): 88 89 "Returns the server port as a string." 90 91 return str(self.trans.connection.local_addr[1]) 92 93 # Request-related methods. 94 95 def get_request_stream(self): 96 97 """ 98 Returns the request stream for the transaction. 99 """ 100 101 return self.trans 102 103 def get_request_method(self): 104 105 """ 106 Returns the request method. 107 """ 108 109 return self.trans.method 110 111 def get_headers(self): 112 113 """ 114 Returns all request headers as a dictionary-like object mapping header 115 names to values. 116 117 NOTE: If duplicate header names are permitted, then this interface will 118 NOTE: need to change. 119 """ 120 121 return self.trans.headers_in 122 123 def get_header_values(self, key): 124 125 """ 126 Returns a list of all request header values associated with the given 127 'key'. Note that according to RFC 2616, 'key' is treated as a 128 case-insensitive string. 129 """ 130 131 return self.convert_to_list(self.trans.headers_in.get(key)) 132 133 def get_content_type(self): 134 135 """ 136 Returns the content type specified on the request, along with the 137 charset employed. 138 """ 139 140 return self.parse_content_type(self.trans.content_type) 141 142 def get_content_charsets(self): 143 144 """ 145 Returns the character set preferences. 146 """ 147 148 return self.parse_content_preferences(self.trans.headers_in.get("Accept-Charset")) 149 150 def get_content_languages(self): 151 152 """ 153 Returns extracted language information from the transaction. 154 """ 155 156 return self.parse_content_preferences(self.trans.headers_in.get("Accept-Language")) 157 158 def get_path(self): 159 160 """ 161 Returns the entire path from the request. 162 """ 163 164 query_string = self.get_query_string() 165 if query_string: 166 return self.trans.uri + "?" + query_string 167 else: 168 return self.trans.uri 169 170 def get_path_without_query(self): 171 172 """ 173 Returns the entire path from the request minus the query string. 174 """ 175 176 return self.trans.uri 177 178 def get_path_info(self): 179 180 """ 181 Returns the "path info" (the part of the URL after the resource name 182 handling the current request) from the request. 183 """ 184 185 return self.trans.path_info 186 187 def get_query_string(self): 188 189 """ 190 Returns the query string from the path in the request. 191 """ 192 193 return self.trans.args or "" 194 195 # Higher level request-related methods. 196 197 def get_fields_from_path(self): 198 199 """ 200 Extracts fields (or request parameters) from the path specified in the 201 transaction. The underlying framework may refuse to supply fields from 202 the path if handling a POST transaction. 203 204 Returns a dictionary mapping field names to lists of values (even if a 205 single value is associated with any given field name). 206 """ 207 208 # NOTE: Support at best ISO-8859-1 values. 209 210 fields = {} 211 for name, values in parse_qs(self.get_query_string(), 1).items(): # keep_blank_values=1 212 fields[name] = [] 213 for value in values: 214 fields[name].append(unicode(value, "iso-8859-1")) 215 return fields 216 217 def get_fields_from_body(self, encoding=None): 218 219 """ 220 Extracts fields (or request parameters) from the message body in the 221 transaction. The optional 'encoding' parameter specifies the character 222 encoding of the message body for cases where no such information is 223 available, but where the default encoding is to be overridden. 224 225 Returns a dictionary mapping field names to lists of values (even if a 226 single value is associated with any given field name). Each value is 227 either a Unicode object (representing a simple form field, for example) 228 or a plain string (representing a file upload form field, for example). 229 230 The mod_python.util.FieldStorage class may augment the fields from the 231 body with fields found in the path. 232 """ 233 234 all_fields = self._get_fields(encoding) 235 fields_from_path = self.get_fields_from_path() 236 return filter_fields(all_fields, fields_from_path) 237 238 def _get_fields(self, encoding=None): 239 encoding = encoding or self.get_content_type().charset or self.default_charset 240 241 if self.storage_body is None: 242 self.storage_body = FieldStorage(self.trans, keep_blank_values=1) 243 244 # Traverse the storage, finding each field value. 245 246 fields = {} 247 for field in self.storage_body.list: 248 if not fields.has_key(field.name): 249 fields[field.name] = [] 250 fields[field.name].append(get_body_field(field.value, encoding)) 251 return fields 252 253 def get_fields(self, encoding=None): 254 255 """ 256 Extracts fields (or request parameters) from both the path specified in 257 the transaction as well as the message body. The optional 'encoding' 258 parameter specifies the character encoding of the message body for cases 259 where no such information is available, but where the default encoding 260 is to be overridden. 261 262 Returns a dictionary mapping field names to lists of values (even if a 263 single value is associated with any given field name). Each value is 264 either a Unicode object (representing a simple form field, for example) 265 or a plain string (representing a file upload form field, for example). 266 267 Where a given field name is used in both the path and message body to 268 specify values, the values from both sources will be combined into a 269 single list associated with that field name. 270 """ 271 272 return self._get_fields(encoding) 273 274 def get_user(self): 275 276 """ 277 Extracts user information from the transaction. 278 279 Returns a username as a string or None if no user is defined. 280 """ 281 282 if self.user is not None: 283 return self.user 284 else: 285 return self.trans.user 286 287 def get_cookies(self): 288 289 """ 290 Obtains cookie information from the request. 291 292 Returns a dictionary mapping cookie names to cookie objects. 293 294 NOTE: No additional information is passed to the underlying API despite 295 NOTE: support for enhanced cookies in mod_python. 296 """ 297 298 if have_cookies: 299 found_cookies = get_cookies(self.trans) 300 else: 301 found_cookies = self.cookies_in 302 return self.process_cookies(found_cookies) 303 304 def get_cookie(self, cookie_name): 305 306 """ 307 Obtains cookie information from the request. 308 309 Returns a cookie object for the given 'cookie_name' or None if no such 310 cookie exists. 311 """ 312 313 return self.get_cookies().get(self.encode_cookie_value(cookie_name)) 314 315 # Response-related methods. 316 317 def get_response_stream(self): 318 319 """ 320 Returns the response stream for the transaction. 321 """ 322 323 # Unicode can upset this operation. Using either the specified charset 324 # or a default encoding. 325 326 encoding = self.get_response_stream_encoding() 327 return ConvertingStream(self.trans, encoding) 328 329 def get_response_stream_encoding(self): 330 331 """ 332 Returns the response stream encoding. 333 """ 334 335 if self.content_type: 336 encoding = self.content_type.charset 337 else: 338 encoding = None 339 return encoding or self.default_charset 340 341 def get_response_code(self): 342 343 """ 344 Get the response code associated with the transaction. If no response 345 code is defined, None is returned. 346 """ 347 348 return self.response_code 349 350 def set_response_code(self, response_code): 351 352 """ 353 Set the 'response_code' using a numeric constant defined in the HTTP 354 specification. 355 """ 356 357 self.response_code = response_code 358 359 def set_header_value(self, header, value): 360 361 """ 362 Set the HTTP 'header' with the given 'value'. 363 """ 364 365 self.trans.headers_out[self.format_header_value(header)] = self.format_header_value(value) 366 367 def set_content_type(self, content_type): 368 369 """ 370 Sets the 'content_type' for the response. 371 """ 372 373 # Remember the content type for encoding purposes later. 374 375 self.content_type = content_type 376 self.trans.content_type = str(content_type) 377 378 # Higher level response-related methods. 379 380 def set_cookie(self, cookie): 381 382 """ 383 Stores the given 'cookie' object in the response. 384 """ 385 386 # NOTE: If multiple cookies of the same name could be specified, this 387 # NOTE: could need changing. 388 389 self.set_cookie_value(cookie.name, cookie.value) 390 391 def set_cookie_value(self, name, value, path=None, expires=None): 392 393 """ 394 Stores a cookie with the given 'name' and 'value' in the response. 395 396 The optional 'path' is a string which specifies the scope of the cookie, 397 and the optional 'expires' parameter is a value compatible with the 398 time.time function, and indicates the expiry date/time of the cookie. 399 """ 400 401 name = self.encode_cookie_value(name) 402 403 if have_cookies: 404 cookie = SimpleCookie(name, self.encode_cookie_value(value)) 405 if expires is not None: 406 cookie.expires = expires 407 if path is not None: 408 cookie.path = path 409 add_cookie(self.trans, cookie) 410 else: 411 cookie_out = SimpleCookie() 412 cookie_out[name] = self.encode_cookie_value(value) 413 if path is not None: 414 cookie_out[name]["path"] = path 415 if expires is not None: 416 cookie_out[name]["expires"] = expires 417 self._write_cookie(cookie_out) 418 419 def delete_cookie(self, cookie_name): 420 421 """ 422 Adds to the response a request that the cookie with the given 423 'cookie_name' be deleted/discarded by the client. 424 """ 425 426 # Create a special cookie, given that we do not know whether the browser 427 # has been sent the cookie or not. 428 # NOTE: Magic discovered in Webware. 429 430 name = self.encode_cookie_value(cookie_name) 431 432 if have_cookies: 433 cookie = SimpleCookie(name, "") 434 cookie.path = "/" 435 cookie.expires = 0 436 cookie.max_age = 0 437 add_cookie(self.trans, cookie) 438 else: 439 cookie_out = SimpleCookie() 440 cookie_out[name] = "" 441 cookie_out[name]["path"] = "/" 442 cookie_out[name]["expires"] = 0 443 cookie_out[name]["max-age"] = 0 444 self._write_cookie(cookie_out) 445 446 def _write_cookie(self, cookie): 447 448 "An internal method adding the given 'cookie' to the headers." 449 450 # NOTE: May not be using the appropriate method. 451 452 for morsel in cookie.values(): 453 self.set_header_value("Set-Cookie", morsel.OutputString()) 454 455 # Session-related methods. 456 457 def get_session(self, create=1): 458 459 """ 460 Gets a session corresponding to an identifier supplied in the 461 transaction. 462 463 If no session has yet been established according to information 464 provided in the transaction then the optional 'create' parameter 465 determines whether a new session will be established. 466 467 Where no session has been established and where 'create' is set to 0 468 then None is returned. In all other cases, a session object is created 469 (where appropriate) and returned. 470 """ 471 472 if Session: 473 # NOTE: Not exposing all functionality. 474 self.session = Session.Session(self.trans) 475 self.session.load() 476 return self.session 477 else: 478 # NOTE: Requires configuration. 479 480 if self.session_store is None: 481 self.session_store = SessionStore(self, os.path.join(apache.server_root(), "WebStack-sessions")) 482 return self.session_store.get_session(create) 483 484 def expire_session(self): 485 486 """ 487 Expires any session established according to information provided in the 488 transaction. 489 """ 490 491 if Session: 492 if self.session is None: 493 self.session = self.get_session(create=0) 494 if self.session: 495 self.session.invalidate() 496 self.session = None 497 else: 498 # NOTE: Requires configuration. 499 500 if self.session_store is None: 501 self.session_store = SessionStore(self, os.path.join(apache.server_root(), "WebStack-sessions")) 502 self.session_store.expire_session() 503 504 # vim: tabstop=4 expandtab shiftwidth=4