1 #!/usr/bin/env python 2 3 """ 4 Zope classes. 5 In places this resembles CGI a lot because Zope seems to recycle a lot of that 6 baggage. 7 """ 8 9 import Generic 10 from Helpers import Environment 11 from Helpers.Request import Cookie, get_body_field, filter_fields 12 from Helpers.Response import ConvertingStream 13 import cgi 14 15 class Transaction(Generic.Transaction): 16 17 """ 18 Zope transaction interface. 19 """ 20 21 def __init__(self, request): 22 23 """ 24 Initialise the transaction with the Zope 'request' object. 25 """ 26 27 self.request = request 28 self.response = request.RESPONSE 29 30 # Attributes which may be changed later. 31 32 self.content_type = None 33 self.user = None 34 35 # Request-related methods. 36 37 def get_request_stream(self): 38 39 """ 40 Returns the request stream for the transaction. 41 """ 42 43 # NOTE: Possibly not safe. 44 45 return self.request.stdin 46 47 def get_request_method(self): 48 49 """ 50 Returns the request method. 51 """ 52 53 return self.request.environ.get("REQUEST_METHOD") 54 55 def get_headers(self): 56 57 """ 58 Returns all request headers as a dictionary-like object mapping header 59 names to values. 60 """ 61 62 return Environment.get_headers(self.request.environ) 63 64 def get_header_values(self, key): 65 66 """ 67 Returns a list of all request header values associated with the given 68 'key'. Note that according to RFC 2616, 'key' is treated as a 69 case-insensitive string. 70 """ 71 72 return self.convert_to_list(self.get_headers().get(key)) 73 74 def get_content_type(self): 75 76 """ 77 Returns the content type specified on the request, along with the 78 charset employed. 79 """ 80 81 return self.parse_content_type(self.request.environ.get("CONTENT_TYPE")) 82 83 def get_content_charsets(self): 84 85 """ 86 Returns the character set preferences. 87 88 NOTE: Not decently supported. 89 """ 90 91 return self.parse_content_preferences(None) 92 93 def get_content_languages(self): 94 95 """ 96 Returns extracted language information from the transaction. 97 98 NOTE: Not decently supported. 99 """ 100 101 return self.parse_content_preferences(None) 102 103 def get_path(self): 104 105 """ 106 Returns the entire path from the request. 107 """ 108 109 # NOTE: Based on WebStack.CGI.get_path. 110 111 path = self.get_path_without_query() 112 qs = self.get_query_string() 113 if qs: 114 path += "?" 115 path += qs 116 return path 117 118 def get_path_without_query(self): 119 120 """ 121 Returns the entire path from the request minus the query string. 122 """ 123 124 # NOTE: Based on WebStack.CGI.get_path. 125 126 path = self.request.environ.get("SCRIPT_NAME") or "" 127 if self.request.environ.has_key("PATH_INFO"): 128 path += self.request.environ["PATH_INFO"] 129 return path 130 131 def get_path_info(self): 132 133 """ 134 Returns the "path info" (the part of the URL after the resource name 135 handling the current request) from the request. 136 """ 137 138 return self.request.environ.get("PATH_INFO") or "" 139 140 def get_query_string(self): 141 142 """ 143 Returns the query string from the path in the request. 144 """ 145 146 return self.request.environ.get("QUERY_STRING") or "" 147 148 # Higher level request-related methods. 149 150 def get_fields_from_path(self): 151 152 """ 153 Extracts fields (or request parameters) from the path specified in the 154 transaction. The underlying framework may refuse to supply fields from 155 the path if handling a POST transaction. 156 157 Returns a dictionary mapping field names to lists of values (even if a 158 single value is associated with any given field name). 159 """ 160 161 # NOTE: Support at best ISO-8859-1 values. 162 163 fields = {} 164 for name, values in cgi.parse_qs(self.get_query_string()).items(): 165 fields[name] = [] 166 for value in values: 167 fields[name].append(unicode(value, "iso-8859-1")) 168 return fields 169 170 def get_fields_from_body(self, encoding=None): 171 172 """ 173 Extracts fields (or request parameters) from the message body in the 174 transaction. The optional 'encoding' parameter specifies the character 175 encoding of the message body for cases where no such information is 176 available, but where the default encoding is to be overridden. 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). Each value is 180 either a Unicode object (representing a simple form field, for example) 181 or a plain string (representing a file upload form field, for example). 182 """ 183 184 all_fields = self._get_fields(encoding) 185 fields_from_path = self.get_fields_from_path() 186 return filter_fields(all_fields, fields_from_path) 187 188 def _get_fields(self, encoding=None): 189 encoding = encoding or self.get_content_type().charset or self.default_charset 190 fields = {} 191 for field_name, field_values in self.request.form.items(): 192 193 # Find the body values. 194 195 if type(field_values) == type([]): 196 fields[field_name] = [] 197 for field_str in field_values: 198 fields[field_name].append(get_body_field(field_str, encoding)) 199 else: 200 fields[field_name] = [get_body_field(field_values, encoding)] 201 202 return fields 203 204 def get_fields(self, encoding=None): 205 206 """ 207 Extracts fields (or request parameters) from both the path specified in 208 the transaction as well as the message body. The optional 'encoding' 209 parameter specifies the character encoding of the message body for cases 210 where no such information is available, but where the default encoding 211 is to be overridden. 212 213 Returns a dictionary mapping field names to lists of values (even if a 214 single value is associated with any given field name). Each value is 215 either a Unicode object (representing a simple form field, for example) 216 or a plain string (representing a file upload form field, for example). 217 218 Where a given field name is used in both the path and message body to 219 specify values, the values from both sources will be combined into a 220 single list associated with that field name. 221 """ 222 223 # NOTE: Zope seems to provide only body fields upon POST requests. 224 225 if self.get_request_method() == "GET": 226 return self._get_fields(encoding) 227 else: 228 fields = {} 229 fields.update(self.get_fields_from_path()) 230 for name, values in self._get_fields(encoding).items(): 231 if not fields.has_key(name): 232 fields[name] = values 233 else: 234 fields[name] += values 235 return fields 236 237 def get_user(self): 238 239 """ 240 Extracts user information from the transaction. 241 242 Returns a username as a string or None if no user is defined. 243 """ 244 245 if self.user is not None: 246 return self.user 247 else: 248 return self.request.environ.get("REMOTE_USER") 249 250 def get_cookies(self): 251 252 """ 253 Obtains cookie information from the request. 254 255 Returns a dictionary mapping cookie names to cookie objects. 256 """ 257 258 return self.process_cookies(self.request.cookies, using_strings=1) 259 260 def get_cookie(self, cookie_name): 261 262 """ 263 Obtains cookie information from the request. 264 265 Returns a cookie object for the given 'cookie_name' or None if no such 266 cookie exists. 267 """ 268 269 value = self.request.cookies.get(self.encode_cookie_value(cookie_name)) 270 if value is not None: 271 return Cookie(cookie_name, value) 272 else: 273 return None 274 275 # Response-related methods. 276 277 def get_response_stream(self): 278 279 """ 280 Returns the response stream for the transaction. 281 """ 282 283 # Unicode can upset this operation. Using either the specified charset 284 # or a default encoding. 285 286 encoding = self.get_response_stream_encoding() 287 return ConvertingStream(self.response, encoding) 288 289 def get_response_stream_encoding(self): 290 291 """ 292 Returns the response stream encoding. 293 """ 294 295 if self.content_type: 296 encoding = self.content_type.charset 297 else: 298 encoding = None 299 return encoding or self.default_charset 300 301 def get_response_code(self): 302 303 """ 304 Get the response code associated with the transaction. If no response 305 code is defined, None is returned. 306 """ 307 308 return self.response.status 309 310 def set_response_code(self, response_code): 311 312 """ 313 Set the 'response_code' using a numeric constant defined in the HTTP 314 specification. 315 """ 316 317 self.response.setStatus(response_code) 318 319 def set_header_value(self, header, value): 320 321 """ 322 Set the HTTP 'header' with the given 'value'. 323 """ 324 325 self.response.setHeader(header, value) 326 327 def set_content_type(self, content_type): 328 329 """ 330 Sets the 'content_type' for the response. 331 """ 332 333 self.content_type = content_type 334 self.response.setHeader("Content-Type", str(content_type)) 335 336 # Higher level response-related methods. 337 338 def set_cookie(self, cookie): 339 340 """ 341 Stores the given 'cookie' object in the response. 342 """ 343 344 self.set_cookie_value(cookie.name, cookie.value) 345 346 def set_cookie_value(self, name, value, path=None, expires=None): 347 348 """ 349 Stores a cookie with the given 'name' and 'value' in the response. 350 351 The optional 'path' is a string which specifies the scope of the cookie, 352 and the optional 'expires' parameter is a value compatible with the 353 time.time function, and indicates the expiry date/time of the cookie. 354 """ 355 356 self.response.setCookie(self.encode_cookie_value(name), self.encode_cookie_value(value)) 357 358 def delete_cookie(self, cookie_name): 359 360 """ 361 Adds to the response a request that the cookie with the given 362 'cookie_name' be deleted/discarded by the client. 363 """ 364 365 self.response.expireCookie(self.encode_cookie_value(cookie_name)) 366 367 # Session-related methods. 368 369 def get_session(self, create=1): 370 371 """ 372 Gets a session corresponding to an identifier supplied in the 373 transaction. 374 375 If no session has yet been established according to information 376 provided in the transaction then the optional 'create' parameter 377 determines whether a new session will be established. 378 379 Where no session has been established and where 'create' is set to 0 380 then None is returned. In all other cases, a session object is created 381 (where appropriate) and returned. 382 """ 383 384 return self.request.SESSION 385 386 def expire_session(self): 387 388 """ 389 Expires any session established according to information provided in the 390 transaction. 391 """ 392 393 self.request.SESSION.invalidate() 394 395 # Application-specific methods. 396 397 def set_user(self, username): 398 399 """ 400 An application-specific method which sets the user information with 401 'username' in the transaction. This affects subsequent calls to 402 'get_user'. 403 """ 404 405 self.user = username 406 407 # vim: tabstop=4 expandtab shiftwidth=4