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 if self.path_info is not None: 166 return self.path_info 167 else: 168 return self.trans.path_info 169 170 def get_query_string(self): 171 172 """ 173 Returns the query string from the path in the request. 174 """ 175 176 return self.trans.args or "" 177 178 # Higher level request-related methods. 179 180 def get_fields_from_path(self): 181 182 """ 183 Extracts fields (or request parameters) from the path specified in the 184 transaction. The underlying framework may refuse to supply fields from 185 the path if handling a POST transaction. 186 187 Returns a dictionary mapping field names to lists of values (even if a 188 single value is associated with any given field name). 189 """ 190 191 # NOTE: Support at best ISO-8859-1 values. 192 193 fields = {} 194 for name, values in parse_qs(self.get_query_string(), 1).items(): # keep_blank_values=1 195 fields[name] = [] 196 for value in values: 197 fields[name].append(unicode(value, "iso-8859-1")) 198 return fields 199 200 def get_fields_from_body(self, encoding=None): 201 202 """ 203 Extracts fields (or request parameters) from the message body in the 204 transaction. The optional 'encoding' parameter specifies the character 205 encoding of the message body for cases where no such information is 206 available, but where the default encoding is to be overridden. 207 208 Returns a dictionary mapping field names to lists of values (even if a 209 single value is associated with any given field name). Each value is 210 either a Unicode object (representing a simple form field, for example) 211 or a plain string (representing a file upload form field, for example). 212 213 The mod_python.util.FieldStorage class may augment the fields from the 214 body with fields found in the path. 215 """ 216 217 all_fields = self._get_fields(encoding) 218 fields_from_path = self.get_fields_from_path() 219 return filter_fields(all_fields, fields_from_path) 220 221 def _get_fields(self, encoding=None): 222 encoding = encoding or self.get_content_type().charset or self.default_charset 223 224 if self.storage_body is None: 225 self.storage_body = FieldStorage(self.trans, keep_blank_values=1) 226 227 # Traverse the storage, finding each field value. 228 229 fields = {} 230 for field in self.storage_body.list: 231 if not fields.has_key(field.name): 232 fields[field.name] = [] 233 fields[field.name].append(get_body_field(field.value, encoding)) 234 return fields 235 236 def get_fields(self, encoding=None): 237 238 """ 239 Extracts fields (or request parameters) from both the path specified in 240 the transaction as well as the message body. The optional 'encoding' 241 parameter specifies the character encoding of the message body for cases 242 where no such information is available, but where the default encoding 243 is to be overridden. 244 245 Returns a dictionary mapping field names to lists of values (even if a 246 single value is associated with any given field name). Each value is 247 either a Unicode object (representing a simple form field, for example) 248 or a plain string (representing a file upload form field, for example). 249 250 Where a given field name is used in both the path and message body to 251 specify values, the values from both sources will be combined into a 252 single list associated with that field name. 253 """ 254 255 return self._get_fields(encoding) 256 257 def get_user(self): 258 259 """ 260 Extracts user information from the transaction. 261 262 Returns a username as a string or None if no user is defined. 263 """ 264 265 if self.user is not None: 266 return self.user 267 else: 268 return self.trans.user 269 270 def get_cookies(self): 271 272 """ 273 Obtains cookie information from the request. 274 275 Returns a dictionary mapping cookie names to cookie objects. 276 277 NOTE: No additional information is passed to the underlying API despite 278 NOTE: support for enhanced cookies in mod_python. 279 """ 280 281 if have_cookies: 282 found_cookies = get_cookies(self.trans) 283 else: 284 found_cookies = self.cookies_in 285 return self.process_cookies(found_cookies) 286 287 def get_cookie(self, cookie_name): 288 289 """ 290 Obtains cookie information from the request. 291 292 Returns a cookie object for the given 'cookie_name' or None if no such 293 cookie exists. 294 """ 295 296 return self.get_cookies().get(self.encode_cookie_value(cookie_name)) 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 # vim: tabstop=4 expandtab shiftwidth=4