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 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 the form fields from the path specified in the transaction. The 162 underlying framework may refuse to supply fields from the path if 163 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 return parse_qs(self.get_query_string(), keep_blank_values=1) 170 171 def get_fields_from_body(self, encoding=None): 172 173 """ 174 Extracts the form fields from the message body in the transaction. The 175 optional 'encoding' parameter specifies the character encoding of the 176 message body for cases where no such information is available, but where 177 the default encoding is to be overridden. 178 179 Returns a dictionary mapping field names to lists of values (even if a 180 single value is associated with any given field name). Each value is 181 either a Unicode object (representing a simple form field, for example) 182 or a plain string (representing a file upload form field, for example). 183 """ 184 185 encoding = encoding or self.get_content_type().charset or self.default_charset 186 fields = {} 187 for field_name, field_values in self.trans.request().fields().items(): 188 if type(field_values) == type([]): 189 fields[field_name] = [] 190 for field_str in field_values: 191 fields[field_name].append(get_body_field(field_str, encoding)) 192 else: 193 fields[field_name] = [get_body_field(field_values, encoding)] 194 return fields 195 196 def get_user(self): 197 198 """ 199 Extracts user information from the transaction. 200 201 Returns a username as a string or None if no user is defined. 202 """ 203 204 # NOTE: Webware relies entirely on a CGI-style environment where the 205 # NOTE: actual headers are not available. Therefore, the Web server must 206 # NOTE: itself be set up to provide user support. 207 208 if self.user is not None: 209 return self.user 210 211 try: 212 return self.trans.request().remoteUser() 213 except KeyError, exc: 214 return None 215 216 def get_cookies(self): 217 218 """ 219 Obtains cookie information from the request. 220 221 Returns a dictionary mapping cookie names to cookie objects. 222 """ 223 224 cookies = {} 225 for name, value in self.trans.request().cookies().items(): 226 cookies[name] = Cookie(name, value) 227 return cookies 228 229 def get_cookie(self, cookie_name): 230 231 """ 232 Obtains cookie information from the request. 233 234 Returns a cookie object for the given 'cookie_name' or None if no such 235 cookie exists. 236 """ 237 238 try: 239 return Cookie(cookie_name, self.trans.request().cookie(cookie_name)) 240 except KeyError: 241 return None 242 243 # Response-related methods. 244 245 def get_response_stream(self): 246 247 """ 248 Returns the response stream for the transaction. 249 """ 250 251 # Unicode can upset this operation. Using either the specified charset 252 # or a default encoding. 253 254 if self.content_type: 255 encoding = self.content_type.charset 256 encoding = encoding or self.default_charset 257 return ConvertingStream(self.trans.response(), encoding) 258 259 def get_response_code(self): 260 261 """ 262 Get the response code associated with the transaction. If no response 263 code is defined, None is returned. 264 """ 265 266 # NOTE: Webware treats the response code as just another header. 267 268 status = self.trans.response().header("Status", None) 269 try: 270 if status is not None: 271 return int(status) 272 else: 273 return None 274 except ValueError: 275 return None 276 277 def set_response_code(self, response_code): 278 279 """ 280 Set the 'response_code' using a numeric constant defined in the HTTP 281 specification. 282 """ 283 284 self.trans.response().setStatus(response_code) 285 286 def set_header_value(self, header, value): 287 288 """ 289 Set the HTTP 'header' with the given 'value'. 290 """ 291 292 self.trans.response().setHeader(self.format_header_value(header), self.format_header_value(value)) 293 294 def set_content_type(self, content_type): 295 296 """ 297 Sets the 'content_type' for the response. 298 """ 299 300 # Remember the content type for encoding purposes later. 301 302 self.content_type = content_type 303 return self.trans.response().setHeader("Content-Type", str(content_type)) 304 305 # Higher level response-related methods. 306 307 def set_cookie(self, cookie): 308 309 """ 310 Stores the given 'cookie' object in the response. 311 """ 312 313 self.trans.response().addCookie(cookie) 314 315 def set_cookie_value(self, name, value, path=None, expires=None): 316 317 """ 318 Stores a cookie with the given 'name' and 'value' in the response. 319 320 The optional 'path' is a string which specifies the scope of the cookie, 321 and the optional 'expires' parameter is a value compatible with the 322 time.time function, and indicates the expiry date/time of the cookie. 323 """ 324 325 self.trans.response().setCookie(name, value, path, expires) 326 327 def delete_cookie(self, cookie_name): 328 329 """ 330 Adds to the response a request that the cookie with the given 331 'cookie_name' be deleted/discarded by the client. 332 """ 333 334 self.trans.response().delCookie(cookie_name) 335 336 # Session-related methods. 337 338 def get_session(self, create=1): 339 340 """ 341 Gets a session corresponding to an identifier supplied in the 342 transaction. 343 344 If no session has yet been established according to information 345 provided in the transaction then the optional 'create' parameter 346 determines whether a new session will be established. 347 348 Where no session has been established and where 'create' is set to 0 349 then None is returned. In all other cases, a session object is created 350 (where appropriate) and returned. 351 """ 352 353 # NOTE: Should really use Webware's hasSession method. 354 355 session = self.trans.session() 356 return Session(session) 357 358 def expire_session(self): 359 360 """ 361 Expires any session established according to information provided in the 362 transaction. 363 """ 364 365 self.trans.request().setSessionExpired(1) 366 367 # Application-specific methods. 368 369 def set_user(self, username): 370 371 """ 372 An application-specific method which sets the user information with 373 'username' in the transaction. This affects subsequent calls to 374 'get_user'. 375 """ 376 377 self.user = username 378 379 class Session: 380 381 "A more dictionary-like session object than the one Webware provides." 382 383 def __init__(self, session): 384 self.session = session 385 386 def items(self): 387 return self.session.values().items() 388 389 def __getattr__(self, name): 390 return getattr(self.__dict__["session"], name) 391 392 # vim: tabstop=4 expandtab shiftwidth=4