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