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