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