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 12 from Helpers.Response import ConvertingStream 13 from ZPublisher.HTTPRequest import FileUpload 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 the form fields from the path specified in the transaction. The 154 underlying framework may refuse to supply fields from the path if 155 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 fields = {} 162 for key, value in self.request.form.items(): 163 if type(value) == type([]): 164 fields[key] = value 165 else: 166 fields[key] = [value] 167 return fields 168 169 def get_fields_from_body(self, encoding=None): 170 171 """ 172 Extracts the form fields from the message body in the transaction. The 173 optional 'encoding' parameter specifies the character encoding of the 174 message body for cases where no such information is available, but where 175 the default encoding is to be overridden. 176 177 Returns a dictionary mapping field names to lists of values (even if a 178 single value is associated with any given field name). Each value is 179 either a Unicode object (representing a simple form field, for example) 180 or a plain string (representing a file upload form field, for example). 181 182 NOTE: Zope doesn't distinguish between path and body fields. 183 """ 184 185 # NOTE: Conversion to Unicode may be inappropriate. 186 187 encoding = encoding or self.get_content_type().charset or self.default_charset 188 fields = {} 189 for field_name, field_values in self.get_fields_from_path().items(): 190 if type(field_values) == type([]): 191 fields[field_name] = [] 192 for field_str in field_values: 193 fields[field_name].append(get_body_field(field_str, encoding)) 194 else: 195 fields[field_name] = [get_body_field(field_values, encoding)] 196 return fields 197 198 def get_user(self): 199 200 """ 201 Extracts user information from the transaction. 202 203 Returns a username as a string or None if no user is defined. 204 """ 205 206 if self.user is not None: 207 return self.user 208 else: 209 return self.request.environ.get("REMOTE_USER") 210 211 def get_cookies(self): 212 213 """ 214 Obtains cookie information from the request. 215 216 Returns a dictionary mapping cookie names to cookie objects. 217 """ 218 219 cookies = {} 220 for name, value in self.request.cookies.items(): 221 cookies[name] = Cookie(name, value) 222 return cookies 223 224 def get_cookie(self, cookie_name): 225 226 """ 227 Obtains cookie information from the request. 228 229 Returns a cookie object for the given 'cookie_name' or None if no such 230 cookie exists. 231 """ 232 233 return Cookie(cookie_name, self.request.cookies.get(cookie_name)) 234 235 # Response-related methods. 236 237 def get_response_stream(self): 238 239 """ 240 Returns the response stream for the transaction. 241 """ 242 243 # Unicode can upset this operation. Using either the specified charset 244 # or a default encoding. 245 246 if self.content_type: 247 encoding = self.content_type.charset 248 encoding = encoding or self.default_charset 249 return ConvertingStream(self.response, encoding) 250 251 def get_response_code(self): 252 253 """ 254 Get the response code associated with the transaction. If no response 255 code is defined, None is returned. 256 """ 257 258 return self.response.status 259 260 def set_response_code(self, response_code): 261 262 """ 263 Set the 'response_code' using a numeric constant defined in the HTTP 264 specification. 265 """ 266 267 self.response.setStatus(response_code) 268 269 def set_header_value(self, header, value): 270 271 """ 272 Set the HTTP 'header' with the given 'value'. 273 """ 274 275 self.response.setHeader(header, value) 276 277 def set_content_type(self, content_type): 278 279 """ 280 Sets the 'content_type' for the response. 281 """ 282 283 self.content_type = content_type 284 self.response.setHeader("Content-Type", str(content_type)) 285 286 # Higher level response-related methods. 287 288 def set_cookie(self, cookie): 289 290 """ 291 Stores the given 'cookie' object in the response. 292 """ 293 294 self.set_cookie_value(cookie.name, cookie.value) 295 296 def set_cookie_value(self, name, value, path=None, expires=None): 297 298 """ 299 Stores a cookie with the given 'name' and 'value' in the response. 300 301 The optional 'path' is a string which specifies the scope of the cookie, 302 and the optional 'expires' parameter is a value compatible with the 303 time.time function, and indicates the expiry date/time of the cookie. 304 """ 305 306 self.response.setCookie(name, value) 307 308 def delete_cookie(self, cookie_name): 309 310 """ 311 Adds to the response a request that the cookie with the given 312 'cookie_name' be deleted/discarded by the client. 313 """ 314 315 self.response.expireCookie(cookie_name) 316 317 # Session-related methods. 318 319 def get_session(self, create=1): 320 321 """ 322 Gets a session corresponding to an identifier supplied in the 323 transaction. 324 325 If no session has yet been established according to information 326 provided in the transaction then the optional 'create' parameter 327 determines whether a new session will be established. 328 329 Where no session has been established and where 'create' is set to 0 330 then None is returned. In all other cases, a session object is created 331 (where appropriate) and returned. 332 """ 333 334 return self.request.SESSION 335 336 def expire_session(self): 337 338 """ 339 Expires any session established according to information provided in the 340 transaction. 341 """ 342 343 self.request.SESSION.invalidate() 344 345 # Application-specific methods. 346 347 def set_user(self, username): 348 349 """ 350 An application-specific method which sets the user information with 351 'username' in the transaction. This affects subsequent calls to 352 'get_user'. 353 """ 354 355 self.user = username 356 357 # vim: tabstop=4 expandtab shiftwidth=4