1 #!/usr/bin/env python 2 3 """ 4 Java Servlet classes. 5 """ 6 7 import Generic 8 from StringIO import StringIO 9 from Helpers.Request import Cookie, get_body_fields, get_storage_items 10 import javax.servlet.http.Cookie 11 12 # Form data decoding. 13 14 import javax.mail.internet.MimeMessage 15 import javax.mail.Session 16 import java.util.Properties 17 18 class Stream: 19 20 """ 21 Wrapper around java.io.BufferedReader. 22 """ 23 24 def __init__(self, stream): 25 26 "Initialise the stream with the given underlying 'stream'." 27 28 self.stream = stream 29 30 def read(self): 31 32 "Read the entire message, returning it as a string." 33 34 characters = StringIO() 35 while 1: 36 c = self.stream.read() 37 if c == -1: 38 return characters.getvalue() 39 else: 40 characters.write(chr(c)) 41 42 def readline(self): 43 44 "Read a line from the stream, returning it as a string." 45 46 return self.stream.readLine() + "\n" 47 48 class Transaction(Generic.Transaction): 49 50 """ 51 Java Servlet transaction interface. 52 """ 53 54 def __init__(self, request, response): 55 56 """ 57 Initialise the transaction using the Java Servlet HTTP 'request' and 58 'response'. 59 """ 60 61 self.request = request 62 self.response = response 63 self.status = None 64 self.user = None 65 66 # Remember the cookies received in the request. 67 # NOTE: Discarding much of the information received. 68 69 self.cookies_in = {} 70 for cookie in self.request.getCookies() or []: 71 cookie_name = cookie.getName() 72 self.cookies_in[cookie_name] = Cookie(cookie_name, cookie.getValue()) 73 74 # Cached information. 75 76 self.storage_body = None 77 78 def commit(self): 79 80 """ 81 A special method, synchronising the transaction with framework-specific 82 objects. 83 """ 84 85 self.get_response_stream().close() 86 87 # Request-related methods. 88 89 def get_request_stream(self): 90 91 """ 92 Returns the request stream for the transaction. 93 """ 94 95 return Stream(self.request.getReader()) 96 97 def get_request_method(self): 98 99 """ 100 Returns the request method. 101 """ 102 103 return self.request.getMethod() 104 105 def get_headers(self): 106 107 """ 108 Returns all request headers as a dictionary-like object mapping header 109 names to values. 110 111 NOTE: If duplicate header names are permitted, then this interface will 112 NOTE: need to change. 113 """ 114 115 headers = {} 116 header_names_enum = self.request.getHeaderNames() 117 while header_names_enum.hasMoreElements(): 118 119 # NOTE: Retrieve only a single value (not using getHeaders). 120 121 header_name = header_names_enum.nextElement() 122 headers[header_name] = self.request.getHeader(header_name) 123 124 return headers 125 126 def get_header_values(self, key): 127 128 """ 129 Returns a list of all request header values associated with the given 130 'key'. Note that according to RFC 2616, 'key' is treated as a 131 case-insensitive string. 132 """ 133 134 values = [] 135 headers_enum = self.request.getHeaders(key) 136 while headers_enum.hasMoreElements(): 137 values.append(headers_enum.nextElement()) 138 return values 139 140 def get_content_type(self): 141 142 """ 143 Returns the content type specified on the request, along with the 144 charset employed. 145 """ 146 147 content_types = self.get_header_values("Content-Type") or [] 148 if len(content_types) >= 1: 149 return self.parse_content_type(content_types[0]) 150 else: 151 return None 152 153 def get_content_charsets(self): 154 155 """ 156 Returns the character set preferences. 157 """ 158 159 accept_charsets = self.get_header_values("Accept-Charset") or [] 160 if len(accept_charsets) >= 1: 161 return self.parse_content_preferences(accept_charsets[0]) 162 else: 163 return None 164 165 def get_content_languages(self): 166 167 """ 168 Returns extracted language information from the transaction. 169 """ 170 171 accept_languages = self.get_header_values("Accept-Language") or [] 172 if len(accept_languages) >= 1: 173 return self.parse_content_preferences(accept_languages[0]) 174 else: 175 return None 176 177 def get_path(self): 178 179 """ 180 Returns the entire path from the request. 181 """ 182 183 # NOTE: To be verified. 184 185 path = self.get_path_without_query() 186 qs = self.get_query_string() 187 if qs: 188 path += "?" 189 path += qs 190 return path 191 192 def get_path_without_query(self): 193 194 """ 195 Returns the entire path from the request minus the query string. 196 """ 197 198 return self.request.getContextPath() + self.request.getServletPath() + self.get_path_info() 199 200 def get_path_info(self): 201 202 """ 203 Returns the "path info" (the part of the URL after the resource name 204 handling the current request) from the request. 205 """ 206 207 return self.request.getPathInfo() or "" 208 209 def get_query_string(self): 210 211 """ 212 Returns the query string from the path in the request. 213 """ 214 215 return self.request.getQueryString() 216 217 # Higher level request-related methods. 218 219 def get_fields_from_path(self): 220 221 """ 222 Extracts the form fields from the path specified in the transaction. The 223 underlying framework may refuse to supply fields from the path if 224 handling a POST transaction. 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). 228 229 NOTE: There may not be a reliable means of extracting only the fields 230 NOTE: from the path. 231 """ 232 233 return self.get_fields_from_body() 234 235 def get_fields_from_body(self, encoding=None): 236 237 """ 238 Extracts the form fields from the message body in the transaction. The 239 optional 'encoding' parameter specifies the character encoding of the 240 message body for cases where no such information is available, but where 241 the default encoding is to be overridden. 242 243 Returns a dictionary mapping field names to lists of values (even if a 244 single value is associated with any given field name). Each value is 245 either a Unicode object (representing a simple form field, for example) 246 or a plain string (representing a file upload form field, for example). 247 248 NOTE: There may not be a reliable means of extracting only the fields 249 NOTE: from the message body. Moreover, the encoding of the fields may 250 NOTE: not be pertinent. 251 """ 252 253 # Where the content type is "multipart/form-data", we use javax.mail 254 # functionality. Otherwise, we use the Servlet API's parameter access 255 # methods. 256 257 if self.get_content_type() and self.get_content_type().content_type == "multipart/form-data": 258 fields = self._get_fields_from_message() 259 260 else: 261 fields = {} 262 parameter_map = self.request.getParameterMap() 263 if parameter_map: 264 for key in parameter_map.keySet(): 265 fields[key] = parameter_map[key] 266 267 return fields 268 269 def get_user(self): 270 271 """ 272 Extracts user information from the transaction. 273 274 Returns a username as a string or None if no user is defined. 275 """ 276 277 if self.user is not None: 278 return self.user 279 else: 280 return self.request.getRemoteUser() 281 282 def get_cookies(self): 283 284 """ 285 Obtains cookie information from the request. 286 287 Returns a dictionary mapping cookie names to cookie objects. 288 """ 289 290 return self.cookies_in 291 292 def get_cookie(self, cookie_name): 293 294 """ 295 Obtains cookie information from the request. 296 297 Returns a cookie object for the given 'cookie_name' or None if no such 298 cookie exists. 299 """ 300 301 return self.cookies_in.get(cookie_name) 302 303 # Response-related methods. 304 305 def get_response_stream(self): 306 307 """ 308 Returns the response stream for the transaction. 309 """ 310 311 return self.response.getOutputStream() 312 313 def get_response_code(self): 314 315 """ 316 Get the response code associated with the transaction. If no response 317 code is defined, None is returned. 318 """ 319 320 return self.status 321 322 def set_response_code(self, response_code): 323 324 """ 325 Set the 'response_code' using a numeric constant defined in the HTTP 326 specification. 327 """ 328 329 self.status = response_code 330 self.response.setStatus(self.status) 331 332 def set_header_value(self, header, value): 333 334 """ 335 Set the HTTP 'header' with the given 'value'. 336 """ 337 338 self.response.setHeader(self.format_header_value(header), self.format_header_value(value)) 339 340 def set_content_type(self, content_type): 341 342 """ 343 Sets the 'content_type' for the response. 344 """ 345 346 return self.response.setHeader("Content-Type", self.format_content_type(content_type)) 347 348 # Higher level response-related methods. 349 350 def set_cookie(self, cookie): 351 352 """ 353 Stores the given 'cookie' object in the response. 354 """ 355 356 new_cookie = javax.servlet.http.Cookie(cookie.name, cookie.value) 357 self.response.addCookie(new_cookie) 358 359 def set_cookie_value(self, name, value, path=None, expires=None): 360 361 """ 362 Stores a cookie with the given 'name' and 'value' in the response. 363 364 The optional 'path' is a string which specifies the scope of the cookie, 365 and the optional 'expires' parameter is a value compatible with the 366 time.time function, and indicates the expiry date/time of the cookie. 367 """ 368 369 cookie = javax.servlet.http.Cookie(name, value) 370 if path is not None: 371 cookie.setPath(path) 372 373 # NOTE: The expires parameter seems not to be supported. 374 375 self.response.addCookie(cookie) 376 377 def delete_cookie(self, cookie_name): 378 379 """ 380 Adds to the response a request that the cookie with the given 381 'cookie_name' be deleted/discarded by the client. 382 """ 383 384 # Create a special cookie, given that we do not know whether the browser 385 # has been sent the cookie or not. 386 # NOTE: Magic discovered in Webware. 387 388 cookie = javax.servlet.http.Cookie(cookie_name, "") 389 cookie.setPath("/") 390 cookie.setMaxAge(0) 391 self.response.addCookie(cookie) 392 393 # Session-related methods. 394 395 def get_session(self, create=1): 396 397 """ 398 Gets a session corresponding to an identifier supplied in the 399 transaction. 400 401 If no session has yet been established according to information 402 provided in the transaction then the optional 'create' parameter 403 determines whether a new session will be established. 404 405 Where no session has been established and where 'create' is set to 0 406 then None is returned. In all other cases, a session object is created 407 (where appropriate) and returned. 408 """ 409 410 return self.request.getSession(create) 411 412 def expire_session(self): 413 414 """ 415 Expires any session established according to information provided in the 416 transaction. 417 """ 418 419 session = self.get_session(create=0) 420 if session: 421 session.invalidate() 422 423 # Application-specific methods. 424 425 def set_user(self, username): 426 427 """ 428 An application-specific method which sets the user information with 429 'username' in the transaction. This affects subsequent calls to 430 'get_user'. 431 """ 432 433 self.user = username 434 435 # Special Java-specific methods. 436 437 def _get_fields_from_message(self): 438 439 "Get fields from a multipart message." 440 441 session = javax.mail.Session.getDefaultInstance(java.util.Properties()) 442 443 # Fake a multipart message. 444 445 str_buffer = java.io.StringWriter() 446 fp = self.get_request_stream() 447 boundary = fp.readline() 448 str_buffer.write('Content-Type: multipart/mixed; boundary="%s"\n\n' % boundary[2:-1]) 449 str_buffer.write(boundary) 450 str_buffer.write(fp.read()) 451 str_buffer.close() 452 453 # Re-read that message. 454 455 input_stream = java.io.StringBufferInputStream(str_buffer.toString()) 456 message = javax.mail.internet.MimeMessage(session, input_stream) 457 content = message.getContent() 458 return self._get_fields_from_multipart(content) 459 460 def _get_fields_from_multipart(self, content): 461 462 "Get fields from multipart 'content'." 463 464 fields = {} 465 for i in range(0, content.getCount()): 466 part = content.getBodyPart(i) 467 subcontent = part.getContent() 468 469 # Convert input stream content. 470 471 if isinstance(subcontent, java.io.InputStream): 472 subcontent = Stream(subcontent).read() 473 474 # Record string content. 475 476 if type(subcontent) == type(""): 477 478 # Should get: form-data; name="x" 479 480 disposition = part.getHeader("Content-Disposition")[0].split(";") 481 482 # Locate: name="x" 483 484 if len(disposition) > 1: 485 for attribute in disposition[1:]: 486 t = attribute.split("=") 487 if len(t) == 2 and t[0].strip() == "name": 488 489 # Locate: "x" -> Locate: x 490 491 name = t[1].strip()[1:-1] 492 if not fields.has_key(name): 493 fields[name] = [] 494 fields[name].append(subcontent) 495 else: 496 fields.update(self._get_fields_from_multipart(subcontent)) 497 498 return fields 499 500 class Session: 501 502 """ 503 A simple session class with behaviour more similar to the Python framework 504 session classes. 505 """ 506 507 def __init__(self, session): 508 509 "Initialise the session object with the framework 'session' object." 510 511 self.session = session 512 513 def keys(self): 514 keys = [] 515 keys_enum = self.session.getAttributeNames() 516 while keys_enum.hasMoreElements(): 517 keys.append(keys_enum.nextElement()) 518 return keys 519 520 def values(self): 521 values = [] 522 for key in self.keys(): 523 values.append(self[key]) 524 return values 525 526 def items(self): 527 items = [] 528 for key in self.keys(): 529 items.append((key, self[key])) 530 return items 531 532 def __getitem__(self, key): 533 return self.session.getAttribute(key) 534 535 def __setitem__(self, key, value): 536 self.session.setAttribute(key, value) 537 538 def __delitem__(self, key): 539 self.session.removeAttribute(key) 540 541 # vim: tabstop=4 expandtab shiftwidth=4