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