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