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 ZPublisher.HTTPRequest import FileUpload 14 import cgi 15 16 class Transaction(Generic.Transaction): 17 18 """ 19 Zope transaction interface. 20 """ 21 22 def __init__(self, request): 23 24 """ 25 Initialise the transaction with the Zope 'request' object. 26 """ 27 28 self.request = request 29 self.response = request.RESPONSE 30 31 # Attributes which may be changed later. 32 33 self.content_type = None 34 self.user = None 35 36 # Request-related methods. 37 38 def get_request_stream(self): 39 40 """ 41 Returns the request stream for the transaction. 42 """ 43 44 # NOTE: Possibly not safe. 45 46 return self.request.stdin 47 48 def get_request_method(self): 49 50 """ 51 Returns the request method. 52 """ 53 54 return self.request.environ.get("REQUEST_METHOD") 55 56 def get_headers(self): 57 58 """ 59 Returns all request headers as a dictionary-like object mapping header 60 names to values. 61 """ 62 63 return Environment.get_headers(self.request.environ) 64 65 def get_header_values(self, key): 66 67 """ 68 Returns a list of all request header values associated with the given 69 'key'. Note that according to RFC 2616, 'key' is treated as a 70 case-insensitive string. 71 """ 72 73 return self.convert_to_list(self.get_headers().get(key)) 74 75 def get_content_type(self): 76 77 """ 78 Returns the content type specified on the request, along with the 79 charset employed. 80 """ 81 82 return self.parse_content_type(self.request.environ.get("CONTENT_TYPE")) 83 84 def get_content_charsets(self): 85 86 """ 87 Returns the character set preferences. 88 89 NOTE: Not decently supported. 90 """ 91 92 return self.parse_content_preferences(None) 93 94 def get_content_languages(self): 95 96 """ 97 Returns extracted language information from the transaction. 98 99 NOTE: Not decently supported. 100 """ 101 102 return self.parse_content_preferences(None) 103 104 def get_path(self): 105 106 """ 107 Returns the entire path from the request. 108 """ 109 110 # NOTE: Based on WebStack.CGI.get_path. 111 112 path = self.get_path_without_query() 113 qs = self.get_query_string() 114 if qs: 115 path += "?" 116 path += qs 117 return path 118 119 def get_path_without_query(self): 120 121 """ 122 Returns the entire path from the request minus the query string. 123 """ 124 125 # NOTE: Based on WebStack.CGI.get_path. 126 127 path = self.request.environ.get("SCRIPT_NAME") or "" 128 if self.request.environ.has_key("PATH_INFO"): 129 path += self.request.environ["PATH_INFO"] 130 return path 131 132 def get_path_info(self): 133 134 """ 135 Returns the "path info" (the part of the URL after the resource name 136 handling the current request) from the request. 137 """ 138 139 return self.request.environ.get("PATH_INFO") or "" 140 141 def get_query_string(self): 142 143 """ 144 Returns the query string from the path in the request. 145 """ 146 147 return self.request.environ.get("QUERY_STRING") or "" 148 149 # Higher level request-related methods. 150 151 def get_fields_from_path(self): 152 153 """ 154 Extracts fields (or request parameters) from the path specified in the 155 transaction. The underlying framework may refuse to supply fields from 156 the path if handling a POST transaction. 157 158 Returns a dictionary mapping field names to lists of values (even if a 159 single value is associated with any given field name). 160 """ 161 162 # NOTE: Support at best ISO-8859-1 values. 163 164 fields = {} 165 for name, values in cgi.parse_qs(self.get_query_string()).items(): 166 fields[name] = [] 167 for value in values: 168 fields[name].append(unicode(value, "iso-8859-1")) 169 return fields 170 171 def get_fields_from_body(self, encoding=None): 172 173 """ 174 Extracts fields (or request parameters) from the message body in the 175 transaction. The optional 'encoding' parameter specifies the character 176 encoding of the message body for cases where no such information is 177 available, but where the default encoding is to be overridden. 178 179 Returns a dictionary mapping field names to lists of values (even if a 180 single value is associated with any given field name). Each value is 181 either a Unicode object (representing a simple form field, for example) 182 or a plain string (representing a file upload form field, for example). 183 """ 184 185 all_fields = self._get_fields(encoding) 186 fields_from_path = self.get_fields_from_path() 187 return filter_fields(all_fields, fields_from_path) 188 189 def _get_fields(self, encoding=None): 190 encoding = encoding or self.get_content_type().charset or self.default_charset 191 fields = {} 192 for field_name, field_values in self.request.form.items(): 193 194 # Find the body values. 195 196 if type(field_values) == type([]): 197 fields[field_name] = [] 198 for field_str in field_values: 199 fields[field_name].append(get_body_field(field_str, encoding)) 200 else: 201 fields[field_name] = [get_body_field(field_values, encoding)] 202 203 return fields 204 205 def get_fields(self, encoding=None): 206 207 """ 208 Extracts fields (or request parameters) from both the path specified in 209 the transaction as well as the message body. The optional 'encoding' 210 parameter specifies the character encoding of the message body for cases 211 where no such information is available, but where the default encoding 212 is to be overridden. 213 214 Returns a dictionary mapping field names to lists of values (even if a 215 single value is associated with any given field name). Each value is 216 either a Unicode object (representing a simple form field, for example) 217 or a plain string (representing a file upload form field, for example). 218 219 Where a given field name is used in both the path and message body to 220 specify values, the values from both sources will be combined into a 221 single list associated with that field name. 222 """ 223 224 # NOTE: Zope seems to provide only body fields upon POST requests. 225 226 if self.get_request_method() == "GET": 227 return self._get_fields(encoding) 228 else: 229 fields = {} 230 fields.update(self.get_fields_from_path()) 231 for name, values in self._get_fields(encoding).items(): 232 if not fields.has_key(name): 233 fields[name] = values 234 else: 235 fields[name] += values 236 return fields 237 238 def get_user(self): 239 240 """ 241 Extracts user information from the transaction. 242 243 Returns a username as a string or None if no user is defined. 244 """ 245 246 if self.user is not None: 247 return self.user 248 else: 249 return self.request.environ.get("REMOTE_USER") 250 251 def get_cookies(self): 252 253 """ 254 Obtains cookie information from the request. 255 256 Returns a dictionary mapping cookie names to cookie objects. 257 """ 258 259 cookies = {} 260 for name, value in self.request.cookies.items(): 261 cookies[name] = Cookie(name, value) 262 return cookies 263 264 def get_cookie(self, cookie_name): 265 266 """ 267 Obtains cookie information from the request. 268 269 Returns a cookie object for the given 'cookie_name' or None if no such 270 cookie exists. 271 """ 272 273 return Cookie(cookie_name, self.request.cookies.get(cookie_name)) 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(name, 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(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