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