1 #!/usr/bin/env python 2 3 """ 4 Twisted classes. 5 """ 6 7 import Generic 8 from Helpers.Auth import UserInfo 9 from Helpers.Request import Cookie, get_body_field 10 from Helpers.Response import ConvertingStream 11 from cgi import parse_qs 12 13 class Transaction(Generic.Transaction): 14 15 """ 16 Twisted transaction interface. 17 """ 18 19 def __init__(self, trans): 20 21 "Initialise the transaction using the Twisted transaction 'trans'." 22 23 self.trans = trans 24 self.user = None 25 self.content_type = None 26 27 # Request-related methods. 28 29 def get_request_stream(self): 30 31 """ 32 Returns the request stream for the transaction. 33 """ 34 35 return self.trans.content 36 37 def get_request_method(self): 38 39 """ 40 Returns the request method. 41 """ 42 43 return self.trans.method 44 45 def get_headers(self): 46 47 """ 48 Returns all request headers as a dictionary-like object mapping header 49 names to values. 50 51 NOTE: If duplicate header names are permitted, then this interface will 52 NOTE: need to change. 53 """ 54 55 return self.trans.received_headers 56 57 def get_header_values(self, key): 58 59 """ 60 Returns a list of all request header values associated with the given 61 'key'. Note that according to RFC 2616, 'key' is treated as a 62 case-insensitive string. 63 """ 64 65 # Twisted does not convert the header key to lower case (which is the 66 # stored representation). 67 68 return self.convert_to_list(self.trans.received_headers.get(key.lower())) 69 70 def get_content_type(self): 71 72 """ 73 Returns the content type specified on the request, along with the 74 charset employed. 75 """ 76 77 return self.parse_content_type(self.trans.getHeader("Content-Type")) 78 79 def get_content_charsets(self): 80 81 """ 82 Returns the character set preferences. 83 """ 84 85 return self.parse_content_preferences(self.trans.getHeader("Accept-Language")) 86 87 def get_content_languages(self): 88 89 """ 90 Returns extracted language information from the transaction. 91 """ 92 93 return self.parse_content_preferences(self.trans.getHeader("Accept-Charset")) 94 95 def get_path(self): 96 97 """ 98 Returns the entire path from the request. 99 """ 100 101 return self.trans.uri 102 103 def get_path_without_query(self): 104 105 """ 106 Returns the entire path from the request minus the query string. 107 """ 108 109 return self.get_path().split("?")[0] 110 111 def get_path_info(self): 112 113 """ 114 Returns the "path info" (the part of the URL after the resource name 115 handling the current request) from the request. 116 """ 117 118 return "/%s" % "/".join(self.trans.postpath) 119 120 def get_query_string(self): 121 122 """ 123 Returns the query string from the path in the request. 124 """ 125 126 t = self.get_path().split("?") 127 if len(t) == 1: 128 return "" 129 else: 130 131 # NOTE: Overlook erroneous usage of "?" characters in the path. 132 133 return "?".join(t[1:]) 134 135 # Higher level request-related methods. 136 137 def get_fields_from_path(self): 138 139 """ 140 Extracts the form fields from the path specified in the transaction. The 141 underlying framework may refuse to supply fields from the path if 142 handling a POST transaction. 143 144 Returns a dictionary mapping field names to lists of values (even if a 145 single value is associated with any given field name). 146 """ 147 148 return parse_qs(self.get_query_string(), keep_blank_values=1) 149 150 def get_fields_from_body(self, encoding=None): 151 152 """ 153 Extracts the form fields from the message body in the transaction. The 154 optional 'encoding' parameter specifies the character encoding of the 155 message body for cases where no such information is available, but where 156 the default encoding is to be overridden. 157 158 Returns a dictionary mapping field names to lists of values (even if a 159 single value is associated with any given field name). Each value is 160 either a Unicode object (representing a simple form field, for example) 161 or a plain string (representing a file upload form field, for example). 162 """ 163 164 # Fix the inclusion of path fields since this prevents Unicode conversion. 165 166 fields_from_path = self.get_fields_from_path() 167 168 encoding = encoding or self.get_content_type().charset or self.default_charset 169 fields = {} 170 for field_name, field_values in self.trans.args.items(): 171 172 # Find the path values for this field (for filtering below). 173 174 if fields_from_path.has_key(field_name): 175 field_from_path_values = fields_from_path[field_name] 176 if type(field_from_path_values) != type([]): 177 field_from_path_values = [field_from_path_values] 178 else: 179 field_from_path_values = [] 180 181 # Find the body values. 182 183 if type(field_values) == type([]): 184 fields[field_name] = [] 185 186 # Twisted stores plain strings. 187 188 for field_str in field_values: 189 # Filter path values. 190 if field_str not in field_from_path_values: 191 fields[field_name].append(get_body_field(field_str, encoding)) 192 193 # Remove filtered fields. 194 195 if fields[field_name] == []: 196 del fields[field_name] 197 else: 198 # Filter path values. 199 if field_values not in field_from_path_values: 200 fields[field_name] = get_body_field(field_values, encoding) 201 202 return fields 203 204 def get_user(self): 205 206 """ 207 Extracts user information from the transaction. 208 209 Returns a username as a string or None if no user is defined. 210 """ 211 212 # Twisted makes headers lower case. 213 214 if self.user is not None: 215 return self.user 216 217 auth_header = self.get_headers().get("authorization") 218 if auth_header: 219 return UserInfo(auth_header).username 220 else: 221 return None 222 223 def get_cookies(self): 224 225 """ 226 Obtains cookie information from the request. 227 228 Returns a dictionary mapping cookie names to cookie objects. 229 NOTE: Twisted does not seem to support this operation via methods. Thus, 230 NOTE: direct access has been employed to get the dictionary. 231 NOTE: Twisted also returns a plain string - a Cookie object is therefore 232 NOTE: introduced. 233 """ 234 235 cookies = {} 236 for name, value in self.trans.received_cookies.items(): 237 cookies[name] = Cookie(name, value) 238 return cookies 239 240 def get_cookie(self, cookie_name): 241 242 """ 243 Obtains cookie information from the request. 244 245 Returns a cookie object for the given 'cookie_name' or None if no such 246 cookie exists. 247 NOTE: Twisted also returns a plain string - a Cookie object is therefore 248 NOTE: introduced. 249 """ 250 251 return Cookie(cookie_name, self.trans.getCookie(cookie_name)) 252 253 # Response-related methods. 254 255 def get_response_stream(self): 256 257 """ 258 Returns the response stream for the transaction. 259 """ 260 261 # Unicode can upset this operation. Using either the specified charset 262 # or a default encoding. 263 264 if self.content_type: 265 encoding = self.content_type.charset 266 encoding = encoding or self.default_charset 267 return ConvertingStream(self.trans, encoding) 268 269 def get_response_code(self): 270 271 """ 272 Get the response code associated with the transaction. If no response 273 code is defined, None is returned. 274 """ 275 276 # NOTE: Accessing the request attribute directly. 277 278 return self.trans.code 279 280 def set_response_code(self, response_code): 281 282 """ 283 Set the 'response_code' using a numeric constant defined in the HTTP 284 specification. 285 """ 286 287 self.trans.setResponseCode(response_code) 288 289 def set_header_value(self, header, value): 290 291 """ 292 Set the HTTP 'header' with the given 'value'. 293 """ 294 295 self.trans.setHeader(self.format_header_value(header), self.format_header_value(value)) 296 297 def set_content_type(self, content_type): 298 299 """ 300 Sets the 'content_type' for the response. 301 """ 302 303 # Remember the content type for encoding purposes later. 304 305 self.content_type = content_type 306 self.trans.setHeader("Content-Type", str(content_type)) 307 308 # Higher level response-related methods. 309 310 def set_cookie(self, cookie): 311 312 """ 313 Stores the given 'cookie' object in the response. 314 """ 315 316 self.trans.addCookie(cookie.name, cookie.value, expires=cookie.expires, path=cookie.path) 317 318 def set_cookie_value(self, name, value, path=None, expires=None): 319 320 """ 321 Stores a cookie with the given 'name' and 'value' in the response. 322 323 The optional 'path' is a string which specifies the scope of the cookie, 324 and the optional 'expires' parameter is a value compatible with the 325 time.time function, and indicates the expiry date/time of the cookie. 326 """ 327 328 self.trans.addCookie(self.format_header_value(name), 329 self.format_header_value(value), expires=expires, path=path) 330 331 def delete_cookie(self, cookie_name): 332 333 """ 334 Adds to the response a request that the cookie with the given 335 'cookie_name' be deleted/discarded by the client. 336 """ 337 338 # Create a special cookie, given that we do not know whether the browser 339 # has been sent the cookie or not. 340 # NOTE: Magic discovered in Webware. 341 342 self.trans.addCookie(cookie_name, "", expires=0, path="/", max_age=0) 343 344 # Application-specific methods. 345 346 def set_user(self, username): 347 348 """ 349 An application-specific method which sets the user information with 350 'username' in the transaction. This affects subsequent calls to 351 'get_user'. 352 """ 353 354 self.user = username 355 356 # vim: tabstop=4 expandtab shiftwidth=4