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