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