1 #!/usr/bin/env python 2 3 """ 4 CGI classes. 5 """ 6 7 import Generic 8 import os, sys 9 from Helpers.Request import MessageBodyStream 10 from Helpers.Auth import UserInfo 11 from Helpers import Environment 12 from cgi import parse_qs, FieldStorage 13 import Cookie 14 from StringIO import StringIO 15 16 class Transaction(Generic.Transaction): 17 18 """ 19 CGI transaction interface. 20 """ 21 22 def __init__(self, input=None, output=None, env=None): 23 24 """ 25 Initialise the transaction using the CGI 'input' and 'output' streams. 26 These streams are optional and default to standard input and standard 27 output respectively. 28 """ 29 30 self.input = input or sys.stdin 31 self.output = output or sys.stdout 32 self.env = env or os.environ 33 34 # Other attributes of interest in instances of this class. 35 36 self.content_type = None 37 self.response_code = 200 38 self.content = StringIO() 39 self.headers_out = {} 40 self.cookies_out = Cookie.SimpleCookie() 41 self.user = None 42 43 # Define the incoming cookies. 44 45 self.cookies_in = Cookie.SimpleCookie(self.env.get("HTTP_COOKIE")) 46 47 def commit(self): 48 49 """ 50 A special method, synchronising the transaction with framework-specific 51 objects. 52 53 See draft-coar-cgi-v11-03, section 7. 54 """ 55 56 # NOTE: Provide sensible messages. 57 58 self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status")) 59 if self.content_type is not None: 60 self.output.write("Content-type: %s\n" % self.format_content_type(self.content_type)) 61 for header, value in self.headers_out.items(): 62 self.output.write("%s: %s\n" % 63 (self.format_header_value(header), self.format_header_value(value)) 64 ) 65 self.output.write(str(self.cookies_out)) 66 self.output.write("\n") 67 68 self.content.seek(0) 69 self.output.write(self.content.read()) 70 71 # Request-related methods. 72 73 def get_request_stream(self): 74 75 """ 76 A framework-specific method which returns the request stream for 77 the transaction. 78 """ 79 80 return self.input 81 82 def get_request_method(self): 83 84 """ 85 A framework-specific method which gets the request method. 86 """ 87 88 return self.env.get("REQUEST_METHOD") 89 90 def get_headers(self): 91 92 """ 93 A framework-specific method which returns all request headers as a 94 dictionary-like object mapping header names to values. 95 """ 96 97 return Environment.get_headers(self.env) 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.get_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.env.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(None) 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(None) 134 135 def get_path(self): 136 137 """ 138 A framework-specific method which gets the entire path from the request. 139 """ 140 141 path = self.env.get("SCRIPT_NAME") or "" 142 if self.env.has_key("PATH_INFO"): 143 path += self.env["PATH_INFO"] 144 qs = self.env.get("QUERY_STRING") 145 if qs: 146 path += "?" 147 path += qs 148 return path 149 150 def get_path_info(self): 151 152 """ 153 A framework-specific method which gets the "path info" (the part of the 154 URL after the resource name handling the current request) from the 155 request. 156 """ 157 158 return self.env.get("PATH_INFO") or "" 159 160 def get_query_string(self): 161 162 """ 163 A framework-specific method which gets the query string from the path in 164 the request. 165 """ 166 167 return self.env.get("QUERY_STRING") or "" 168 169 # Higher level request-related methods. 170 171 def get_fields_from_path(self): 172 173 """ 174 A framework-specific method which extracts the form fields from the 175 path specified in the transaction. The underlying framework may refuse 176 to supply fields from the path if handling a POST transaction. 177 178 Returns a dictionary mapping field names to lists of values (even if a 179 single value is associated with any given field name). 180 """ 181 182 return parse_qs(self.get_query_string(), keep_blank_values=1) 183 184 def get_fields_from_body(self): 185 186 """ 187 A framework-specific method which extracts the form fields from the 188 message body in the 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 storage = FieldStorage(fp=self.get_request_stream(), keep_blank_values=1) 195 196 # Avoid strange design issues with FieldStorage by checking the internal 197 # field list directly. 198 199 fields = {} 200 if storage.list is not None: 201 202 # Traverse the storage, finding each field value. 203 204 for field_name in storage.keys(): 205 fields[field_name] = storage.getlist(field_name) 206 return fields 207 208 def get_user(self): 209 210 """ 211 A framework-specific method which extracts user information from the 212 transaction. 213 214 Returns a username as a string or None if no user is defined. 215 """ 216 217 if self.user is not None: 218 return self.user 219 else: 220 return self.env.get("REMOTE_USER") 221 222 def get_cookies(self): 223 224 """ 225 A framework-specific method which obtains cookie information from the 226 request. 227 228 Returns a dictionary mapping cookie names to cookie objects. 229 """ 230 231 return self.cookies_in 232 233 def get_cookie(self, cookie_name): 234 235 """ 236 A framework-specific method which obtains cookie information from the 237 request. 238 239 Returns a cookie object for the given 'cookie_name' or None if no such 240 cookie exists. 241 """ 242 243 return self.cookies_in.get(cookie_name) 244 245 # Response-related methods. 246 247 def get_response_stream(self): 248 249 """ 250 A framework-specific method which returns the response stream for 251 the transaction. 252 """ 253 254 # Return a stream which is later emptied into the real stream. 255 256 return self.content 257 258 def get_response_code(self): 259 260 """ 261 Get the response code associated with the transaction. If no response 262 code is defined, None is returned. 263 """ 264 265 return self.response_code 266 267 def set_response_code(self, response_code): 268 269 """ 270 Set the 'response_code' using a numeric constant defined in the HTTP 271 specification. 272 """ 273 274 self.response_code = response_code 275 276 def set_header_value(self, header, value): 277 278 """ 279 Set the HTTP 'header' with the given 'value'. 280 """ 281 282 # The header is not written out immediately due to the buffering in use. 283 284 self.headers_out[header] = value 285 286 def set_content_type(self, content_type): 287 288 """ 289 A framework-specific method which sets the 'content_type' for the 290 response. 291 """ 292 293 # The content type has to be written as a header, before actual content, 294 # but after the response line. This means that some kind of buffering is 295 # required. Hence, we don't write the header out immediately. 296 297 self.content_type = content_type 298 299 # Higher level response-related methods. 300 301 def set_cookie(self, cookie): 302 303 """ 304 A framework-specific method which stores the given 'cookie' object in 305 the response. 306 """ 307 308 # NOTE: If multiple cookies of the same name could be specified, this 309 # NOTE: could need changing. 310 311 self.cookies_out[cookie.name] = cookie.value 312 313 def set_cookie_value(self, name, value, path=None, expires=None): 314 315 """ 316 A framework-specific method which stores a cookie with the given 'name' 317 and 'value' in the response. 318 319 The optional 'path' is a string which specifies the scope of the cookie, 320 and the optional 'expires' parameter is a value compatible with the 321 time.time function, and indicates the expiry date/time of the cookie. 322 """ 323 324 self.cookies_out[name] = value 325 if path is not None: 326 self.cookies_out[name]["path"] = path 327 if expires is not None: 328 self.cookies_out[name]["expires"] = expires 329 330 def delete_cookie(self, cookie_name): 331 332 """ 333 A framework-specific method which adds to the response a request that 334 the cookie with the given 'cookie_name' be deleted/discarded by the 335 client. 336 """ 337 338 # Create a special cookie, given that we do not know whether the browser 339 # has been sent the cookie or not. 340 # NOTE: Magic discovered in Webware. 341 342 self.cookies_out[cookie_name] = "" 343 self.cookies_out[cookie_name]["path"] = "/" 344 self.cookies_out[cookie_name]["expires"] = 0 345 self.cookies_out[cookie_name]["max-age"] = 0 346 347 # Application-specific methods. 348 349 def set_user(self, username): 350 351 """ 352 An application-specific method which sets the user information with 353 'username' in the transaction. This affects subsequent calls to 354 'get_user'. 355 """ 356 357 self.user = username 358 359 # vim: tabstop=4 expandtab shiftwidth=4