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