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, get_body_fields, get_storage_items 10 from Helpers.Response import ConvertingStream 11 from Helpers.Auth import UserInfo 12 from Helpers import Environment 13 from cgi import parse_qs, FieldStorage 14 import Cookie 15 from StringIO import StringIO 16 17 class Transaction(Generic.Transaction): 18 19 """ 20 CGI transaction interface. 21 """ 22 23 def __init__(self, input=None, output=None, env=None): 24 25 """ 26 Initialise the transaction using the CGI 'input' and 'output' streams. 27 These streams are optional and default to standard input and standard 28 output respectively. 29 """ 30 31 self.input = input or sys.stdin 32 self.output = output or sys.stdout 33 self.env = env or os.environ 34 35 # Other attributes of interest in instances of this class. 36 37 self.content_type = None 38 self.response_code = 200 39 self.content = StringIO() 40 self.headers_out = {} 41 self.cookies_out = Cookie.SimpleCookie() 42 self.user = None 43 44 # Define the incoming cookies. 45 46 self.cookies_in = Cookie.SimpleCookie(self.env.get("HTTP_COOKIE")) 47 48 # Cached information. 49 50 self.storage_body = None 51 52 def commit(self): 53 54 """ 55 A special method, synchronising the transaction with framework-specific 56 objects. 57 58 See draft-coar-cgi-v11-03, section 7. 59 """ 60 61 # NOTE: Provide sensible messages. 62 63 self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status")) 64 if self.content_type is not None: 65 self.output.write("Content-type: %s\n" % self.format_content_type(self.content_type)) 66 for header, value in self.headers_out.items(): 67 self.output.write("%s: %s\n" % 68 (self.format_header_value(header), self.format_header_value(value)) 69 ) 70 self.output.write(str(self.cookies_out)) 71 self.output.write("\n") 72 self.output.write("\n") 73 74 self.content.seek(0) 75 self.output.write(self.content.read()) 76 77 # Request-related methods. 78 79 def get_request_stream(self): 80 81 """ 82 Returns the request stream for the transaction. 83 """ 84 85 return self.input 86 87 def get_request_method(self): 88 89 """ 90 Returns the request method. 91 """ 92 93 return self.env.get("REQUEST_METHOD") 94 95 def get_headers(self): 96 97 """ 98 Returns all request headers as a dictionary-like object mapping header 99 names to values. 100 """ 101 102 return Environment.get_headers(self.env) 103 104 def get_header_values(self, key): 105 106 """ 107 Returns a list of all request header values associated with the given 108 'key'. Note that according to RFC 2616, 'key' is treated as a 109 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 Returns the content type specified on the request, along with the 118 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 Returns extracted language information from the transaction. 135 """ 136 137 return self.parse_content_preferences(None) 138 139 def get_path(self): 140 141 """ 142 Returns the entire path from the request. 143 """ 144 145 path = self.get_path_without_query() 146 qs = self.get_query_string() 147 if qs: 148 path += "?" 149 path += qs 150 return path 151 152 def get_path_without_query(self): 153 154 """ 155 Returns the entire path from the request minus the query string. 156 """ 157 158 path = self.env.get("SCRIPT_NAME") or "" 159 if self.env.has_key("PATH_INFO"): 160 path += self.env["PATH_INFO"] 161 return path 162 163 def get_path_info(self): 164 165 """ 166 Returns the "path info" (the part of the URL after the resource name 167 handling the current request) from the request. 168 """ 169 170 return self.env.get("PATH_INFO") or "" 171 172 def get_query_string(self): 173 174 """ 175 Returns the query string from the path in the request. 176 """ 177 178 return self.env.get("QUERY_STRING") or "" 179 180 # Higher level request-related methods. 181 182 def get_fields_from_path(self): 183 184 """ 185 Extracts the form fields from the path specified in the transaction. The 186 underlying framework may refuse to supply fields from the path if 187 handling a POST transaction. 188 189 Returns a dictionary mapping field names to lists of values (even if a 190 single value is associated with any given field name). 191 """ 192 193 return parse_qs(self.get_query_string(), keep_blank_values=1) 194 195 def get_fields_from_body(self, encoding=None): 196 197 """ 198 Extracts the form fields from the message body in the transaction. The 199 optional 'encoding' parameter specifies the character encoding of the 200 message body for cases where no such information is available, but where 201 the default encoding is to be overridden. 202 203 Returns a dictionary mapping field names to lists of values (even if a 204 single value is associated with any given field name). Each value is 205 either a Unicode object (representing a simple form field, for example) 206 or a plain string (representing a file upload form field, for example). 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(), keep_blank_values=1) 213 214 # Avoid strange design issues with FieldStorage by checking the internal 215 # field list directly. 216 217 fields = {} 218 if self.storage_body.list is not None: 219 220 # Traverse the storage, finding each field value. 221 222 fields = get_body_fields(get_storage_items(self.storage_body), encoding) 223 224 return fields 225 226 def get_user(self): 227 228 """ 229 Extracts user information from the transaction. 230 231 Returns a username as a string or None if no user is defined. 232 """ 233 234 if self.user is not None: 235 return self.user 236 else: 237 return self.env.get("REMOTE_USER") 238 239 def get_cookies(self): 240 241 """ 242 Obtains cookie information from the 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 Obtains cookie information from the request. 253 254 Returns a cookie object for the given 'cookie_name' or None if no such 255 cookie exists. 256 """ 257 258 return self.cookies_in.get(cookie_name) 259 260 # Response-related methods. 261 262 def get_response_stream(self): 263 264 """ 265 Returns the response stream for the transaction. 266 """ 267 268 # Return a stream which is later emptied into the real stream. 269 # Unicode can upset this operation. Using either the specified charset, 270 # the same charset as that used in the request, or a default encoding. 271 272 encoding = self.get_content_type().charset or "utf-8" 273 if self.content_type: 274 encoding = self.content_type.charset or encoding 275 return ConvertingStream(self.content, encoding) 276 277 def get_response_code(self): 278 279 """ 280 Get the response code associated with the transaction. If no response 281 code is defined, None is returned. 282 """ 283 284 return self.response_code 285 286 def set_response_code(self, response_code): 287 288 """ 289 Set the 'response_code' using a numeric constant defined in the HTTP 290 specification. 291 """ 292 293 self.response_code = response_code 294 295 def set_header_value(self, header, value): 296 297 """ 298 Set the HTTP 'header' with the given 'value'. 299 """ 300 301 # The header is not written out immediately due to the buffering in use. 302 303 self.headers_out[header] = value 304 305 def set_content_type(self, content_type): 306 307 """ 308 Sets the 'content_type' for the response. 309 """ 310 311 # The content type has to be written as a header, before actual content, 312 # but after the response line. This means that some kind of buffering is 313 # required. Hence, we don't write the header out immediately. 314 315 self.content_type = content_type 316 317 # Higher level response-related methods. 318 319 def set_cookie(self, cookie): 320 321 """ 322 Stores the given 'cookie' object in the response. 323 """ 324 325 # NOTE: If multiple cookies of the same name could be specified, this 326 # NOTE: could need changing. 327 328 self.cookies_out[cookie.name] = cookie.value 329 330 def set_cookie_value(self, name, value, path=None, expires=None): 331 332 """ 333 Stores a cookie with the given 'name' 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 Adds to the response a request that the cookie with the given 350 'cookie_name' be deleted/discarded by the client. 351 """ 352 353 # Create a special cookie, given that we do not know whether the browser 354 # has been sent the cookie or not. 355 # NOTE: Magic discovered in Webware. 356 357 self.cookies_out[cookie_name] = "" 358 self.cookies_out[cookie_name]["path"] = "/" 359 self.cookies_out[cookie_name]["expires"] = 0 360 self.cookies_out[cookie_name]["max-age"] = 0 361 362 # Application-specific methods. 363 364 def set_user(self, username): 365 366 """ 367 An application-specific method which sets the user information with 368 'username' in the transaction. This affects subsequent calls to 369 'get_user'. 370 """ 371 372 self.user = username 373 374 # vim: tabstop=4 expandtab shiftwidth=4