diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java index fc3172e68c2..390687d8ad9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.server.interceptor; +import static org.apache.commons.lang3.StringUtils.isBlank; + /* * #%L * HAPI FHIR - Core Library @@ -39,16 +41,19 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.UrlUtil; /** - * This interceptor detects when a request is coming from a browser, and automatically - * returns a response with syntax highlighted (coloured) HTML for the response instead - * of just returning raw XML/JSON. + * This interceptor detects when a request is coming from a browser, and automatically returns a response with syntax + * highlighted (coloured) HTML for the response instead of just returning raw XML/JSON. * * @since 1.0 */ public class ResponseHighlighterInterceptor extends InterceptorAdapter { + public static final String PARAM_RAW_TRUE = "true"; + public static final String PARAM_RAW = "_raw"; + private String format(String theResultBody, EncodingEnum theEncodingEnum) { String str = StringEscapeUtils.escapeHtml4(theResultBody); if (str == null || theEncodingEnum == null) { @@ -161,8 +166,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { } @Override - public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) - throws AuthenticationException { + public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { /* * It's not a browser... @@ -171,9 +175,9 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { if (highestRankedAcceptValues.contains(Constants.CT_HTML) == false) { return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse); } - + /* - * It's an AJAX request, so no HTML + * It's an AJAX request, so no HTML */ String requestedWith = theServletRequest.getHeader("X-Requested-With"); if (requestedWith != null) { @@ -186,9 +190,14 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { if (theRequestDetails.getRequestType() != RequestTypeEnum.GET) { return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse); } - + + String[] rawParamValues = theRequestDetails.getParameters().get(PARAM_RAW); + if (rawParamValues != null && rawParamValues.length > 0 && rawParamValues[0].equals(PARAM_RAW_TRUE)) { + return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse); + } + streamResponse(theRequestDetails, theServletResponse, theResponseObject); - + return false; } @@ -201,43 +210,67 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext()); RestfulServerUtils.configureResponseParser(theRequestDetails, p); } - - EncodingEnum encoding = p.getEncoding(); + + EncodingEnum encoding = p.getEncoding(); String encoded = p.encodeResourceToString(resource); - + theServletResponse.setContentType(Constants.CT_HTML_WITH_UTF8); + + StringBuilder rawB = new StringBuilder(); + for (String next : theRequestDetails.getParameters().keySet()) { + if (next.equals(PARAM_RAW)) { + continue; + } + for (String nextValue : theRequestDetails.getParameters().get(next)) { + if (isBlank(nextValue)) { + continue; + } + if (rawB.length() == 0) { + rawB.append('?'); + }else { + rawB.append('&'); + } + rawB.append(UrlUtil.escape(next)); + rawB.append('='); + rawB.append(UrlUtil.escape(nextValue)); + } + } + if (rawB.length() == 0) { + rawB.append('?'); + }else { + rawB.append('&'); + } + rawB.append(PARAM_RAW).append('=').append(PARAM_RAW_TRUE); - //@formatter:on - String out = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n" + - " " + - "
" + format(encoded, encoding) + "
" + - " " + - ""; //@formatter:off - + String out = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + " " + + "This result is being rendered in HTML for easy viewing. Click here to disable this.

" + + "
" + 
+		format(encoded, encoding) + 
+		"
" + + " " + + ""; + //@formatter:on + try { theServletResponse.getWriter().append(out); theServletResponse.getWriter().close(); @@ -255,9 +288,9 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { if (!accept.contains(Constants.CT_HTML)) { return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); } - + /* - * It's an AJAX request, so no HTML + * It's an AJAX request, so no HTML */ String requestedWith = theServletRequest.getHeader("X-Requested-With"); if (requestedWith != null) { @@ -270,15 +303,14 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { if (theRequestDetails.getRequestType() != RequestTypeEnum.GET) { return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); } - + if (theException.getOperationOutcome() == null) { return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); } - + streamResponse(theRequestDetails, theServletResponse, theException.getOperationOutcome()); - + return false; } - } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java index 59396688282..e7c144394e8 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java @@ -3,7 +3,10 @@ package ca.uhn.fhir.rest.server.interceptor; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -28,7 +31,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -171,6 +173,40 @@ public class ResponseHighlightingInterceptorTest { assertThat(output, stringContainsInOrder("", "
", "\n", "
")); } + @Test + public void testHighlightForceRaw() throws Exception { + ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer>() { + @Override + public Enumeration answer(InvocationOnMock theInvocation) throws Throwable { + return new ArrayEnumeration("text/html,application/xhtml+xml,application/xml;q=0.9"); + } + }); + + HttpServletResponse resp = mock(HttpServletResponse.class); + StringWriter sw = new StringWriter(); + when(resp.getWriter()).thenReturn(new PrintWriter(sw)); + + Patient resource = new Patient(); + resource.addName().addFamily("FAMILY"); + + RequestDetails reqDetails = new RequestDetails(); + reqDetails.setRequestType(RequestTypeEnum.GET); + HashMap params = new HashMap(); + params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE }); + params.put(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML }); + params.put(ResponseHighlighterInterceptor.PARAM_RAW, new String[] { ResponseHighlighterInterceptor.PARAM_RAW_TRUE }); + reqDetails.setParameters(params); + reqDetails.setServer(new RestfulServer()); + reqDetails.setServletRequest(req); + + // true means it decided to not handle the request.. + assertTrue(ic.outgoingResponse(reqDetails, resource, req, resp)); + + } + @Test public void testHighlightNormalResponse() throws Exception { ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); @@ -202,6 +238,7 @@ public class ResponseHighlightingInterceptorTest { ourLog.info(output); assertThat(output, containsString("Patient")); assertThat(output, not(stringContainsInOrder("", "
", "\n", "
"))); + assertThat(output, containsString("")); } /** diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 52fef3e3e8d..f8e29867d48 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -172,6 +172,12 @@ JPA server can now store Conformance resources, per a request from David Hay + + ResponseHighlightingInterceptor now skips handling responses if it + finds a URL parameter of _raw=true]]> (in other + words, if this parameter is found, the response won't be returned as + HTML even if the request is detected as coming from a browser. +