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 cookies = {} 258 for name, value in self.trans.request().cookies().items(): 259 cookies[name] = Cookie(name, value) 260 return cookies 261 262 def get_cookie(self, cookie_name): 263 264 """ 265 Obtains cookie information from the request. 266 267 Returns a cookie object for the given 'cookie_name' or None if no such 268 cookie exists. 269 """ 270 271 try: 272 return Cookie(cookie_name, self.trans.request().cookie(cookie_name)) 273 except KeyError: 274 return None 275 276 # Response-related methods. 277 278 def get_response_stream(self): 279 280 """ 281 Returns the response stream for the transaction. 282 """ 283 284 # Unicode can upset this operation. Using either the specified charset 285 # or a default encoding. 286 287 encoding = self.get_response_stream_encoding() 288 return ConvertingStream(self.trans.response(), encoding) 289 290 def get_response_stream_encoding(self): 291 292 """ 293 Returns the response stream encoding. 294 """ 295 296 if self.content_type: 297 encoding = self.content_type.charset 298 else: 299 encoding = None 300 return encoding or self.default_charset 301 302 def get_response_code(self): 303 304 """ 305 Get the response code associated with the transaction. If no response 306 code is defined, None is returned. 307 """ 308 309 # NOTE: Webware treats the response code as just another header. 310 311 status = self.trans.response().header("Status", None) 312 try: 313 if status is not None: 314 return int(status) 315 else: 316 return None 317 except ValueError: 318 return None 319 320 def set_response_code(self, response_code): 321 322 """ 323 Set the 'response_code' using a numeric constant defined in the HTTP 324 specification. 325 """ 326 327 self.trans.response().setStatus(response_code) 328 329 def set_header_value(self, header, value): 330 331 """ 332 Set the HTTP 'header' with the given 'value'. 333 """ 334 335 self.trans.response().setHeader(self.format_header_value(header), self.format_header_value(value)) 336 337 def set_content_type(self, content_type): 338 339 """ 340 Sets the 'content_type' for the response. 341 """ 342 343 # Remember the content type for encoding purposes later. 344 345 self.content_type = content_type 346 return self.trans.response().setHeader("Content-Type", str(content_type)) 347 348 # Higher level response-related methods. 349 350 def set_cookie(self, cookie): 351 352 """ 353 Stores the given 'cookie' object in the response. 354 """ 355 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(name, 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(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: Should really use Webware's hasSession method. 397 398 session = self.trans.session() 399 return Session(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 # Application-specific methods. 411 412 def set_user(self, username): 413 414 """ 415 An application-specific method which sets the user information with 416 'username' in the transaction. This affects subsequent calls to 417 'get_user'. 418 """ 419 420 self.user = username 421 422 class Session: 423 424 "A more dictionary-like session object than the one Webware provides." 425 426 def __init__(self, session): 427 self.session = session 428 429 def items(self): 430 return self.session.values().items() 431 432 def __getattr__(self, name): 433 return getattr(self.__dict__["session"], name) 434 435 # vim: tabstop=4 expandtab shiftwidth=4