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