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