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