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 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). 160 """ 161 162 encoding = self.get_content_type().charset or encoding or "iso-8859-1" 163 fields = {} 164 for field_name, field_values in self.trans.args.items(): 165 fields[field_name] = [] 166 for field_value in field_values: 167 fields[field_name].append(unicode(field_value, encoding)) 168 return fields 169 170 def get_user(self): 171 172 """ 173 Extracts user information from the transaction. 174 175 Returns a username as a string or None if no user is defined. 176 """ 177 178 # Twisted makes headers lower case. 179 180 if self.user is not None: 181 return self.user 182 183 auth_header = self.get_headers().get("authorization") 184 if auth_header: 185 return UserInfo(auth_header).username 186 else: 187 return None 188 189 def get_cookies(self): 190 191 """ 192 Obtains cookie information from the request. 193 194 Returns a dictionary mapping cookie names to cookie objects. 195 NOTE: Twisted does not seem to support this operation via methods. Thus, 196 NOTE: direct access has been employed to get the dictionary. 197 NOTE: Twisted also returns a plain string - a Cookie object is therefore 198 NOTE: introduced. 199 """ 200 201 cookies = {} 202 for name, value in self.trans.received_cookies.items(): 203 cookies[name] = Cookie(name, value) 204 return cookies 205 206 def get_cookie(self, cookie_name): 207 208 """ 209 Obtains cookie information from the request. 210 211 Returns a cookie object for the given 'cookie_name' or None if no such 212 cookie exists. 213 NOTE: Twisted also returns a plain string - a Cookie object is therefore 214 NOTE: introduced. 215 """ 216 217 return Cookie(cookie_name, self.trans.getCookie(cookie_name)) 218 219 # Response-related methods. 220 221 def get_response_stream(self): 222 223 """ 224 Returns the response stream for the transaction. 225 """ 226 227 # Unicode can upset this operation. Using either the specified charset, 228 # the same charset as that used in the request, or a default encoding. 229 230 encoding = self.get_content_type().charset or "utf-8" 231 if self.content_type: 232 encoding = self.content_type.charset or encoding 233 return ConvertingStream(self.trans, encoding) 234 235 def get_response_code(self): 236 237 """ 238 Get the response code associated with the transaction. If no response 239 code is defined, None is returned. 240 """ 241 242 # NOTE: Accessing the request attribute directly. 243 244 return self.trans.code 245 246 def set_response_code(self, response_code): 247 248 """ 249 Set the 'response_code' using a numeric constant defined in the HTTP 250 specification. 251 """ 252 253 self.trans.setResponseCode(response_code) 254 255 def set_header_value(self, header, value): 256 257 """ 258 Set the HTTP 'header' with the given 'value'. 259 """ 260 261 self.trans.setHeader(self.format_header_value(header), self.format_header_value(value)) 262 263 def set_content_type(self, content_type): 264 265 """ 266 Sets the 'content_type' for the response. 267 """ 268 269 # Remember the content type for encoding purposes later. 270 271 self.content_type = content_type 272 self.trans.setHeader("Content-Type", self.format_content_type(content_type)) 273 274 # Higher level response-related methods. 275 276 def set_cookie(self, cookie): 277 278 """ 279 Stores the given 'cookie' object in the response. 280 """ 281 282 self.trans.addCookie(cookie.name, cookie.value, expires=cookie.expires, path=cookie.path) 283 284 def set_cookie_value(self, name, value, path=None, expires=None): 285 286 """ 287 Stores a cookie with the given 'name' and 'value' in the response. 288 289 The optional 'path' is a string which specifies the scope of the cookie, 290 and the optional 'expires' parameter is a value compatible with the 291 time.time function, and indicates the expiry date/time of the cookie. 292 """ 293 294 self.trans.addCookie(self.format_header_value(name), 295 self.format_header_value(value), expires=expires, path=path) 296 297 def delete_cookie(self, cookie_name): 298 299 """ 300 Adds to the response a request that the cookie with the given 301 'cookie_name' be deleted/discarded by the client. 302 """ 303 304 # Create a special cookie, given that we do not know whether the browser 305 # has been sent the cookie or not. 306 # NOTE: Magic discovered in Webware. 307 308 self.trans.addCookie(cookie_name, "", expires=0, path="/", max_age=0) 309 310 # Application-specific methods. 311 312 def set_user(self, username): 313 314 """ 315 An application-specific method which sets the user information with 316 'username' in the transaction. This affects subsequent calls to 317 'get_user'. 318 """ 319 320 self.user = username 321 322 # vim: tabstop=4 expandtab shiftwidth=4