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.get_path_without_query() 147 qs = self.get_query_string() 148 if qs: 149 path += "?" 150 path += qs 151 return path 152 153 def get_path_without_query(self): 154 155 """ 156 A framework-specific method which gets the entire path from the request 157 minus the query string. 158 """ 159 160 path = self.env.get("SCRIPT_NAME") or "" 161 if self.env.has_key("PATH_INFO"): 162 path += self.env["PATH_INFO"] 163 return path 164 165 def get_path_info(self): 166 167 """ 168 A framework-specific method which gets the "path info" (the part of the 169 URL after the resource name handling the current request) from the 170 request. 171 """ 172 173 return self.env.get("PATH_INFO") or "" 174 175 def get_query_string(self): 176 177 """ 178 A framework-specific method which gets the query string from the path in 179 the request. 180 """ 181 182 return self.env.get("QUERY_STRING") or "" 183 184 # Higher level request-related methods. 185 186 def get_fields_from_path(self): 187 188 """ 189 A framework-specific method which extracts the form fields from the 190 path specified in the transaction. The underlying framework may refuse 191 to supply fields from the path if handling a POST transaction. 192 193 Returns a dictionary mapping field names to lists of values (even if a 194 single value is associated with any given field name). 195 """ 196 197 return parse_qs(self.get_query_string(), keep_blank_values=1) 198 199 def get_fields_from_body(self): 200 201 """ 202 A framework-specific method which extracts the form fields from the 203 message body in the transaction. 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 if self.storage_body is None: 210 self.storage_body = FieldStorage(fp=self.get_request_stream(), keep_blank_values=1) 211 212 # Avoid strange design issues with FieldStorage by checking the internal 213 # field list directly. 214 215 fields = {} 216 if self.storage_body.list is not None: 217 218 # Traverse the storage, finding each field value. 219 220 for field_name in self.storage_body.keys(): 221 fields[field_name] = self.storage_body.getlist(field_name) 222 return fields 223 224 def get_user(self): 225 226 """ 227 A framework-specific method which extracts user information from the 228 transaction. 229 230 Returns a username as a string or None if no user is defined. 231 """ 232 233 if self.user is not None: 234 return self.user 235 else: 236 return self.env.get("REMOTE_USER") 237 238 def get_cookies(self): 239 240 """ 241 A framework-specific method which obtains cookie information from the 242 request. 243 244 Returns a dictionary mapping cookie names to cookie objects. 245 """ 246 247 return self.cookies_in 248 249 def get_cookie(self, cookie_name): 250 251 """ 252 A framework-specific method which obtains cookie information from the 253 request. 254 255 Returns a cookie object for the given 'cookie_name' or None if no such 256 cookie exists. 257 """ 258 259 return self.cookies_in.get(cookie_name) 260 261 # Response-related methods. 262 263 def get_response_stream(self): 264 265 """ 266 A framework-specific method which returns the response stream for 267 the transaction. 268 """ 269 270 # Return a stream which is later emptied into the real stream. 271 272 return self.content 273 274 def get_response_code(self): 275 276 """ 277 Get the response code associated with the transaction. If no response 278 code is defined, None is returned. 279 """ 280 281 return self.response_code 282 283 def set_response_code(self, response_code): 284 285 """ 286 Set the 'response_code' using a numeric constant defined in the HTTP 287 specification. 288 """ 289 290 self.response_code = response_code 291 292 def set_header_value(self, header, value): 293 294 """ 295 Set the HTTP 'header' with the given 'value'. 296 """ 297 298 # The header is not written out immediately due to the buffering in use. 299 300 self.headers_out[header] = value 301 302 def set_content_type(self, content_type): 303 304 """ 305 A framework-specific method which sets the 'content_type' for the 306 response. 307 """ 308 309 # The content type has to be written as a header, before actual content, 310 # but after the response line. This means that some kind of buffering is 311 # required. Hence, we don't write the header out immediately. 312 313 self.content_type = content_type 314 315 # Higher level response-related methods. 316 317 def set_cookie(self, cookie): 318 319 """ 320 A framework-specific method which stores the given 'cookie' object in 321 the response. 322 """ 323 324 # NOTE: If multiple cookies of the same name could be specified, this 325 # NOTE: could need changing. 326 327 self.cookies_out[cookie.name] = cookie.value 328 329 def set_cookie_value(self, name, value, path=None, expires=None): 330 331 """ 332 A framework-specific method which stores a cookie with the given 'name' 333 and 'value' in the response. 334 335 The optional 'path' is a string which specifies the scope of the cookie, 336 and the optional 'expires' parameter is a value compatible with the 337 time.time function, and indicates the expiry date/time of the cookie. 338 """ 339 340 self.cookies_out[name] = value 341 if path is not None: 342 self.cookies_out[name]["path"] = path 343 if expires is not None: 344 self.cookies_out[name]["expires"] = expires 345 346 def delete_cookie(self, cookie_name): 347 348 """ 349 A framework-specific method which adds to the response a request that 350 the cookie with the given 'cookie_name' be deleted/discarded by the 351 client. 352 """ 353 354 # Create a special cookie, given that we do not know whether the browser 355 # has been sent the cookie or not. 356 # NOTE: Magic discovered in Webware. 357 358 self.cookies_out[cookie_name] = "" 359 self.cookies_out[cookie_name]["path"] = "/" 360 self.cookies_out[cookie_name]["expires"] = 0 361 self.cookies_out[cookie_name]["max-age"] = 0 362 363 # Application-specific methods. 364 365 def set_user(self, username): 366 367 """ 368 An application-specific method which sets the user information with 369 'username' in the transaction. This affects subsequent calls to 370 'get_user'. 371 """ 372 373 self.user = username 374 375 # vim: tabstop=4 expandtab shiftwidth=4