Add content-disposition header for binary resources

This commit is contained in:
James Agnew 2014-07-22 14:25:33 -04:00
parent fc74cda994
commit e53aaf0950
4 changed files with 66 additions and 2 deletions

View File

@ -8,7 +8,7 @@
<body> <body>
<release version="0.5" date="TBD"> <release version="0.5" date="TBD">
<action type="add"> <action type="add">
Allow server methods to return wildcard genrric types (e.g. List&lt;? extends IResource&gt;) Allow server methods to return wildcard generic types (e.g. List&lt;? extends IResource&gt;)
</action> </action>
<action type="add"> <action type="add">
Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as
@ -47,6 +47,12 @@
Fix issue where vread invocations on server incorrectly get routed to instance history method if one is Fix issue where vread invocations on server incorrectly get routed to instance history method if one is
defined. Thanks to Neal Acharya from UHN for surfacing this one! defined. Thanks to Neal Acharya from UHN for surfacing this one!
</action> </action>
<action type="add">
Binary reads on a server not include the Content-Disposition header, to prevent HTML in binary
blobs from being used for nefarious purposes. See
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=3298">FHIR Tracker Bug 3298</a>]]>
for more information.
</action>
</release> </release>
<release version="0.4" date="2014-Jul-13"> <release version="0.4" date="2014-Jul-13">
<action type="add"> <action type="add">

View File

@ -95,6 +95,7 @@ public class Constants {
public static final String HEADERVALUE_CORS_ALLOW_METHODS_ALL = "GET, POST, PUT, DELETE"; public static final String HEADERVALUE_CORS_ALLOW_METHODS_ALL = "GET, POST, PUT, DELETE";
public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String HEADER_AUTHORIZATION = "Authorization";
public static final String PARAMQUALIFIER_TOKEN_TEXT = ":text"; public static final String PARAMQUALIFIER_TOKEN_TEXT = ":text";
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
static { static {

View File

@ -1099,6 +1099,9 @@ public class RestfulServer extends HttpServlet {
if (bin.getContent() == null || bin.getContent().length == 0) { if (bin.getContent() == null || bin.getContent().length == 0) {
return; return;
} }
theHttpResponse.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
theHttpResponse.setContentLength(bin.getContent().length); theHttpResponse.setContentLength(bin.getContent().length);
ServletOutputStream oos = theHttpResponse.getOutputStream(); ServletOutputStream oos = theHttpResponse.getOutputStream();
oos.write(bin.getContent()); oos.write(bin.getContent());

View File

@ -21,6 +21,7 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Binary;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
@ -58,6 +59,36 @@ public class ReadTest {
} }
} }
@Test
public void testBinaryRead() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/1");
HttpResponse status = ourClient.execute(httpGet);
byte[] responseContent = IOUtils.toByteArray(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("application/x-foo", status.getEntity().getContentType().getValue());
Header cl = status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION_LC);
assertNotNull(cl);
assertEquals("http://localhost:" + ourPort + "/Binary/1/_history/1", cl.getValue());
Header cd = status.getFirstHeader("content-disposition");
assertNotNull(cd);
assertEquals("Attachment;", cd.getValue());
assertEquals(4,responseContent.length);
for (int i = 0; i < 4; i++) {
assertEquals(i+1, responseContent[i]); // should be 1,2,3,4
}
}
}
@Test @Test
public void testVRead() throws Exception { public void testVRead() throws Exception {
@ -93,7 +124,7 @@ public class ReadTest {
ServletHandler proxyHandler = new ServletHandler(); ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(); RestfulServer servlet = new RestfulServer();
ourCtx = servlet.getFhirContext(); ourCtx = servlet.getFhirContext();
servlet.setResourceProviders(patientProvider); servlet.setResourceProviders(patientProvider, new DummyBinaryProvider());
ServletHolder servletHolder = new ServletHolder(servlet); ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*"); proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler); ourServer.setHandler(proxyHandler);
@ -126,4 +157,27 @@ public class ReadTest {
} }
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyBinaryProvider implements IResourceProvider {
@Read(version = true)
public Binary findPatient(@IdParam IdDt theId) {
Binary bin = new Binary();
bin.setContentType("application/x-foo");
bin.setContent(new byte[] {1,2,3,4});
bin.setId("Binary/1/_history/1");
return bin;
}
@Override
public Class<? extends IResource> getResourceType() {
return Binary.class;
}
}
} }