1 #!/usr/bin/env python 2 3 """ 4 Webware classes. 5 6 Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 import Generic 24 from cgi import parse_qs 25 import StringIO 26 from Helpers import Environment 27 from Helpers.Request import Cookie, get_body_field, filter_fields 28 from Helpers.Response import ConvertingStream 29 30 class Transaction(Generic.Transaction): 31 32 """ 33 Webware transaction interface. 34 """ 35 36 def __init__(self, trans): 37 38 "Initialise the transaction using the Webware transaction 'trans'." 39 40 self.trans = trans 41 self.content_type = None 42 self.user = None 43 self.path_info = None 44 45 # Server-related methods. 46 47 def get_server_name(self): 48 49 "Returns the server name." 50 51 return self.trans.request().serverURL().split("/")[0].split(":")[0] 52 53 def get_server_port(self): 54 55 "Returns the server port as a string." 56 57 host_and_port = self.trans.request().serverURL().split("/")[0].split(":") 58 if len(host_and_port) > 1: 59 return host_and_port[1] 60 else: 61 return "80" 62 63 # Request-related methods. 64 65 def get_request_stream(self): 66 67 """ 68 Returns the request stream for the transaction. 69 """ 70 71 request = self.trans.request() 72 try: 73 stream = request.rawInput(rewind=1) 74 if stream is None: 75 return StringIO.StringIO("") 76 77 # NOTE: Dubious catch-all situation, but it is difficult to control 78 # NOTE: cases where Webware's internals themselves fail. 79 80 except: 81 return StringIO.StringIO("") 82 83 return stream 84 85 def get_request_method(self): 86 87 """ 88 Returns the request method. 89 """ 90 91 return self.trans.request().method() 92 93 def get_headers(self): 94 95 """ 96 Returns all request headers as a dictionary-like object mapping header 97 names to values. 98 99 NOTE: If duplicate header names are permitted, then this interface will 100 NOTE: need to change. 101 """ 102 103 # Use the Webware environment and some assumptions about variable names. 104 # NOTE: Using lower case for the header names. 105 106 env = self.trans.request().environ() 107 return Environment.get_headers(env) 108 109 def get_header_values(self, key): 110 111 """ 112 Returns a list of all request header values associated with the given 113 'key'. Note that according to RFC 2616, 'key' is treated as a 114 case-insensitive string. 115 """ 116 117 # Use the Webware environment and some assumptions about variable names. 118 119 env = self.trans.request().environ() 120 cgi_key = "HTTP_" + key.replace("-", "_").upper() 121 if env.has_key(cgi_key): 122 return [env[cgi_key]] 123 else: 124 return [] 125 126 def get_content_type(self): 127 128 """ 129 Returns the content type specified on the request, along with the 130 charset employed. 131 """ 132 133 return self.parse_content_type(self.trans.request().contentType()) 134 135 def get_content_charsets(self): 136 137 """ 138 Returns the character set preferences. 139 NOTE: Requires enhancements to HTTPRequest. 140 """ 141 142 return self.trans.request().contentCharsets() 143 144 def get_content_languages(self): 145 146 """ 147 Returns extracted language information from the transaction. 148 NOTE: Requires enhancements to HTTPRequest. 149 """ 150 151 return self.trans.request().contentLanguages() 152 153 def get_path(self, encoding=None): 154 155 """ 156 Returns the entire path from the request as a Unicode object. Any "URL 157 encoded" character values in the part of the path before the query 158 string will be decoded and presented as genuine characters; the query 159 string will remain "URL encoded", however. 160 161 If the optional 'encoding' is set, use that in preference to the default 162 encoding to convert the path into a form not containing "URL encoded" 163 character values. 164 """ 165 166 path = self.get_path_without_query(encoding) 167 qs = self.get_query_string() 168 if qs: 169 return path + "?" + qs 170 else: 171 return path 172 173 def get_path_without_query(self, encoding=None): 174 175 """ 176 Returns the entire path from the request minus the query string as a 177 Unicode object containing genuine characters (as opposed to "URL 178 encoded" character values). 179 180 If the optional 'encoding' is set, use that in preference to the default 181 encoding to convert the path into a form not containing "URL encoded" 182 character values. 183 """ 184 185 return self.decode_path(self.trans.request().uri().split("?")[0], encoding) 186 187 def get_path_info(self, encoding=None): 188 189 """ 190 Returns the "path info" (the part of the URL after the resource name 191 handling the current request) from the request as a Unicode object 192 containing genuine characters (as opposed to "URL encoded" character 193 values). 194 195 If the optional 'encoding' is set, use that in preference to the default 196 encoding to convert the path into a form not containing "URL encoded" 197 character values. 198 """ 199 200 path_info = self.trans.request().pathInfo() 201 context_name = self.trans.request().contextName() 202 if path_info.startswith(context_name): 203 real_path_info = path_info[len(context_name):] 204 else: 205 real_path_info = path_info 206 return self.decode_path(real_path_info, encoding) 207 208 def get_query_string(self): 209 210 """ 211 Returns the query string from the path in the request. 212 """ 213 214 return self.trans.request().queryString() 215 216 # Higher level request-related methods. 217 218 def get_fields_from_path(self, encoding=None): 219 220 """ 221 Extracts fields (or request parameters) from the path specified in the 222 transaction. The underlying framework may refuse to supply fields from 223 the path if handling a POST transaction. The optional 'encoding' 224 parameter specifies the character encoding of the query string for cases 225 where the default encoding is to be overridden. 226 227 Returns a dictionary mapping field names to lists of values (even if a 228 single value is associated with any given field name). 229 """ 230 231 fields = {} 232 for name, values in parse_qs(self.get_query_string(), keep_blank_values=1).items(): 233 name = self.decode_path(name, encoding) 234 fields[name] = [] 235 for value in values: 236 value = self.decode_path(value, encoding) 237 fields[name].append(value) 238 return fields 239 240 def get_fields_from_body(self, encoding=None): 241 242 """ 243 Extracts fields (or request parameters) from the message body in the 244 transaction. The optional 'encoding' parameter specifies the character 245 encoding of the message body for cases where no such information is 246 available, but where the default encoding is to be overridden. 247 248 Returns a dictionary mapping field names to lists of values (even if a 249 single value is associated with any given field name). Each value is 250 either a Unicode object (representing a simple form field, for example) 251 or a plain string (representing a file upload form field, for example). 252 """ 253 254 all_fields = self._get_fields(encoding) 255 fields_from_path = self.get_fields_from_path() 256 return filter_fields(all_fields, fields_from_path) 257 258 def _get_fields(self, encoding=None): 259 encoding = encoding or self.get_content_type().charset or self.default_charset 260 fields = {} 261 for field_name, field_values in self.trans.request().fields().items(): 262 if type(field_values) == type([]): 263 fields[field_name] = [] 264 for field_str in field_values: 265 fields[field_name].append(get_body_field(field_str, encoding)) 266 else: 267 fields[field_name] = [get_body_field(field_values, encoding)] 268 return fields 269 270 def get_fields(self, encoding=None): 271 272 """ 273 Extracts fields (or request parameters) from both the path specified in 274 the transaction as well as the message body. The optional 'encoding' 275 parameter specifies the character encoding of the message body for cases 276 where no such information is available, but where the default encoding 277 is to be overridden. 278 279 Returns a dictionary mapping field names to lists of values (even if a 280 single value is associated with any given field name). Each value is 281 either a Unicode object (representing a simple form field, for example) 282 or a plain string (representing a file upload form field, for example). 283 284 Where a given field name is used in both the path and message body to 285 specify values, the values from both sources will be combined into a 286 single list associated with that field name. 287 """ 288 289 return self._get_fields(encoding) 290 291 def get_user(self): 292 293 """ 294 Extracts user information from the transaction. 295 296 Returns a username as a string or None if no user is defined. 297 """ 298 299 # NOTE: Webware relies entirely on a CGI-style environment where the 300 # NOTE: actual headers are not available. Therefore, the Web server must 301 # NOTE: itself be set up to provide user support. 302 303 if self.user is not None: 304 return self.user 305 306 try: 307 return self.trans.request().remoteUser() 308 except KeyError, exc: 309 return None 310 311 def get_cookies(self): 312 313 """ 314 Obtains cookie information from the request. 315 316 Returns a dictionary mapping cookie names to cookie objects. 317 """ 318 319 return self.process_cookies(self.trans.request().cookies(), using_strings=1) 320 321 def get_cookie(self, cookie_name): 322 323 """ 324 Obtains cookie information from the request. 325 326 Returns a cookie object for the given 'cookie_name' or None if no such 327 cookie exists. 328 """ 329 330 try: 331 value = self.trans.request().cookie(self.encode_cookie_value(cookie_name)) 332 return Cookie(cookie_name, self.decode_cookie_value(value)) 333 except KeyError: 334 return None 335 336 # Response-related methods. 337 338 def get_response_stream(self): 339 340 """ 341 Returns the response stream for the transaction. 342 """ 343 344 # Unicode can upset this operation. Using either the specified charset 345 # or a default encoding. 346 347 encoding = self.get_response_stream_encoding() 348 return ConvertingStream(self.trans.response(), encoding) 349 350 def get_response_stream_encoding(self): 351 352 """ 353 Returns the response stream encoding. 354 """ 355 356 if self.content_type: 357 encoding = self.content_type.charset 358 else: 359 encoding = None 360 return encoding or self.default_charset 361 362 def get_response_code(self): 363 364 """ 365 Get the response code associated with the transaction. If no response 366 code is defined, None is returned. 367 """ 368 369 # NOTE: Webware treats the response code as just another header. 370 371 status = self.trans.response().header("Status", None) 372 try: 373 if status is not None: 374 return int(status) 375 else: 376 return None 377 except ValueError: 378 return None 379 380 def set_response_code(self, response_code): 381 382 """ 383 Set the 'response_code' using a numeric constant defined in the HTTP 384 specification. 385 """ 386 387 self.trans.response().setStatus(response_code) 388 389 def set_header_value(self, header, value): 390 391 """ 392 Set the HTTP 'header' with the given 'value'. 393 """ 394 395 self.trans.response().setHeader(self.format_header_value(header), self.format_header_value(value)) 396 397 def set_content_type(self, content_type): 398 399 """ 400 Sets the 'content_type' for the response. 401 """ 402 403 # Remember the content type for encoding purposes later. 404 405 self.content_type = content_type 406 return self.trans.response().setHeader("Content-Type", str(content_type)) 407 408 # Higher level response-related methods. 409 410 def set_cookie(self, cookie): 411 412 """ 413 Stores the given 'cookie' object in the response. 414 """ 415 416 self.set_cookie_value(cookie.name, cookie.value) 417 #self.trans.response().addCookie(cookie) 418 419 def set_cookie_value(self, name, value, path=None, expires=None): 420 421 """ 422 Stores a cookie with the given 'name' and 'value' in the response. 423 424 The optional 'path' is a string which specifies the scope of the cookie, 425 and the optional 'expires' parameter is a value compatible with the 426 time.time function, and indicates the expiry date/time of the cookie. 427 """ 428 429 self.trans.response().setCookie(self.encode_cookie_value(name), 430 self.encode_cookie_value(value), path, expires) 431 432 def delete_cookie(self, cookie_name): 433 434 """ 435 Adds to the response a request that the cookie with the given 436 'cookie_name' be deleted/discarded by the client. 437 """ 438 439 self.trans.response().delCookie(self.encode_cookie_value(cookie_name)) 440 441 # Session-related methods. 442 443 def get_session(self, create=1): 444 445 """ 446 Gets a session corresponding to an identifier supplied in the 447 transaction. 448 449 If no session has yet been established according to information 450 provided in the transaction then the optional 'create' parameter 451 determines whether a new session will be established. 452 453 Where no session has been established and where 'create' is set to 0 454 then None is returned. In all other cases, a session object is created 455 (where appropriate) and returned. 456 """ 457 458 # NOTE: create and hasSession() not used. 459 460 return Session(self.trans.session()) 461 462 def expire_session(self): 463 464 """ 465 Expires any session established according to information provided in the 466 transaction. 467 """ 468 469 self.trans.request().setSessionExpired(1) 470 471 class Session: 472 473 "A more dictionary-like session object than the one Webware provides." 474 475 def __init__(self, session): 476 self.session = session 477 478 def items(self): 479 return self.session.values().items() 480 481 def __getattr__(self, name): 482 return getattr(self.__dict__["session"], name) 483 484 def __delitem__(self, name): 485 del self.session[name] 486 487 def __setitem__(self, name, value): 488 self.session[name] = value 489 490 def __getitem__(self, name): 491 return self.session[name] 492 493 # vim: tabstop=4 expandtab shiftwidth=4