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 encoding = self.get_response_stream_encoding() 304 return ConvertingStream(self.trans, encoding) 305 306 def get_response_stream_encoding(self): 307 308 """ 309 Returns the response stream encoding. 310 """ 311 312 if self.content_type: 313 encoding = self.content_type.charset 314 else: 315 encoding = None 316 return encoding or self.default_charset 317 318 def get_response_code(self): 319 320 """ 321 Get the response code associated with the transaction. If no response 322 code is defined, None is returned. 323 """ 324 325 return self.response_code 326 327 def set_response_code(self, response_code): 328 329 """ 330 Set the 'response_code' using a numeric constant defined in the HTTP 331 specification. 332 """ 333 334 self.response_code = response_code 335 336 def set_header_value(self, header, value): 337 338 """ 339 Set the HTTP 'header' with the given 'value'. 340 """ 341 342 self.trans.headers_out[self.format_header_value(header)] = self.format_header_value(value) 343 344 def set_content_type(self, content_type): 345 346 """ 347 Sets the 'content_type' for the response. 348 """ 349 350 # Remember the content type for encoding purposes later. 351 352 self.content_type = content_type 353 self.trans.content_type = str(content_type) 354 355 # Higher level response-related methods. 356 357 def set_cookie(self, cookie): 358 359 """ 360 Stores the given 'cookie' object in the response. 361 """ 362 363 if Cookie: 364 Cookie.add_cookie(self.trans, cookie) 365 else: 366 # NOTE: If multiple cookies of the same name could be specified, this 367 # NOTE: could need changing. 368 369 cookie_out = SimpleCookie() 370 cookie_out[cookie.name] = cookie.value 371 self._write_cookie(cookie_out) 372 373 def set_cookie_value(self, name, value, path=None, expires=None): 374 375 """ 376 Stores a cookie with the given 'name' and 'value' in the response. 377 378 The optional 'path' is a string which specifies the scope of the cookie, 379 and the optional 'expires' parameter is a value compatible with the 380 time.time function, and indicates the expiry date/time of the cookie. 381 """ 382 383 # NOTE: We just hope that Cookie converts Unicode arguments to US-ASCII. 384 385 if Cookie: 386 cookie = Cookie.Cookie(name, value) 387 if expires is not None: 388 cookie.expires = expires 389 if path is not None: 390 cookie.path = path 391 Cookie.add_cookie(self.trans, cookie) 392 else: 393 cookie_out = SimpleCookie() 394 cookie_out[name] = value 395 if path is not None: 396 cookie_out[name]["path"] = path 397 if expires is not None: 398 cookie_out[name]["expires"] = expires 399 self._write_cookie(cookie_out) 400 401 def delete_cookie(self, cookie_name): 402 403 """ 404 Adds to the response a request that the cookie with the given 405 'cookie_name' be deleted/discarded by the client. 406 """ 407 408 # Create a special cookie, given that we do not know whether the browser 409 # has been sent the cookie or not. 410 # NOTE: Magic discovered in Webware. 411 412 if Cookie: 413 cookie = Cookie.Cookie(cookie_name, "") 414 cookie.path = "/" 415 cookie.expires = 0 416 cookie.max_age = 0 417 Cookie.add_cookie(self.trans, cookie) 418 else: 419 cookie_out = SimpleCookie() 420 cookie_out[cookie_name] = "" 421 cookie_out[cookie_name]["path"] = "/" 422 cookie_out[cookie_name]["expires"] = 0 423 cookie_out[cookie_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 # Application-specific methods. 481 482 def set_user(self, username): 483 484 """ 485 An application-specific method which sets the user information with 486 'username' in the transaction. This affects subsequent calls to 487 'get_user'. 488 """ 489 490 self.user = username 491 492 # vim: tabstop=4 expandtab shiftwidth=4