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