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