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 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 A framework-specific method which returns the request stream for 33 the transaction. 34 """ 35 36 return self.trans.content 37 38 def get_request_method(self): 39 40 """ 41 A framework-specific method which gets the request method. 42 """ 43 44 return self.trans.method 45 46 def get_headers(self): 47 48 """ 49 A framework-specific method which returns all request headers as a 50 dictionary-like object mapping header names to values. 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 A framework-specific method which returns a list of all request header 61 values associated with the given 'key'. Note that according to RFC 2616, 62 'key' is treated as a 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 A framework-specific method which gets the content type specified on the 74 request, along with the 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 A framework-specific method which extracts language information from 91 the transaction. 92 """ 93 94 return self.parse_content_preferences(self.trans.getHeader("Accept-Charset")) 95 96 def get_path(self): 97 98 """ 99 A framework-specific method which gets the entire path from the request. 100 """ 101 102 return self.trans.uri 103 104 def get_path_without_query(self): 105 106 """ 107 A framework-specific method which gets the entire path from the request 108 minus the query string. 109 """ 110 111 return self.get_path().split("?")[0] 112 113 def get_path_info(self): 114 115 """ 116 A framework-specific method which gets the "path info" (the part of the 117 URL after the resource name handling the current request) from the 118 request. 119 """ 120 121 return "/%s" % "/".join(self.trans.postpath) 122 123 def get_query_string(self): 124 125 """ 126 A framework-specific method which gets the query string from the path in 127 the request. 128 """ 129 130 t = self.get_path().split("?") 131 if len(t) == 1: 132 return "" 133 else: 134 135 # NOTE: Overlook erroneous usage of "?" characters in the path. 136 137 return "?".join(t[1:]) 138 139 # Higher level request-related methods. 140 141 def get_fields_from_path(self): 142 143 """ 144 A framework-specific method which extracts the form fields from the 145 path specified in the transaction. The underlying framework may refuse 146 to supply fields from the path if handling a POST transaction. 147 148 Returns a dictionary mapping field names to lists of values (even if a 149 single value is associated with any given field name). 150 """ 151 152 return parse_qs(self.get_query_string(), keep_blank_values=1) 153 154 def get_fields_from_body(self, encoding=None): 155 156 """ 157 A framework-specific method which extracts the form fields from the 158 message body in the transaction. The optional 'encoding' parameter 159 specifies the character encoding of the message body for cases where no 160 such information is available, but where the default encoding is to be 161 overridden. 162 163 Returns a dictionary mapping field names to lists of values (even if a 164 single value is associated with any given field name). 165 """ 166 167 encoding = self.get_content_type().charset or encoding or "iso-8859-1" 168 fields = {} 169 for field_name, field_values in self.trans.args.items(): 170 fields[field_name] = [] 171 for field_value in field_values: 172 fields[field_name].append(unicode(field_value, encoding)) 173 return fields 174 175 def get_user(self): 176 177 """ 178 A framework-specific method which extracts user information from the 179 transaction. 180 181 Returns a username as a string or None if no user is defined. 182 """ 183 184 # Twisted makes headers lower case. 185 186 if self.user is not None: 187 return self.user 188 189 auth_header = self.get_headers().get("authorization") 190 if auth_header: 191 return UserInfo(auth_header).username 192 else: 193 return None 194 195 def get_cookies(self): 196 197 """ 198 A framework-specific method which obtains cookie information from the 199 request. 200 201 Returns a dictionary mapping cookie names to cookie objects. 202 NOTE: Twisted does not seem to support this operation via methods. Thus, 203 NOTE: direct access has been employed to get the dictionary. 204 NOTE: Twisted also returns a plain string - a Cookie object is therefore 205 NOTE: introduced. 206 """ 207 208 cookies = {} 209 for name, value in self.trans.received_cookies.items(): 210 cookies[name] = Cookie(name, value) 211 return cookies 212 213 def get_cookie(self, cookie_name): 214 215 """ 216 A framework-specific method which obtains cookie information from the 217 request. 218 219 Returns a cookie object for the given 'cookie_name' or None if no such 220 cookie exists. 221 NOTE: Twisted also returns a plain string - a Cookie object is therefore 222 NOTE: introduced. 223 """ 224 225 return Cookie(cookie_name, self.trans.getCookie(cookie_name)) 226 227 # Response-related methods. 228 229 def get_response_stream(self): 230 231 """ 232 A framework-specific method which returns the response stream for 233 the transaction. 234 """ 235 236 # Unicode can upset this operation. Using either the specified charset, 237 # the same charset as that used in the request, or a default encoding. 238 239 encoding = self.get_content_type().charset or "utf-8" 240 if self.content_type: 241 encoding = self.content_type.charset or encoding 242 return ConvertingStream(self.trans, encoding) 243 244 def get_response_code(self): 245 246 """ 247 Get the response code associated with the transaction. If no response 248 code is defined, None is returned. 249 """ 250 251 # NOTE: Accessing the request attribute directly. 252 253 return self.trans.code 254 255 def set_response_code(self, response_code): 256 257 """ 258 Set the 'response_code' using a numeric constant defined in the HTTP 259 specification. 260 """ 261 262 self.trans.setResponseCode(response_code) 263 264 def set_header_value(self, header, value): 265 266 """ 267 Set the HTTP 'header' with the given 'value'. 268 """ 269 270 self.trans.setHeader(self.format_header_value(header), self.format_header_value(value)) 271 272 def set_content_type(self, content_type): 273 274 """ 275 A framework-specific method which sets the 'content_type' for the 276 response. 277 """ 278 279 # Remember the content type for encoding purposes later. 280 281 self.content_type = content_type 282 self.trans.setHeader("Content-Type", self.format_content_type(content_type)) 283 284 # Higher level response-related methods. 285 286 def set_cookie(self, cookie): 287 288 """ 289 A framework-specific method which stores the given 'cookie' object in 290 the response. 291 """ 292 293 self.trans.addCookie(cookie.name, cookie.value, expires=cookie.expires, path=cookie.path) 294 295 def set_cookie_value(self, name, value, path=None, expires=None): 296 297 """ 298 A framework-specific method which stores a cookie with the given 'name' 299 and 'value' in the response. 300 301 The optional 'path' is a string which specifies the scope of the cookie, 302 and the optional 'expires' parameter is a value compatible with the 303 time.time function, and indicates the expiry date/time of the cookie. 304 """ 305 306 self.trans.addCookie(self.format_header_value(name), 307 self.format_header_value(value), expires=expires, path=path) 308 309 def delete_cookie(self, cookie_name): 310 311 """ 312 A framework-specific method which adds to the response a request that 313 the cookie with the given 'cookie_name' be deleted/discarded by the 314 client. 315 """ 316 317 # Create a special cookie, given that we do not know whether the browser 318 # has been sent the cookie or not. 319 # NOTE: Magic discovered in Webware. 320 321 self.trans.addCookie(cookie_name, "", expires=0, path="/", max_age=0) 322 323 # Application-specific methods. 324 325 def set_user(self, username): 326 327 """ 328 An application-specific method which sets the user information with 329 'username' in the transaction. This affects subsequent calls to 330 'get_user'. 331 """ 332 333 self.user = username 334 335 # vim: tabstop=4 expandtab shiftwidth=4