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 return self.get_cookies().get(self.encode_cookie_value(cookie_name)) 293 294 # Response-related methods. 295 296 def get_response_stream(self): 297 298 """ 299 Returns the response stream for the transaction. 300 """ 301 302 # Unicode can upset this operation. Using either the specified charset 303 # or a default encoding. 304 305 encoding = self.get_response_stream_encoding() 306 return ConvertingStream(self.trans, encoding) 307 308 def get_response_stream_encoding(self): 309 310 """ 311 Returns the response stream encoding. 312 """ 313 314 if self.content_type: 315 encoding = self.content_type.charset 316 else: 317 encoding = None 318 return encoding or self.default_charset 319 320 def get_response_code(self): 321 322 """ 323 Get the response code associated with the transaction. If no response 324 code is defined, None is returned. 325 """ 326 327 return self.response_code 328 329 def set_response_code(self, response_code): 330 331 """ 332 Set the 'response_code' using a numeric constant defined in the HTTP 333 specification. 334 """ 335 336 self.response_code = response_code 337 338 def set_header_value(self, header, value): 339 340 """ 341 Set the HTTP 'header' with the given 'value'. 342 """ 343 344 self.trans.headers_out[self.format_header_value(header)] = self.format_header_value(value) 345 346 def set_content_type(self, content_type): 347 348 """ 349 Sets the 'content_type' for the response. 350 """ 351 352 # Remember the content type for encoding purposes later. 353 354 self.content_type = content_type 355 self.trans.content_type = str(content_type) 356 357 # Higher level response-related methods. 358 359 def set_cookie(self, cookie): 360 361 """ 362 Stores the given 'cookie' object in the response. 363 """ 364 365 # NOTE: If multiple cookies of the same name could be specified, this 366 # NOTE: could need changing. 367 368 self.set_cookie_value(cookie.name, cookie.value) 369 370 def set_cookie_value(self, name, value, path=None, expires=None): 371 372 """ 373 Stores a cookie with the given 'name' and 'value' in the response. 374 375 The optional 'path' is a string which specifies the scope of the cookie, 376 and the optional 'expires' parameter is a value compatible with the 377 time.time function, and indicates the expiry date/time of the cookie. 378 """ 379 380 name = self.encode_cookie_value(name) 381 382 if have_cookies: 383 cookie = SimpleCookie(name, self.encode_cookie_value(value)) 384 if expires is not None: 385 cookie.expires = expires 386 if path is not None: 387 cookie.path = path 388 add_cookie(self.trans, cookie) 389 else: 390 cookie_out = SimpleCookie() 391 cookie_out[name] = self.encode_cookie_value(value) 392 if path is not None: 393 cookie_out[name]["path"] = path 394 if expires is not None: 395 cookie_out[name]["expires"] = expires 396 self._write_cookie(cookie_out) 397 398 def delete_cookie(self, cookie_name): 399 400 """ 401 Adds to the response a request that the cookie with the given 402 'cookie_name' be deleted/discarded by the client. 403 """ 404 405 # Create a special cookie, given that we do not know whether the browser 406 # has been sent the cookie or not. 407 # NOTE: Magic discovered in Webware. 408 409 name = self.encode_cookie_value(cookie_name) 410 411 if have_cookies: 412 cookie = SimpleCookie(name, "") 413 cookie.path = "/" 414 cookie.expires = 0 415 cookie.max_age = 0 416 add_cookie(self.trans, cookie) 417 else: 418 cookie_out = SimpleCookie() 419 cookie_out[name] = "" 420 cookie_out[name]["path"] = "/" 421 cookie_out[name]["expires"] = 0 422 cookie_out[name]["max-age"] = 0 423 self._write_cookie(cookie_out) 424 425 def _write_cookie(self, cookie): 426 427 "An internal method adding the given 'cookie' to the headers." 428 429 # NOTE: May not be using the appropriate method. 430 431 for morsel in cookie.values(): 432 self.set_header_value("Set-Cookie", morsel.OutputString()) 433 434 # Session-related methods. 435 436 def get_session(self, create=1): 437 438 """ 439 Gets a session corresponding to an identifier supplied in the 440 transaction. 441 442 If no session has yet been established according to information 443 provided in the transaction then the optional 'create' parameter 444 determines whether a new session will be established. 445 446 Where no session has been established and where 'create' is set to 0 447 then None is returned. In all other cases, a session object is created 448 (where appropriate) and returned. 449 """ 450 451 if Session: 452 # NOTE: Not exposing all functionality. 453 return Session.Session(self.trans) 454 else: 455 # NOTE: Requires configuration. 456 457 if self.session_store is None: 458 self.session_store = SessionStore(self, os.path.join(apache.server_root(), "WebStack-sessions")) 459 return self.session_store.get_session(create) 460 461 def expire_session(self): 462 463 """ 464 Expires any session established according to information provided in the 465 transaction. 466 """ 467 468 if Session: 469 session = self.get_session(create=0) 470 if session: 471 session.invalidate() 472 else: 473 # NOTE: Requires configuration. 474 475 if self.session_store is None: 476 self.session_store = SessionStore(self, os.path.join(apache.server_root(), "WebStack-sessions")) 477 self.session_store.expire_session() 478 479 # Application-specific methods. 480 481 def set_user(self, username): 482 483 """ 484 An application-specific method which sets the user information with 485 'username' in the transaction. This affects subsequent calls to 486 'get_user'. 487 """ 488 489 self.user = username 490 491 # vim: tabstop=4 expandtab shiftwidth=4