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