package org.opentripplanner.standalone; import java.io.IOException; import java.io.OutputStream; import com.sun.jersey.spi.container.ContainerRequest; import com.sun.jersey.spi.container.ContainerResponse; import com.sun.jersey.spi.container.ContainerResponseFilter; import com.sun.jersey.spi.container.ContainerResponseWriter; /** * The Same Origin Policy states that JavaScript code (or other scripts) running on a web page may * not interact with resources originating from sites with a different hostname, protocol, or port * number. * * JSONP ("JSON with padding") is a way to get around this security policy, in which the requester * provides the name of a callback function as a query parameter, and the server returns the * response object wrapped in a call to the specified callback function. The request is made by * dynamically inserting a script tag into the client-side web page, since script tags are not held * to the same origin policy. * * The OTP Leaflet client uses JSONP for its requests, and this filter allows our server to work * with this convention. * * Despite being very common, this is of course a big hack to defeat a security policy. Modern * browsers have "Cross Origin Resource Sharing", so we should probably switch to that some day: * http://enable-cors.org/ * * This filter is also rather hackish. If it finds a "callback" query parameter in the request * object, it replaces the ResponseWriter in the response object with a wrapper that prepends the * JavaScript function name before the MessageBodyWriter ever gets ahold of the OutputStream, then * appends a closing parenthesis as the writing process is finished. * * This could probably be more properly done with a MessageBodyWriter, since JSONP requests have the * Accept header set to media types like text/javascript, while our REST endpoints "produce" * MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML. * * @author abyrd * */ public class JsonpFilter implements ContainerResponseFilter { @Override public ContainerResponse filter(ContainerRequest request, ContainerResponse response) { String callback = request.getQueryParameters().getFirst("callback"); if (callback != null) { ContainerResponseWriter writer = response.getContainerResponseWriter(); response.setContainerResponseWriter(new JsonpResponseWriter(writer, callback)); } return response; } public static class JsonpResponseWriter implements ContainerResponseWriter { private ContainerResponseWriter writer; private String callback; private OutputStream os; public JsonpResponseWriter(ContainerResponseWriter writer, String callback) { this.writer = writer; this.callback = callback; } @Override public OutputStream writeStatusAndHeaders(long contentLength, ContainerResponse response) throws IOException { OutputStream os = writer.writeStatusAndHeaders(contentLength, response); os.write(callback.getBytes()); os.write('('); this.os = os; return os; } @Override public void finish() throws IOException { os.write(')'); writer.finish(); } } }