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 from StringIO import StringIO 12 13 class Transaction(Generic.Transaction): 14 15 """ 16 BaseHTTPRequestHandler transaction interface. 17 """ 18 19 def __init__(self, trans): 20 21 """ 22 Initialise the transaction using the BaseHTTPRequestHandler instance 23 'trans'. 24 """ 25 26 self.trans = trans 27 28 # Other attributes of interest in instances of this class. 29 30 self.content_type = None 31 self.response_code = 200 32 self.content = StringIO() 33 self.headers = {} 34 35 def commit(self): 36 37 """ 38 A special method, synchronising the transaction with framework-specific 39 objects. 40 """ 41 42 self.trans.send_response(self.response_code) 43 if self.content_type is not None: 44 self.trans.send_header("Content-Type", self.format_content_type(self.content_type)) 45 for header, value in self.headers.items(): 46 self.trans.send_header(self.format_header_value(header), self.format_header_value(value)) 47 self.trans.end_headers() 48 self.content.seek(0) 49 self.trans.wfile.write(self.content.read()) 50 51 # Request-related methods. 52 53 def get_request_stream(self): 54 55 """ 56 A framework-specific method which returns the request stream for 57 the transaction. 58 """ 59 60 return MessageBodyStream(self.trans.rfile, self.get_headers()) 61 62 def get_request_method(self): 63 64 """ 65 A framework-specific method which gets the request method. 66 """ 67 68 return self.trans.command 69 70 def get_headers(self): 71 72 """ 73 A framework-specific method which returns all request headers. 74 NOTE: If duplicate header names are permitted, then this interface will 75 NOTE: need to change. 76 """ 77 78 return self.trans.headers 79 80 def get_header_values(self, key): 81 82 """ 83 A framework-specific method which returns a list of all request header 84 values associated with the given 'key'. Note that according to RFC 2616, 85 'key' is treated as a case-insensitive string. 86 """ 87 88 return self.convert_to_list(self.trans.headers.get(key)) 89 90 def get_content_type(self): 91 92 """ 93 A framework-specific method which gets the content type specified on the 94 request, along with the charset employed. 95 """ 96 97 return self.parse_content_type(self.trans.headers.get("Content-type") or 98 self.trans.headers.get("Content-Type")) 99 100 def get_content_charsets(self): 101 102 """ 103 Returns the character set preferences. 104 """ 105 106 return self.parse_content_preferences(self.trans.headers["Accept-Charset"]) 107 108 def get_content_languages(self): 109 110 """ 111 A framework-specific method which extracts language information from 112 the transaction. 113 """ 114 115 return self.parse_content_preferences(self.trans.headers["Accept-Language"]) 116 117 def get_path(self): 118 119 """ 120 A framework-specific method which gets the entire path from the request. 121 """ 122 123 return self.trans.path 124 125 def get_path_info(self): 126 127 """ 128 A framework-specific method which gets the "path info" (the part of the 129 URL after the resource name handling the current request) from the 130 request. 131 """ 132 133 # Remove the query string from the end of the path. 134 135 return self.trans.path.split("?")[0] 136 137 def get_query_string(self): 138 139 """ 140 A framework-specific method which gets the query string from the path in 141 the request. 142 """ 143 144 t = self.trans.path.split("?") 145 if len(t) == 1: 146 return "" 147 else: 148 149 # NOTE: Overlook erroneous usage of "?" characters in the path. 150 151 return "?".join(t[1:]) 152 153 # Higher level request-related methods. 154 155 def get_fields_from_path(self): 156 157 """ 158 A framework-specific method which extracts the form fields from the 159 path specified in the transaction. The underlying framework may refuse 160 to supply fields from the path if handling a POST transaction. 161 162 Returns a dictionary mapping field names to lists of values (even if a 163 single value is associated with any given field name). 164 """ 165 166 return parse_qs(self.get_query_string(), keep_blank_values=1) 167 168 def get_fields_from_body(self): 169 170 """ 171 A framework-specific method which extracts the form fields from the 172 message body in the transaction. 173 174 Returns a dictionary mapping field names to lists of values (even if a 175 single value is associated with any given field name). 176 """ 177 178 storage = FieldStorage(fp=self.get_request_stream(), headers=self.get_headers(), 179 environ={"REQUEST_METHOD" : self.get_request_method()}, keep_blank_values=1) 180 181 # Avoid strange design issues with FieldStorage by checking the internal 182 # field list directly. 183 184 fields = {} 185 if storage.list is not None: 186 187 # Traverse the storage, finding each field value. 188 189 for field_name in storage.keys(): 190 fields[field_name] = storage.getlist(field_name) 191 return fields 192 193 def get_user(self): 194 195 """ 196 A framework-specific method which extracts user information from the 197 transaction. 198 """ 199 200 auth_header = self.get_headers().get("Authorization") 201 if auth_header: 202 return UserInfo(auth_header).username 203 else: 204 return None 205 206 # Response-related methods. 207 208 def get_response_stream(self): 209 210 """ 211 A framework-specific method which returns the response stream for 212 the transaction. 213 """ 214 215 # Return a stream which is later emptied into the real stream. 216 217 return self.content 218 219 def get_response_code(self): 220 221 """ 222 Get the response code associated with the transaction. If no response 223 code is defined, None is returned. 224 """ 225 226 return self.response_code 227 228 def set_response_code(self, response_code): 229 230 """ 231 Set the 'response_code' using a numeric constant defined in the HTTP 232 specification. 233 """ 234 235 self.response_code = response_code 236 237 def set_header_value(self, header, value): 238 239 """ 240 Set the HTTP 'header' with the given 'value'. 241 """ 242 243 # The header is not written out immediately due to the buffering in use. 244 245 self.headers[header] = value 246 247 def set_content_type(self, content_type): 248 249 """ 250 A framework-specific method which sets the 'content_type' for the 251 response. 252 """ 253 254 # The content type has to be written as a header, before actual content, 255 # but after the response line. This means that some kind of buffering is 256 # required. Hence, we don't write the header out immediately. 257 258 self.content_type = content_type 259 260 # vim: tabstop=4 expandtab shiftwidth=4