1 #!/usr/bin/env python 2 3 """ 4 BaseHTTPRequestHandler classes. 5 """ 6 7 import Generic 8 from Helpers.Request import MessageBodyStream 9 from Helpers.Auth import UserInfo 10 from cgi import parse_qs, FieldStorage 11 import Cookie 12 from StringIO import StringIO 13 14 class Transaction(Generic.Transaction): 15 16 """ 17 BaseHTTPRequestHandler transaction interface. 18 """ 19 20 def __init__(self, trans): 21 22 """ 23 Initialise the transaction using the BaseHTTPRequestHandler instance 24 'trans'. 25 """ 26 27 self.trans = trans 28 29 # Other attributes of interest in instances of this class. 30 31 self.content_type = None 32 self.response_code = 200 33 self.content = StringIO() 34 self.headers_out = {} 35 self.cookies_out = Cookie.SimpleCookie() 36 self.user = None 37 38 # Define the incoming cookies. 39 40 self.cookies_in = Cookie.SimpleCookie(self.get_headers().get("cookie")) 41 42 # Cached information. 43 44 self.storage_body = None 45 46 def commit(self): 47 48 """ 49 A special method, synchronising the transaction with framework-specific 50 objects. 51 """ 52 53 self.trans.send_response(self.response_code) 54 if self.content_type is not None: 55 self.trans.send_header("Content-Type", self.format_content_type(self.content_type)) 56 57 for header, value in self.headers_out.items(): 58 self.trans.send_header(self.format_header_value(header), self.format_header_value(value)) 59 60 # NOTE: May not be using the appropriate method. 61 62 for morsel in self.cookies_out.values(): 63 self.trans.send_header("Set-Cookie", morsel.OutputString()) 64 65 self.trans.end_headers() 66 self.content.seek(0) 67 self.trans.wfile.write(self.content.read()) 68 69 # Request-related methods. 70 71 def get_request_stream(self): 72 73 """ 74 A framework-specific method which returns the request stream for 75 the transaction. 76 """ 77 78 return MessageBodyStream(self.trans.rfile, self.get_headers()) 79 80 def get_request_method(self): 81 82 """ 83 A framework-specific method which gets the request method. 84 """ 85 86 return self.trans.command 87 88 def get_headers(self): 89 90 """ 91 A framework-specific method which returns all request headers as a 92 dictionary-like object mapping header names to values. 93 NOTE: If duplicate header names are permitted, then this interface will 94 NOTE: need to change. 95 """ 96 97 return self.trans.headers 98 99 def get_header_values(self, key): 100 101 """ 102 A framework-specific method which returns a list of all request header 103 values associated with the given 'key'. Note that according to RFC 2616, 104 'key' is treated as a case-insensitive string. 105 """ 106 107 return self.convert_to_list(self.trans.headers.get(key)) 108 109 def get_content_type(self): 110 111 """ 112 A framework-specific method which gets the content type specified on the 113 request, along with the charset employed. 114 """ 115 116 return self.parse_content_type(self.trans.headers.get("content-type")) 117 118 def get_content_charsets(self): 119 120 """ 121 Returns the character set preferences. 122 """ 123 124 return self.parse_content_preferences(self.trans.headers.get("accept-charset")) 125 126 def get_content_languages(self): 127 128 """ 129 A framework-specific method which extracts language information from 130 the transaction. 131 """ 132 133 return self.parse_content_preferences(self.trans.headers.get("accept-language")) 134 135 def get_path(self): 136 137 """ 138 A framework-specific method which gets the entire path from the request. 139 """ 140 141 return self.trans.path 142 143 def get_path_info(self): 144 145 """ 146 A framework-specific method which gets the "path info" (the part of the 147 URL after the resource name handling the current request) from the 148 request. 149 """ 150 151 # Remove the query string from the end of the path. 152 153 return self.trans.path.split("?")[0] 154 155 def get_query_string(self): 156 157 """ 158 A framework-specific method which gets the query string from the path in 159 the request. 160 """ 161 162 t = self.trans.path.split("?") 163 if len(t) == 1: 164 return "" 165 else: 166 167 # NOTE: Overlook erroneous usage of "?" characters in the path. 168 169 return "?".join(t[1:]) 170 171 # Higher level request-related methods. 172 173 def get_fields_from_path(self): 174 175 """ 176 A framework-specific method which extracts the form fields from the 177 path specified in the transaction. The underlying framework may refuse 178 to supply fields from the path if handling a POST transaction. 179 180 Returns a dictionary mapping field names to lists of values (even if a 181 single value is associated with any given field name). 182 """ 183 184 return parse_qs(self.get_query_string(), keep_blank_values=1) 185 186 def get_fields_from_body(self): 187 188 """ 189 A framework-specific method which extracts the form fields from the 190 message body in the transaction. 191 192 Returns a dictionary mapping field names to lists of values (even if a 193 single value is associated with any given field name). 194 """ 195 196 if self.storage_body is None: 197 self.storage_body = FieldStorage(fp=self.get_request_stream(), headers=self.get_headers(), 198 environ={"REQUEST_METHOD" : self.get_request_method()}, keep_blank_values=1) 199 200 # Avoid strange design issues with FieldStorage by checking the internal 201 # field list directly. 202 203 fields = {} 204 if self.storage_body.list is not None: 205 206 # Traverse the storage, finding each field value. 207 208 for field_name in self.storage_body.keys(): 209 fields[field_name] = self.storage_body.getlist(field_name) 210 return fields 211 212 def get_user(self): 213 214 """ 215 A framework-specific method which extracts user information from the 216 transaction. 217 218 Returns a username as a string or None if no user is defined. 219 """ 220 221 if self.user is not None: 222 return self.user 223 224 auth_header = self.get_headers().get("authorization") 225 if auth_header: 226 return UserInfo(auth_header).username 227 else: 228 return None 229 230 def get_cookies(self): 231 232 """ 233 A framework-specific method which obtains cookie information from the 234 request. 235 236 Returns a dictionary mapping cookie names to cookie objects. 237 """ 238 239 return self.cookies_in 240 241 def get_cookie(self, cookie_name): 242 243 """ 244 A framework-specific method which obtains cookie information from the 245 request. 246 247 Returns a cookie object for the given 'cookie_name' or None if no such 248 cookie exists. 249 """ 250 251 return self.cookies_in.get(cookie_name) 252 253 # Response-related methods. 254 255 def get_response_stream(self): 256 257 """ 258 A framework-specific method which returns the response stream for 259 the transaction. 260 """ 261 262 # Return a stream which is later emptied into the real stream. 263 264 return self.content 265 266 def get_response_code(self): 267 268 """ 269 Get the response code associated with the transaction. If no response 270 code is defined, None is returned. 271 """ 272 273 return self.response_code 274 275 def set_response_code(self, response_code): 276 277 """ 278 Set the 'response_code' using a numeric constant defined in the HTTP 279 specification. 280 """ 281 282 self.response_code = response_code 283 284 def set_header_value(self, header, value): 285 286 """ 287 Set the HTTP 'header' with the given 'value'. 288 """ 289 290 # The header is not written out immediately due to the buffering in use. 291 292 self.headers_out[header] = value 293 294 def set_content_type(self, content_type): 295 296 """ 297 A framework-specific method which sets the 'content_type' for the 298 response. 299 """ 300 301 # The content type has to be written as a header, before actual content, 302 # but after the response line. This means that some kind of buffering is 303 # required. Hence, we don't write the header out immediately. 304 305 self.content_type = content_type 306 307 def set_cookie(self, cookie): 308 309 """ 310 A framework-specific method which stores the given 'cookie' object in 311 the response. 312 """ 313 314 # NOTE: If multiple cookies of the same name could be specified, this 315 # NOTE: could need changing. 316 317 self.cookies_out[cookie.name] = cookie.value 318 319 def set_cookie_value(self, name, value, path=None, expires=None): 320 321 """ 322 A framework-specific method which stores a cookie with the given 'name' 323 and 'value' in the response. 324 325 The optional 'path' is a string which specifies the scope of the cookie, 326 and the optional 'expires' parameter is a value compatible with the 327 time.time function, and indicates the expiry date/time of the cookie. 328 """ 329 330 self.cookies_out[name] = value 331 if path is not None: 332 self.cookies_out[name]["path"] = path 333 if expires is not None: 334 self.cookies_out[name]["expires"] = expires 335 336 def delete_cookie(self, cookie_name): 337 338 """ 339 A framework-specific method which adds to the response a request that 340 the cookie with the given 'cookie_name' be deleted/discarded by the 341 client. 342 """ 343 344 # Create a special cookie, given that we do not know whether the browser 345 # has been sent the cookie or not. 346 # NOTE: Magic discovered in Webware. 347 348 self.cookies_out[cookie_name] = "" 349 self.cookies_out[cookie_name]["path"] = "/" 350 self.cookies_out[cookie_name]["expires"] = 0 351 self.cookies_out[cookie_name]["max-age"] = 0 352 353 # Application-specific methods. 354 355 def set_user(self, username): 356 357 """ 358 An application-specific method which sets the user information with 359 'username' in the transaction. This affects subsequent calls to 360 'get_user'. 361 """ 362 363 self.user = username 364 365 # vim: tabstop=4 expandtab shiftwidth=4