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