Skip to content

Instantly share code, notes, and snippets.

@fberger
Created July 14, 2010 23:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fberger/476245 to your computer and use it in GitHub Desktop.
Save fberger/476245 to your computer and use it in GitHub Desktop.
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;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Collections;
/**
* Container response filter that wraps responses of type {@link javax.ws.rs.core.MediaType#APPLICATION_JSON_TYPE} and
* {@link #OPENSEARCH_SUGGESTIONS_JSON_UTF8_MEDIA_TYPE} into Javascript function calls if all of the following
* conditions are met:
* <ul>
* <li>the http method is GET</li>
* <li>no cookies are being sent in the request (this prevents cross site scripting attacks)</li>
* <li>no Authorization header is included in the request (this prevents cross site scripting attacks)</li>
* <li>a query parameter named callback is specified (http://url/?callback=functionName)</li>
* </ul>
*/
public class JSONPContainerResponseFilter implements ContainerResponseFilter {
private static final MediaType OPENSEARCH_SUGGESTIONS_JSON_UTF8_MEDIA_TYPE = MediaType.valueOf("application/x-suggestions+json");
private static final String APPLICATION_JAVASCRIPT_NO_CHARSET = "application/javascript";
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
if (!request.getMethod().equals("GET")) {
return response;
}
MediaType responseType = response.getMediaType();
if (responseType == null) {
return response;
}
if (!responseType.isCompatible(MediaType.APPLICATION_JSON_TYPE) && !responseType.isCompatible(OPENSEARCH_SUGGESTIONS_JSON_UTF8_MEDIA_TYPE)) {
return response;
}
if (!request.getCookies().isEmpty()) {
return response;
}
if (request.getHeaderValue(HttpHeaders.AUTHORIZATION) != null) {
return response;
}
MultivaluedMap<String, String> parameters = request.getQueryParameters();
String callback = parameters.getFirst("callback");
if (callback == null) {
return response;
}
String charset = responseType.getParameters().get("charset");
response.setContainerResponseWriter(new JSONPContainerResponseWriter(callback, response.getContainerResponseWriter(), charset));
response.getHttpHeaders().putSingle(HttpHeaders.CONTENT_TYPE,
charset != null ?
new MediaType("application", "javascript", Collections.singletonMap("charset", charset)).toString() :
APPLICATION_JAVASCRIPT_NO_CHARSET);
return response;
}
private static final class JSONPContainerResponseWriter implements ContainerResponseWriter {
private final String callback;
private final ContainerResponseWriter crw;
private final byte[] prefix;
private final byte[] postfix;
private OutputStream out;
JSONPContainerResponseWriter(String callback, ContainerResponseWriter containerResponseWriter, String encoding) {
this.callback = callback;
this.crw = containerResponseWriter;
// if no charset is specified use http default content charset iso-8859-1
Charset charset = encoding != null ? Charset.forName(encoding) : Charset.forName("ISO-8859-1");
this.prefix = (callback + "(").getBytes(charset);
this.postfix = ");".getBytes(charset);
}
public OutputStream writeStatusAndHeaders(long contentLength, ContainerResponse response) throws IOException {
out = crw.writeStatusAndHeaders(contentLength == -1 ? -1 : contentLength + prefix.length + postfix.length, response);
out.write(prefix);
return out;
}
public void finish() throws IOException {
out.write(postfix);
crw.finish();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment