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): 154 155 """ 156 Returns the entire path from the request. 157 """ 158 159 return self.trans.request().uri() 160 161 def get_path_without_query(self): 162 163 """ 164 Returns the entire path from the request minus the query string. 165 """ 166 167 return self.get_path().split("?")[0] 168 169 def get_path_info(self): 170 171 """ 172 Returns the "path info" (the part of the URL after the resource name 173 handling the current request) from the request. 174 """ 175 176 path_info = self.trans.request().pathInfo() 177 context_name = self.trans.request().contextName() 178 if path_info.startswith(context_name): 179 return path_info[len(context_name):] 180 else: 181 return path_info 182 183 def get_query_string(self): 184 185 """ 186 Returns the query string from the path in the request. 187 """ 188 189 return self.trans.request().queryString() 190 191 # Higher level request-related methods. 192 193 def get_fields_from_path(self): 194 195 """ 196 Extracts fields (or request parameters) from the path specified in the 197 transaction. The underlying framework may refuse to supply fields from 198 the path if handling a POST transaction. 199 200 Returns a dictionary mapping field names to lists of values (even if a 201 single value is associated with any given field name). 202 """ 203 204 # NOTE: Support at best ISO-8859-1 values. 205 206 fields = {} 207 for name, values in parse_qs(self.get_query_string(), keep_blank_values=1).items(): 208 fields[name] = [] 209 for value in values: 210 fields[name].append(unicode(value, "iso-8859-1")) 211 return fields 212 213 def get_fields_from_body(self, encoding=None): 214 215 """ 216 Extracts fields (or request parameters) from the message body in the 217 transaction. The optional 'encoding' parameter specifies the character 218 encoding of the message body for cases where no such information is 219 available, but where the default encoding is to be overridden. 220 221 Returns a dictionary mapping field names to lists of values (even if a 222 single value is associated with any given field name). Each value is 223 either a Unicode object (representing a simple form field, for example) 224 or a plain string (representing a file upload form field, for example). 225 """ 226 227 all_fields = self._get_fields(encoding) 228 fields_from_path = self.get_fields_from_path() 229 return filter_fields(all_fields, fields_from_path) 230 231 def _get_fields(self, encoding=None): 232 encoding = encoding or self.get_content_type().charset or self.default_charset 233 fields = {} 234 for field_name, field_values in self.trans.request().fields().items(): 235 if type(field_values) == type([]): 236 fields[field_name] = [] 237 for field_str in field_values: 238 fields[field_name].append(get_body_field(field_str, encoding)) 239 else: 240 fields[field_name] = [get_body_field(field_values, encoding)] 241 return fields 242 243 def get_fields(self, encoding=None): 244 245 """ 246 Extracts fields (or request parameters) from both the path specified in 247 the transaction as well as the message body. The optional 'encoding' 248 parameter specifies the character encoding of the message body for cases 249 where no such information is available, but where the default encoding 250 is to be overridden. 251 252 Returns a dictionary mapping field names to lists of values (even if a 253 single value is associated with any given field name). Each value is 254 either a Unicode object (representing a simple form field, for example) 255 or a plain string (representing a file upload form field, for example). 256 257 Where a given field name is used in both the path and message body to 258 specify values, the values from both sources will be combined into a 259 single list associated with that field name. 260 """ 261 262 return self._get_fields(encoding) 263 264 def get_user(self): 265 266 """ 267 Extracts user information from the transaction. 268 269 Returns a username as a string or None if no user is defined. 270 """ 271 272 # NOTE: Webware relies entirely on a CGI-style environment where the 273 # NOTE: actual headers are not available. Therefore, the Web server must 274 # NOTE: itself be set up to provide user support. 275 276 if self.user is not None: 277 return self.user 278 279 try: 280 return self.trans.request().remoteUser() 281 except KeyError, exc: 282 return None 283 284 def get_cookies(self): 285 286 """ 287 Obtains cookie information from the request. 288 289 Returns a dictionary mapping cookie names to cookie objects. 290 """ 291 292 return self.process_cookies(self.trans.request().cookies(), using_strings=1) 293 294 def get_cookie(self, cookie_name): 295 296 """ 297 Obtains cookie information from the request. 298 299 Returns a cookie object for the given 'cookie_name' or None if no such 300 cookie exists. 301 """ 302 303 try: 304 value = self.trans.request().cookie(self.encode_cookie_value(cookie_name)) 305 return Cookie(cookie_name, self.decode_cookie_value(value)) 306 except KeyError: 307 return None 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.response(), 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 # NOTE: Webware treats the response code as just another header. 343 344 status = self.trans.response().header("Status", None) 345 try: 346 if status is not None: 347 return int(status) 348 else: 349 return None 350 except ValueError: 351 return None 352 353 def set_response_code(self, response_code): 354 355 """ 356 Set the 'response_code' using a numeric constant defined in the HTTP 357 specification. 358 """ 359 360 self.trans.response().setStatus(response_code) 361 362 def set_header_value(self, header, value): 363 364 """ 365 Set the HTTP 'header' with the given 'value'. 366 """ 367 368 self.trans.response().setHeader(self.format_header_value(header), self.format_header_value(value)) 369 370 def set_content_type(self, content_type): 371 372 """ 373 Sets the 'content_type' for the response. 374 """ 375 376 # Remember the content type for encoding purposes later. 377 378 self.content_type = content_type 379 return self.trans.response().setHeader("Content-Type", str(content_type)) 380 381 # Higher level response-related methods. 382 383 def set_cookie(self, cookie): 384 385 """ 386 Stores the given 'cookie' object in the response. 387 """ 388 389 self.set_cookie_value(cookie.name, cookie.value) 390 #self.trans.response().addCookie(cookie) 391 392 def set_cookie_value(self, name, value, path=None, expires=None): 393 394 """ 395 Stores a cookie with the given 'name' and 'value' in the response. 396 397 The optional 'path' is a string which specifies the scope of the cookie, 398 and the optional 'expires' parameter is a value compatible with the 399 time.time function, and indicates the expiry date/time of the cookie. 400 """ 401 402 self.trans.response().setCookie(self.encode_cookie_value(name), 403 self.encode_cookie_value(value), path, expires) 404 405 def delete_cookie(self, cookie_name): 406 407 """ 408 Adds to the response a request that the cookie with the given 409 'cookie_name' be deleted/discarded by the client. 410 """ 411 412 self.trans.response().delCookie(self.encode_cookie_value(cookie_name)) 413 414 # Session-related methods. 415 416 def get_session(self, create=1): 417 418 """ 419 Gets a session corresponding to an identifier supplied in the 420 transaction. 421 422 If no session has yet been established according to information 423 provided in the transaction then the optional 'create' parameter 424 determines whether a new session will be established. 425 426 Where no session has been established and where 'create' is set to 0 427 then None is returned. In all other cases, a session object is created 428 (where appropriate) and returned. 429 """ 430 431 # NOTE: create and hasSession() not used. 432 433 return Session(self.trans.session()) 434 435 def expire_session(self): 436 437 """ 438 Expires any session established according to information provided in the 439 transaction. 440 """ 441 442 self.trans.request().setSessionExpired(1) 443 444 class Session: 445 446 "A more dictionary-like session object than the one Webware provides." 447 448 def __init__(self, session): 449 self.session = session 450 451 def items(self): 452 return self.session.values().items() 453 454 def __getattr__(self, name): 455 return getattr(self.__dict__["session"], name) 456 457 def __delitem__(self, name): 458 del self.session[name] 459 460 def __setitem__(self, name, value): 461 self.session[name] = value 462 463 def __getitem__(self, name): 464 return self.session[name] 465 466 # vim: tabstop=4 expandtab shiftwidth=4