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 return self.request.getPathInfo() or "" 215 216 def get_query_string(self): 217 218 """ 219 Returns the query string from the path in the request. 220 """ 221 222 return self.request.getQueryString() or "" 223 224 # Higher level request-related methods. 225 226 def get_fields_from_path(self): 227 228 """ 229 Extracts fields (or request parameters) from the path specified in the 230 transaction. The underlying framework may refuse to supply fields from 231 the path if handling a POST transaction. 232 233 Returns a dictionary mapping field names to lists of values (even if a 234 single value is associated with any given field name). 235 """ 236 237 # There may not be a reliable means of extracting only the fields from 238 # the path using the API. Moreover, any access to the request parameters 239 # disrupts the proper extraction and decoding of the request parameters 240 # which originated in the request body. 241 242 return get_fields_from_query_string(self.get_query_string(), java.net.URLDecoder().decode) 243 244 def get_fields_from_body(self, encoding=None): 245 246 """ 247 Extracts fields (or request parameters) from the message body in the 248 transaction. The optional 'encoding' parameter specifies the character 249 encoding of the message body for cases where no such information is 250 available, but where the default encoding is to be overridden. 251 252 Returns a dictionary mapping field names to lists of values (even if a 253 single value is associated with any given field name). Each value is 254 either a Unicode object (representing a simple form field, for example) 255 or a plain string (representing a file upload form field, for example). 256 """ 257 258 # There may not be a reliable means of extracting only the fields 259 # the message body using the API. Remove fields originating from the 260 # path in the mixture provided by the API. 261 262 all_fields = self._get_fields(encoding) 263 fields_from_path = self.get_fields_from_path() 264 return filter_fields(all_fields, fields_from_path) 265 266 def _get_fields(self, encoding=None): 267 268 # Override the default encoding if requested. 269 270 if encoding is not None: 271 self.request.setCharacterEncoding(encoding) 272 273 # Where the content type is "multipart/form-data", we use javax.mail 274 # functionality. Otherwise, we use the Servlet API's parameter access 275 # methods. 276 277 if self.get_content_type() and self.get_content_type().media_type == "multipart/form-data": 278 if self.message_fields is not None: 279 return self.message_fields 280 else: 281 fields = self.message_fields = self._get_fields_from_message() 282 else: 283 fields = {} 284 parameter_map = self.request.getParameterMap() 285 if parameter_map: 286 for field_name in parameter_map.keySet(): 287 fields[field_name] = parameter_map[field_name] 288 289 return fields 290 291 def get_fields(self, encoding=None): 292 293 """ 294 Extracts fields (or request parameters) from both the path specified in 295 the transaction as well as the message body. The optional 'encoding' 296 parameter specifies the character encoding of the message body for cases 297 where no such information is available, but where the default encoding 298 is to be overridden. 299 300 Returns a dictionary mapping field names to lists of values (even if a 301 single value is associated with any given field name). Each value is 302 either a Unicode object (representing a simple form field, for example) 303 or a plain string (representing a file upload form field, for example). 304 305 Where a given field name is used in both the path and message body to 306 specify values, the values from both sources will be combined into a 307 single list associated with that field name. 308 """ 309 310 # NOTE: The Java Servlet API (like Zope) seems to provide only body 311 # NOTE: fields upon POST requests. 312 313 if self.get_request_method() == "GET": 314 return self._get_fields(encoding) 315 else: 316 fields = {} 317 fields.update(self.get_fields_from_path()) 318 for name, values in self._get_fields(encoding).items(): 319 if not fields.has_key(name): 320 fields[name] = values 321 else: 322 fields[name] += values 323 return fields 324 325 def get_user(self): 326 327 """ 328 Extracts user information from the transaction. 329 330 Returns a username as a string or None if no user is defined. 331 """ 332 333 if self.user is not None: 334 return self.user 335 else: 336 return self.request.getRemoteUser() 337 338 def get_cookies(self): 339 340 """ 341 Obtains cookie information from the request. 342 343 Returns a dictionary mapping cookie names to cookie objects. 344 """ 345 346 return self.cookies_in 347 348 def get_cookie(self, cookie_name): 349 350 """ 351 Obtains cookie information from the request. 352 353 Returns a cookie object for the given 'cookie_name' or None if no such 354 cookie exists. 355 """ 356 357 return self.cookies_in.get(cookie_name) 358 359 # Response-related methods. 360 361 def get_response_stream(self): 362 363 """ 364 Returns the response stream for the transaction. 365 """ 366 367 return self.response.getWriter() 368 369 def get_response_stream_encoding(self): 370 371 """ 372 Returns the response stream encoding. 373 """ 374 375 return self.response.getCharacterEncoding() 376 377 def get_response_code(self): 378 379 """ 380 Get the response code associated with the transaction. If no response 381 code is defined, None is returned. 382 """ 383 384 return self.status 385 386 def set_response_code(self, response_code): 387 388 """ 389 Set the 'response_code' using a numeric constant defined in the HTTP 390 specification. 391 """ 392 393 self.status = response_code 394 self.response.setStatus(self.status) 395 396 def set_header_value(self, header, value): 397 398 """ 399 Set the HTTP 'header' with the given 'value'. 400 """ 401 402 self.response.setHeader(self.format_header_value(header), self.format_header_value(value)) 403 404 def set_content_type(self, content_type): 405 406 """ 407 Sets the 'content_type' for the response. 408 """ 409 410 self.response.setContentType(str(content_type)) 411 412 # Higher level response-related methods. 413 414 def set_cookie(self, cookie): 415 416 """ 417 Stores the given 'cookie' object in the response. 418 """ 419 420 self.set_cookie_value(cookie.name, cookie.value) 421 422 def set_cookie_value(self, name, value, path=None, expires=None): 423 424 """ 425 Stores a cookie with the given 'name' and 'value' in the response. 426 427 The optional 'path' is a string which specifies the scope of the cookie, 428 and the optional 'expires' parameter is a value compatible with the 429 time.time function, and indicates the expiry date/time of the cookie. 430 """ 431 432 cookie = javax.servlet.http.Cookie(self.encode_cookie_value(name), 433 self.encode_cookie_value(value)) 434 if path is not None: 435 cookie.setPath(path) 436 437 # NOTE: The expires parameter seems not to be supported. 438 439 self.response.addCookie(cookie) 440 441 def delete_cookie(self, cookie_name): 442 443 """ 444 Adds to the response a request that the cookie with the given 445 'cookie_name' be deleted/discarded by the client. 446 """ 447 448 # Create a special cookie, given that we do not know whether the browser 449 # has been sent the cookie or not. 450 # NOTE: Magic discovered in Webware. 451 452 cookie = javax.servlet.http.Cookie(self.encode_cookie_value(cookie_name), "") 453 cookie.setPath("/") 454 cookie.setMaxAge(0) 455 self.response.addCookie(cookie) 456 457 # Session-related methods. 458 459 def get_session(self, create=1): 460 461 """ 462 Gets a session corresponding to an identifier supplied in the 463 transaction. 464 465 If no session has yet been established according to information 466 provided in the transaction then the optional 'create' parameter 467 determines whether a new session will be established. 468 469 Where no session has been established and where 'create' is set to 0 470 then None is returned. In all other cases, a session object is created 471 (where appropriate) and returned. 472 """ 473 474 session = self.request.getSession(create) 475 if session: 476 return Session(session) 477 else: 478 return None 479 480 def expire_session(self): 481 482 """ 483 Expires any session established according to information provided in the 484 transaction. 485 """ 486 487 session = self.request.getSession(0) 488 if session: 489 session.invalidate() 490 491 # Special Java-specific methods. 492 493 def _get_fields_from_message(self): 494 495 "Get fields from a multipart message." 496 497 session = javax.mail.Session.getDefaultInstance(java.util.Properties()) 498 499 # Fake a multipart message. 500 501 str_buffer = java.io.StringWriter() 502 fp = self.get_request_stream() 503 boundary = fp.readline() 504 str_buffer.write('Content-Type: multipart/mixed; boundary="%s"\n\n' % boundary[2:-1]) 505 str_buffer.write(boundary) 506 str_buffer.write(fp.read()) 507 str_buffer.close() 508 509 # Re-read that message. 510 511 input_stream = java.io.StringBufferInputStream(str_buffer.toString()) 512 message = javax.mail.internet.MimeMessage(session, input_stream) 513 content = message.getContent() 514 return self._get_fields_from_multipart(content) 515 516 def _get_fields_from_multipart(self, content): 517 518 "Get fields from multipart 'content'." 519 520 fields = {} 521 for i in range(0, content.getCount()): 522 part = content.getBodyPart(i) 523 subcontent = part.getContent() 524 525 # Convert input stream content. 526 527 if isinstance(subcontent, java.io.InputStream): 528 subcontent = Stream(subcontent).read() 529 530 # Record string content. 531 532 if type(subcontent) == type(""): 533 534 # Should get: form-data; name="x" 535 536 disposition = self.parse_header_value(HeaderValue, part.getHeader("Content-Disposition")[0]) 537 538 # Store and optionally convert the field. 539 540 if disposition.name is not None: 541 if not fields.has_key(disposition.name[1:-1]): 542 fields[disposition.name[1:-1]] = [] 543 fields[disposition.name[1:-1]].append(subcontent) 544 545 # Otherwise, descend deeper into the multipart hierarchy. 546 547 else: 548 fields.update(self._get_fields_from_multipart(subcontent)) 549 550 return fields 551 552 class Session: 553 554 """ 555 A simple session class with behaviour more similar to the Python framework 556 session classes. 557 """ 558 559 def __init__(self, session): 560 561 "Initialise the session object with the framework 'session' object." 562 563 self.session = session 564 565 def keys(self): 566 keys = [] 567 keys_enum = self.session.getAttributeNames() 568 while keys_enum.hasMoreElements(): 569 keys.append(keys_enum.nextElement()) 570 return keys 571 572 def values(self): 573 values = [] 574 for key in self.keys(): 575 values.append(self[key]) 576 return values 577 578 def items(self): 579 items = [] 580 for key in self.keys(): 581 items.append((key, self[key])) 582 return items 583 584 def __getitem__(self, key): 585 return self.session.getAttribute(key) 586 587 def __setitem__(self, key, value): 588 self.session.setAttribute(key, value) 589 590 def __delitem__(self, key): 591 self.session.removeAttribute(key) 592 593 # vim: tabstop=4 expandtab shiftwidth=4