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 cookies = {} 259 for name, value in self.request.cookies.items(): 260 cookies[name] = Cookie(name, value) 261 return cookies 262 263 def get_cookie(self, cookie_name): 264 265 """ 266 Obtains cookie information from the request. 267 268 Returns a cookie object for the given 'cookie_name' or None if no such 269 cookie exists. 270 """ 271 272 return Cookie(cookie_name, self.request.cookies.get(cookie_name)) 273 274 # Response-related methods. 275 276 def get_response_stream(self): 277 278 """ 279 Returns the response stream for the transaction. 280 """ 281 282 # Unicode can upset this operation. Using either the specified charset 283 # or a default encoding. 284 285 encoding = self.get_response_stream_encoding() 286 return ConvertingStream(self.response, encoding) 287 288 def get_response_stream_encoding(self): 289 290 """ 291 Returns the response stream encoding. 292 """ 293 294 if self.content_type: 295 encoding = self.content_type.charset 296 else: 297 encoding = None 298 return encoding or self.default_charset 299 300 def get_response_code(self): 301 302 """ 303 Get the response code associated with the transaction. If no response 304 code is defined, None is returned. 305 """ 306 307 return self.response.status 308 309 def set_response_code(self, response_code): 310 311 """ 312 Set the 'response_code' using a numeric constant defined in the HTTP 313 specification. 314 """ 315 316 self.response.setStatus(response_code) 317 318 def set_header_value(self, header, value): 319 320 """ 321 Set the HTTP 'header' with the given 'value'. 322 """ 323 324 self.response.setHeader(header, value) 325 326 def set_content_type(self, content_type): 327 328 """ 329 Sets the 'content_type' for the response. 330 """ 331 332 self.content_type = content_type 333 self.response.setHeader("Content-Type", str(content_type)) 334 335 # Higher level response-related methods. 336 337 def set_cookie(self, cookie): 338 339 """ 340 Stores the given 'cookie' object in the response. 341 """ 342 343 self.set_cookie_value(cookie.name, cookie.value) 344 345 def set_cookie_value(self, name, value, path=None, expires=None): 346 347 """ 348 Stores a cookie with the given 'name' and 'value' in the response. 349 350 The optional 'path' is a string which specifies the scope of the cookie, 351 and the optional 'expires' parameter is a value compatible with the 352 time.time function, and indicates the expiry date/time of the cookie. 353 """ 354 355 self.response.setCookie(name, value) 356 357 def delete_cookie(self, cookie_name): 358 359 """ 360 Adds to the response a request that the cookie with the given 361 'cookie_name' be deleted/discarded by the client. 362 """ 363 364 self.response.expireCookie(cookie_name) 365 366 # Session-related methods. 367 368 def get_session(self, create=1): 369 370 """ 371 Gets a session corresponding to an identifier supplied in the 372 transaction. 373 374 If no session has yet been established according to information 375 provided in the transaction then the optional 'create' parameter 376 determines whether a new session will be established. 377 378 Where no session has been established and where 'create' is set to 0 379 then None is returned. In all other cases, a session object is created 380 (where appropriate) and returned. 381 """ 382 383 return self.request.SESSION 384 385 def expire_session(self): 386 387 """ 388 Expires any session established according to information provided in the 389 transaction. 390 """ 391 392 self.request.SESSION.invalidate() 393 394 # Application-specific methods. 395 396 def set_user(self, username): 397 398 """ 399 An application-specific method which sets the user information with 400 'username' in the transaction. This affects subsequent calls to 401 'get_user'. 402 """ 403 404 self.user = username 405 406 # vim: tabstop=4 expandtab shiftwidth=4