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