1 #!/usr/bin/env python 2 3 """ 4 CGI classes. 5 6 Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 import Generic 24 import os, sys 25 from Helpers.Request import MessageBodyStream, get_body_fields, get_storage_items, Cookie 26 from Helpers.Response import ConvertingStream 27 from Helpers.Auth import UserInfo 28 from Helpers.Session import SessionStore 29 from Helpers import Environment 30 from cgi import parse_qs, FieldStorage 31 from Cookie import SimpleCookie 32 from StringIO import StringIO 33 34 class Transaction(Generic.Transaction): 35 36 """ 37 CGI transaction interface. 38 """ 39 40 def __init__(self, input=None, output=None, env=None): 41 42 """ 43 Initialise the transaction using the CGI 'input' and 'output' streams. 44 These streams are optional and default to standard input and standard 45 output respectively. 46 """ 47 48 self.input = input or sys.stdin 49 self.output = output or sys.stdout 50 self.env = env or os.environ 51 52 # Other attributes of interest in instances of this class. 53 54 self.content_type = None 55 self.response_code = 200 56 self.content = StringIO() 57 self.headers_out = {} 58 self.cookies_out = SimpleCookie() 59 self.user = None 60 self.path_info = None 61 62 # Define the incoming cookies. 63 64 self.cookies_in = SimpleCookie(self.env.get("HTTP_COOKIE")) 65 66 # Cached information. 67 68 self.storage_body = None 69 70 # Special objects retained throughout the transaction. 71 72 self.session_store = None 73 74 def commit(self): 75 76 """ 77 A special method, synchronising the transaction with framework-specific 78 objects. 79 80 See draft-coar-cgi-v11-03, section 7. 81 """ 82 83 # Close the session store. 84 85 if self.session_store is not None: 86 self.session_store.close() 87 88 # NOTE: Provide sensible messages. 89 90 self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status")) 91 if self.content_type is not None: 92 self.output.write("Content-type: %s\n" % str(self.content_type)) 93 for header, value in self.headers_out.items(): 94 self.output.write("%s: %s\n" % 95 (self.format_header_value(header), self.format_header_value(value)) 96 ) 97 self.output.write(str(self.cookies_out)) 98 self.output.write("\n") 99 100 self.content.seek(0) 101 self.output.write(self.content.read()) 102 103 # Server-related methods. 104 105 def get_server_name(self): 106 107 "Returns the server name." 108 109 return self.env.get("SERVER_NAME") 110 111 def get_server_port(self): 112 113 "Returns the server port as a string." 114 115 return self.env.get("SERVER_PORT") 116 117 # Request-related methods. 118 119 def get_request_stream(self): 120 121 """ 122 Returns the request stream for the transaction. 123 """ 124 125 return self.input 126 127 def get_request_method(self): 128 129 """ 130 Returns the request method. 131 """ 132 133 return self.env.get("REQUEST_METHOD") 134 135 def get_headers(self): 136 137 """ 138 Returns all request headers as a dictionary-like object mapping header 139 names to values. 140 """ 141 142 return Environment.get_headers(self.env) 143 144 def get_header_values(self, key): 145 146 """ 147 Returns a list of all request header values associated with the given 148 'key'. Note that according to RFC 2616, 'key' is treated as a 149 case-insensitive string. 150 """ 151 152 return self.convert_to_list(self.get_headers().get(key)) 153 154 def get_content_type(self): 155 156 """ 157 Returns the content type specified on the request, along with the 158 charset employed. 159 """ 160 161 return self.parse_content_type(self.env.get("CONTENT_TYPE")) 162 163 def get_content_charsets(self): 164 165 """ 166 Returns the character set preferences. 167 """ 168 169 return self.parse_content_preferences(None) 170 171 def get_content_languages(self): 172 173 """ 174 Returns extracted language information from the transaction. 175 """ 176 177 return self.parse_content_preferences(None) 178 179 def get_path(self): 180 181 """ 182 Returns the entire path from the request. 183 """ 184 185 path = self.get_path_without_query() 186 qs = self.get_query_string() 187 if qs: 188 path += "?" 189 path += qs 190 return path 191 192 def get_path_without_query(self): 193 194 """ 195 Returns the entire path from the request minus the query string. 196 """ 197 198 path = self.env.get("SCRIPT_NAME") or "" 199 if self.env.has_key("PATH_INFO"): 200 path += self.env["PATH_INFO"] 201 return path 202 203 def get_path_info(self): 204 205 """ 206 Returns the "path info" (the part of the URL after the resource name 207 handling the current request) from the request. 208 """ 209 210 return self.env.get("PATH_INFO") or "" 211 212 def get_query_string(self): 213 214 """ 215 Returns the query string from the path in the request. 216 """ 217 218 return self.env.get("QUERY_STRING") or "" 219 220 # Higher level request-related methods. 221 222 def get_fields_from_path(self, encoding=None): 223 224 """ 225 Extracts fields (or request parameters) from the path specified in the 226 transaction. The underlying framework may refuse to supply fields from 227 the path if handling a POST transaction. The optional 'encoding' 228 parameter specifies the character encoding of the query string for cases 229 where the default encoding is to be overridden. 230 231 Returns a dictionary mapping field names to lists of values (even if a 232 single value is associated with any given field name). 233 """ 234 235 fields = {} 236 for name, values in parse_qs(self.get_query_string(), keep_blank_values=1).items(): 237 name = self.decode_path(name, encoding) 238 fields[name] = [] 239 for value in values: 240 value = self.decode_path(value, encoding) 241 fields[name].append(value) 242 return fields 243 244 def get_fields_from_body(self, encoding=None): 245 246 """ 247 Extracts fields (or request parameters) from the message body in the 248 transaction. The optional 'encoding' parameter specifies the character 249 encoding of the message body for cases where no such information is 250 available, but where the default encoding is to be overridden. 251 252 Returns a dictionary mapping field names to lists of values (even if a 253 single value is associated with any given field name). Each value is 254 either a Unicode object (representing a simple form field, for example) 255 or a plain string (representing a file upload form field, for example). 256 """ 257 258 encoding = encoding or self.get_content_type().charset or self.default_charset 259 260 if self.storage_body is None: 261 self.storage_body = FieldStorage(fp=self.get_request_stream(), 262 headers={"content-type" : str(self.get_content_type())}, 263 environ={"REQUEST_METHOD" : self.get_request_method()}, 264 keep_blank_values=1) 265 266 # Avoid strange design issues with FieldStorage by checking the internal 267 # field list directly. 268 269 fields = {} 270 if self.storage_body.list is not None: 271 272 # Traverse the storage, finding each field value. 273 274 fields = get_body_fields(get_storage_items(self.storage_body), encoding) 275 276 return fields 277 278 def get_fields(self, encoding=None): 279 280 """ 281 Extracts fields (or request parameters) from both the path specified in 282 the transaction as well as the message body. The optional 'encoding' 283 parameter specifies the character encoding of the message body for cases 284 where no such information is available, but where the default encoding 285 is to be overridden. 286 287 Returns a dictionary mapping field names to lists of values (even if a 288 single value is associated with any given field name). Each value is 289 either a Unicode object (representing a simple form field, for example) 290 or a plain string (representing a file upload form field, for example). 291 292 Where a given field name is used in both the path and message body to 293 specify values, the values from both sources will be combined into a 294 single list associated with that field name. 295 """ 296 297 # Combine the two sources. 298 299 fields = {} 300 fields.update(self.get_fields_from_path()) 301 for name, values in self.get_fields_from_body(encoding).items(): 302 if not fields.has_key(name): 303 fields[name] = values 304 else: 305 fields[name] += values 306 return fields 307 308 def get_user(self): 309 310 """ 311 Extracts user information from the transaction. 312 313 Returns a username as a string or None if no user is defined. 314 """ 315 316 if self.user is not None: 317 return self.user 318 else: 319 return self.env.get("REMOTE_USER") 320 321 def get_cookies(self): 322 323 """ 324 Obtains cookie information from the request. 325 326 Returns a dictionary mapping cookie names to cookie objects. 327 """ 328 329 return self.process_cookies(self.cookies_in) 330 331 def get_cookie(self, cookie_name): 332 333 """ 334 Obtains cookie information from the request. 335 336 Returns a cookie object for the given 'cookie_name' or None if no such 337 cookie exists. 338 """ 339 340 cookie = self.cookies_in.get(self.encode_cookie_value(cookie_name)) 341 if cookie is not None: 342 return Cookie(cookie_name, self.decode_cookie_value(cookie.value)) 343 else: 344 return None 345 346 # Response-related methods. 347 348 def get_response_stream(self): 349 350 """ 351 Returns the response stream for the transaction. 352 """ 353 354 # Return a stream which is later emptied into the real stream. 355 # Unicode can upset this operation. Using either the specified charset 356 # or a default encoding. 357 358 encoding = self.get_response_stream_encoding() 359 return ConvertingStream(self.content, encoding) 360 361 def get_response_stream_encoding(self): 362 363 """ 364 Returns the response stream encoding. 365 """ 366 367 if self.content_type: 368 encoding = self.content_type.charset 369 else: 370 encoding = None 371 return encoding or self.default_charset 372 373 def get_response_code(self): 374 375 """ 376 Get the response code associated with the transaction. If no response 377 code is defined, None is returned. 378 """ 379 380 return self.response_code 381 382 def set_response_code(self, response_code): 383 384 """ 385 Set the 'response_code' using a numeric constant defined in the HTTP 386 specification. 387 """ 388 389 self.response_code = response_code 390 391 def set_header_value(self, header, value): 392 393 """ 394 Set the HTTP 'header' with the given 'value'. 395 """ 396 397 # The header is not written out immediately due to the buffering in use. 398 399 self.headers_out[header] = value 400 401 def set_content_type(self, content_type): 402 403 """ 404 Sets the 'content_type' for the response. 405 """ 406 407 # The content type has to be written as a header, before actual content, 408 # but after the response line. This means that some kind of buffering is 409 # required. Hence, we don't write the header out immediately. 410 411 self.content_type = content_type 412 413 # Higher level response-related methods. 414 415 def set_cookie(self, cookie): 416 417 """ 418 Stores the given 'cookie' object in the response. 419 """ 420 421 # NOTE: If multiple cookies of the same name could be specified, this 422 # NOTE: could need changing. 423 424 self.set_cookie_value(cookie.name, cookie.value) 425 426 def set_cookie_value(self, name, value, path=None, expires=None): 427 428 """ 429 Stores a cookie with the given 'name' and 'value' in the response. 430 431 The optional 'path' is a string which specifies the scope of the cookie, 432 and the optional 'expires' parameter is a value compatible with the 433 time.time function, and indicates the expiry date/time of the cookie. 434 """ 435 436 name = self.encode_cookie_value(name) 437 self.cookies_out[name] = self.encode_cookie_value(value) 438 if path is not None: 439 self.cookies_out[name]["path"] = path 440 if expires is not None: 441 self.cookies_out[name]["expires"] = expires 442 443 def delete_cookie(self, cookie_name): 444 445 """ 446 Adds to the response a request that the cookie with the given 447 'cookie_name' be deleted/discarded by the client. 448 """ 449 450 # Create a special cookie, given that we do not know whether the browser 451 # has been sent the cookie or not. 452 # NOTE: Magic discovered in Webware. 453 454 name = self.encode_cookie_value(cookie_name) 455 self.cookies_out[name] = "" 456 self.cookies_out[name]["path"] = "/" 457 self.cookies_out[name]["expires"] = 0 458 self.cookies_out[name]["max-age"] = 0 459 460 # Session-related methods. 461 462 def get_session(self, create=1): 463 464 """ 465 Gets a session corresponding to an identifier supplied in the 466 transaction. 467 468 If no session has yet been established according to information 469 provided in the transaction then the optional 'create' parameter 470 determines whether a new session will be established. 471 472 Where no session has been established and where 'create' is set to 0 473 then None is returned. In all other cases, a session object is created 474 (where appropriate) and returned. 475 """ 476 477 # NOTE: Requires configuration. 478 479 if self.session_store is None: 480 self.session_store = SessionStore(self, "WebStack-sessions") 481 return self.session_store.get_session(create) 482 483 def expire_session(self): 484 485 """ 486 Expires any session established according to information provided in the 487 transaction. 488 """ 489 490 # NOTE: Requires configuration. 491 492 if self.session_store is None: 493 self.session_store = SessionStore(self, "WebStack-sessions") 494 self.session_store.expire_session() 495 496 # vim: tabstop=4 expandtab shiftwidth=4