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