Add line selection and header display to ResponseHighlighterInterceptor

This commit is contained in:
James Agnew 2017-07-12 09:04:05 -04:00
parent 41a64a6999
commit 38b7212e52
7 changed files with 423 additions and 143 deletions

View File

@ -44,6 +44,7 @@ public class BanUnsupportedHttpMethodsInterceptor extends InterceptorAdapter {
myAllowedMethods.add(RequestTypeEnum.DELETE); myAllowedMethods.add(RequestTypeEnum.DELETE);
myAllowedMethods.add(RequestTypeEnum.PUT); myAllowedMethods.add(RequestTypeEnum.PUT);
myAllowedMethods.add(RequestTypeEnum.POST); myAllowedMethods.add(RequestTypeEnum.POST);
myAllowedMethods.add(RequestTypeEnum.PATCH);
myAllowedMethods.add(RequestTypeEnum.HEAD); myAllowedMethods.add(RequestTypeEnum.HEAD);
} }

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server.interceptor; package ca.uhn.fhir.rest.server.interceptor;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -25,9 +26,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date; import java.util.*;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@ -41,13 +40,9 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.*;
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; import ca.uhn.fhir.util.UrlUtil;
/** /**
@ -67,23 +62,64 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
* requesting _format=json or xml so eventually this parameter should be removed * requesting _format=json or xml so eventually this parameter should be removed
*/ */
public static final String PARAM_RAW = "_raw"; public static final String PARAM_RAW = "_raw";
public static final String PARAM_RAW_TRUE = "true"; public static final String PARAM_RAW_TRUE = "true";
public static final String PARAM_TRUE = "true"; public static final String PARAM_TRUE = "true";
private String format(String theResultBody, EncodingEnum theEncodingEnum) { private boolean myShowRequestHeaders;
String str = StringEscapeUtils.escapeHtml4(theResultBody); private boolean myShowResponseHeaders;
if (str == null || theEncodingEnum == null) {
return str; /**
* Constructor
*/
public ResponseHighlighterInterceptor() {
super();
} }
StringBuilder b = new StringBuilder(); private String createLinkHref(Map<String, String[]> parameters, String formatValue) {
StringBuilder rawB = new StringBuilder();
for (String next : parameters.keySet()) {
if (Constants.PARAM_FORMAT.equals(next)) {
continue;
}
for (String nextValue : parameters.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(Constants.PARAM_FORMAT).append('=').append(formatValue);
if (theEncodingEnum == EncodingEnum.JSON) { String link = rawB.toString();
return link;
}
private int format(String theResultBody, StringBuilder theTarget, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
theTarget.append(str);
return 0;
}
theTarget.append("<div id=\"line1\">");
boolean inValue = false; boolean inValue = false;
boolean inQuote = false; boolean inQuote = false;
boolean inTag = false;
int lineCount = 1;
for (int i = 0; i < str.length(); i++) { for (int i = 0; i < str.length(); i++) {
char prevChar = (i > 0) ? str.charAt(i - 1) : ' '; char prevChar = (i > 0) ? str.charAt(i - 1) : ' ';
char nextChar = str.charAt(i); char nextChar = str.charAt(i);
@ -92,95 +128,99 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' '; char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' '; char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' '; char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (nextChar == '\n') {
lineCount++;
theTarget.append("</div><div id=\"line");
theTarget.append(lineCount);
theTarget.append("\" onclick=\"window.location.hash='L");
theTarget.append(lineCount);
theTarget.append("';\">");
continue;
}
if (theEncodingEnum == EncodingEnum.JSON) {
if (inQuote) { if (inQuote) {
b.append(nextChar); theTarget.append(nextChar);
if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;</span>"); theTarget.append("quot;</span>");
i += 5; i += 5;
inQuote = false; inQuote = false;
} else if (nextChar == '\\' && nextChar2 == '"') { } else if (nextChar == '\\' && nextChar2 == '"') {
b.append("quot;</span>"); theTarget.append("quot;</span>");
i += 5; i += 5;
inQuote = false; inQuote = false;
} }
} else { } else {
if (nextChar == ':') { if (nextChar == ':') {
inValue = true; inValue = true;
b.append(nextChar); theTarget.append(nextChar);
} else if (nextChar == '[' || nextChar == '{') { } else if (nextChar == '[' || nextChar == '{') {
b.append("<span class='hlControl'>"); theTarget.append("<span class='hlControl'>");
b.append(nextChar); theTarget.append(nextChar);
b.append("</span>"); theTarget.append("</span>");
inValue = false; inValue = false;
} else if (nextChar == '{' || nextChar == '}' || nextChar == ',') { } else if (nextChar == '{' || nextChar == '}' || nextChar == ',') {
b.append("<span class='hlControl'>"); theTarget.append("<span class='hlControl'>");
b.append(nextChar); theTarget.append(nextChar);
b.append("</span>"); theTarget.append("</span>");
inValue = false; inValue = false;
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { } else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
if (inValue) { if (inValue) {
b.append("<span class='hlQuot'>&quot;"); theTarget.append("<span class='hlQuot'>&quot;");
} else { } else {
b.append("<span class='hlTagName'>&quot;"); theTarget.append("<span class='hlTagName'>&quot;");
} }
inQuote = true; inQuote = true;
i += 5; i += 5;
} else if (nextChar == ':') { } else if (nextChar == ':') {
b.append("<span class='hlControl'>"); theTarget.append("<span class='hlControl'>");
b.append(nextChar); theTarget.append(nextChar);
b.append("</span>"); theTarget.append("</span>");
inValue = true; inValue = true;
} else { } else {
b.append(nextChar); theTarget.append(nextChar);
}
} }
} }
} else { } else {
boolean inQuote = false;
boolean inTag = false;
for (int i = 0; i < str.length(); i++) {
char nextChar = str.charAt(i);
char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' ';
char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' ';
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (inQuote) { if (inQuote) {
b.append(nextChar); theTarget.append(nextChar);
if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;</span>"); theTarget.append("quot;</span>");
i += 5; i += 5;
inQuote = false; inQuote = false;
} }
} else if (inTag) { } else if (inTag) {
if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') { if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') {
b.append("</span><span class='hlControl'>&gt;</span>"); theTarget.append("</span><span class='hlControl'>&gt;</span>");
inTag = false; inTag = false;
i += 3; i += 3;
} else if (nextChar == ' ') { } else if (nextChar == ' ') {
b.append("</span><span class='hlAttr'>"); theTarget.append("</span><span class='hlAttr'>");
b.append(nextChar); theTarget.append(nextChar);
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { } else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("<span class='hlQuot'>&quot;"); theTarget.append("<span class='hlQuot'>&quot;");
inQuote = true; inQuote = true;
i += 5; i += 5;
} else { } else {
b.append(nextChar); theTarget.append(nextChar);
} }
} else { } else {
if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') { if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') {
b.append("<span class='hlControl'>&lt;</span><span class='hlTagName'>"); theTarget.append("<span class='hlControl'>&lt;</span><span class='hlTagName'>");
inTag = true; inTag = true;
i += 3; i += 3;
} else { } else {
b.append(nextChar); theTarget.append(nextChar);
} }
} }
} }
} }
return b.toString(); theTarget.append("</div>");
return lineCount;
} }
@Override @Override
@ -218,6 +258,22 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
return false; return false;
} }
/**
* If set to <code>true</code> (default is <code>false</code>) response will include the
* request headers
*/
public boolean isShowRequestHeaders() {
return myShowRequestHeaders;
}
/**
* If set to <code>true</code> (default is <code>false</code>) response will include the
* response headers
*/
public boolean isShowResponseHeaders() {
return myShowResponseHeaders;
}
@Override @Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException { throws AuthenticationException {
@ -288,7 +344,56 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
return false; return false;
} }
/**
* If set to <code>true</code> (default is <code>false</code>) response will include the
* request headers
*
* @return Returns a reference to this for easy method chaining
*/
public ResponseHighlighterInterceptor setShowRequestHeaders(boolean theShowRequestHeaders) {
myShowRequestHeaders = theShowRequestHeaders;
return this;
}
/**
* If set to <code>true</code> (default is <code>false</code>) response will include the
* response headers
*
* @return Returns a reference to this for easy method chaining
*/
public ResponseHighlighterInterceptor setShowResponseHeaders(boolean theShowResponseHeaders) {
myShowResponseHeaders = theShowResponseHeaders;
return this;
}
private void streamRequestHeaders(ServletRequest theServletRequest, StringBuilder b) {
if (theServletRequest instanceof HttpServletRequest) {
HttpServletRequest sr = (HttpServletRequest) theServletRequest;
b.append("<h1>Request</h1>");
b.append("<div class=\"headersDiv\">");
Enumeration<String> headerNamesEnum = sr.getHeaderNames();
while (headerNamesEnum.hasMoreElements()) {
String nextHeaderName = headerNamesEnum.nextElement();
Enumeration<String> headerValuesEnum = sr.getHeaders(nextHeaderName);
while (headerValuesEnum.hasMoreElements()) {
String nextHeaderValue = headerValuesEnum.nextElement();
b.append("<div class=\"headersRow\">");
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
}
}
b.append("</div>");
}
}
private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource, ServletRequest theServletRequest, int theStatusCode) { private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource, ServletRequest theServletRequest, int theStatusCode) {
if (theRequestDetails.getServer() instanceof RestfulServer) {
RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
rs.addHeadersToResponse(theServletResponse);
}
IParser p; IParser p;
Map<String, String[]> parameters = theRequestDetails.getParameters(); Map<String, String[]> parameters = theRequestDetails.getParameters();
if (parameters.containsKey(Constants.PARAM_FORMAT)) { if (parameters.containsKey(Constants.PARAM_FORMAT)) {
@ -327,6 +432,10 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append(" <head>\n"); b.append(" <head>\n");
b.append(" <meta charset=\"utf-8\" />\n"); b.append(" <meta charset=\"utf-8\" />\n");
b.append(" <style>\n"); b.append(" <style>\n");
b.append(".httpStatusDiv {");
b.append(" font-size: 1.2em;");
b.append(" font-weight: bold;");
b.append("}");
b.append(".hlQuot { color: #88F; }\n"); b.append(".hlQuot { color: #88F; }\n");
b.append(".hlQuot a { text-decoration: none; color: #88F; }\n"); b.append(".hlQuot a { text-decoration: none; color: #88F; }\n");
b.append(".hlQuot .uuid, .hlQuot .dateTime {\n"); b.append(".hlQuot .uuid, .hlQuot .dateTime {\n");
@ -350,7 +459,12 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append(".hlUrlBase {\n"); b.append(".hlUrlBase {\n");
b.append("}"); b.append("}");
b.append(".headersDiv {\n"); b.append(".headersDiv {\n");
b.append(" background: #EEE;"); b.append(" padding: 10px;");
b.append(" margin-left: 10px;");
b.append(" border: 1px solid #CCC;");
b.append(" border-radius: 10px;");
b.append("}");
b.append(".headersRow {\n");
b.append("}"); b.append("}");
b.append(".headerName {\n"); b.append(".headerName {\n");
b.append(" color: #888;\n"); b.append(" color: #888;\n");
@ -360,6 +474,32 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append(" color: #88F;\n"); b.append(" color: #88F;\n");
b.append(" font-family: monospace;\n"); b.append(" font-family: monospace;\n");
b.append("}"); b.append("}");
b.append(".responseBodyTable {");
b.append(" width: 100%;");
b.append(" margin-left: 0px;");
b.append(" margin-top: 20px;");
b.append("}");
b.append(".responseBodyTableFirstColumn {");
b.append("}");
b.append(".responseBodyTableSecondColumn {");
b.append(" width: 100%;");
b.append("}");
b.append(".lineAnchor A {");
b.append(" text-decoration: none;");
b.append(" padding-left: 20px;");
b.append("}");
b.append(".lineAnchor {");
b.append(" display: block;");
b.append(" padding-right: 20px;");
b.append("}");
b.append(".selectedLine {");
b.append(" background-color: #EEF;");
b.append(" font-weight: bold;");
b.append("}");
b.append("H1 {");
b.append(" font-size: 1.1em;");
b.append(" color: #666;");
b.append("}");
b.append("BODY {\n"); b.append("BODY {\n");
b.append(" font-family: Arial;\n"); b.append(" font-family: Arial;\n");
b.append("}"); b.append("}");
@ -403,19 +543,63 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append("\n"); b.append("\n");
// if (isEncodeHeaders()) { // status (e.g. HTTP 200 OK)
// b.append("<h1>Request Headers</h1>"); String statusName = Constants.HTTP_STATUS_NAMES.get(theServletResponse.getStatus());
// b.append("<div class=\"headersDiv\">"); statusName = defaultString(statusName);
// for (int next : theRequestDetails.get) b.append("<div class=\"httpStatusDiv\">");
// b.append("</div>"); b.append("HTTP ");
// b.append("<h1>Response Headers</h1>"); b.append(theServletResponse.getStatus());
// b.append("<div class=\"headersDiv\">"); b.append(" ");
// b.append("</div>"); b.append(statusName);
// b.append("<h1>Response Body</h1>"); b.append("</div>");
// }
b.append("<pre>"); b.append("\n");
b.append(format(encoded, encoding)); b.append("\n");
b.append("</pre>");
if (true) {
try {
if (isShowRequestHeaders()) {
streamRequestHeaders(theServletRequest, b);
}
if (isShowResponseHeaders()) {
streamResponseHeaders(theRequestDetails, theServletResponse, b);
}
} catch (Throwable t) {
// ignore (this will hit if we're running in a servlet 2.5 environment)
}
}
StringBuilder target = new StringBuilder();
int linesCount = format(encoded, target, encoding);
b.append("<table class=\"responseBodyTable\" cellspacing=\"0\">");
b.append("<tr>");
b.append("<td class=\"responseBodyTableFirstColumn\"><pre>");
for (int i = 1; i <= linesCount; i++) {
b.append("<div class=\"lineAnchor\" id=\"anchor");
b.append(i);
b.append("\">");
;
b.append("<a href=\"#L");
b.append(i);
b.append("\" name=\"L");
b.append(i);
b.append("\">");
b.append(i);
b.append("</a></div>");
}
b.append("</pre></td>");
// Response Body
b.append("<td class=\"responseBodyTableSecondColumn\"><pre>");
b.append(target);
b.append("</pre></td>");
b.append("</tr>");
b.append("</table>");
b.append("\n"); b.append("\n");
InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js"); InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js");
@ -438,35 +622,31 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
} }
} }
private String createLinkHref(Map<String, String[]> parameters, String formatValue) { private void streamResponseHeaders(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, StringBuilder b) {
StringBuilder rawB = new StringBuilder(); if (theServletResponse.getHeaderNames().isEmpty() == false) {
for (String next : parameters.keySet()) { b.append("<h1>Response</h1>");
if (Constants.PARAM_FORMAT.equals(next)) {
continue;
}
for (String nextValue : parameters.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(Constants.PARAM_FORMAT).append('=').append(formatValue);
String link = rawB.toString(); b.append("<div class=\"headersDiv\">");
return link; for (String nextHeaderName : theServletResponse.getHeaderNames()) {
for (String nextHeaderValue : theServletResponse.getHeaders(nextHeaderName)) {
/*
* Let's pretend we're returning a FHIR content type even though we're
* actually returning an HTML one
*/
if (nextHeaderName.equalsIgnoreCase(Constants.HEADER_CONTENT_TYPE)) {
ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, theRequestDetails.getServer().getDefaultResponseEncoding());
if (responseEncoding != null && isNotBlank(responseEncoding.getResourceContentType())) {
nextHeaderValue = responseEncoding.getResourceContentType() + ";charset=utf-8";
}
}
b.append("<div class=\"headersRow\">");
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
}
}
b.append("</div>");
}
} }
} }

View File

@ -1,7 +1,42 @@
var selectedLines = new Array();
function updateHighlightedLine() {
for (var next in selectedLines) {
document.getElementById('line' + selectedLines[next]).className = '';
document.getElementById('anchor' + selectedLines[next]).className = 'lineAnchor';
}
selectedLines = new Array();
var line = -1;
if (window.location.hash && window.location.hash.match('L[0-9]+-L[0-9]+')) {
var dashIndex = window.location.hash.indexOf('-');
var start = parseInt(window.location.hash.substring(2, dashIndex));
var end = parseInt(window.location.hash.substring(dashIndex+2));
for (var i = start; i <= end; i++) {
selectedLines.push(i);
}
} else if (window.location.hash && window.location.hash.match('L[0-9]+')) {
var line = parseInt(window.location.hash.substring(2));
selectedLines.push(line);
}
for (var next in selectedLines) {
document.getElementById('line' + selectedLines[next]).className = 'selectedLine';
document.getElementById('anchor' + selectedLines[next]).className = 'lineAnchor selectedLine';
}
selectedLine = line;
}
(function() { (function() {
'use strict'; 'use strict';
updateHighlightedLine();
window.onhashchange = updateHighlightedLine;
/* bail out if user is testing a version of this script via Greasemonkey or Tampermonkey */ /* bail out if user is testing a version of this script via Greasemonkey or Tampermonkey */
if (window.HAPI_ResponseHighlighter_userscript) { if (window.HAPI_ResponseHighlighter_userscript) {
console.log("HAPI ResponseHighlighter: userscript detected - not executing embedded script"); console.log("HAPI ResponseHighlighter: userscript detected - not executing embedded script");

View File

@ -197,7 +197,11 @@ public class TestRestfulServer extends RestfulServer {
* We want to format the response using nice HTML if it's a browser, since this * We want to format the response using nice HTML if it's a browser, since this
* makes things a little easier for testers. * makes things a little easier for testers.
*/ */
registerInterceptor(new ResponseHighlighterInterceptor()); ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor();
responseHighlighterInterceptor.setShowRequestHeaders(true);
responseHighlighterInterceptor.setShowResponseHeaders(true);
registerInterceptor(responseHighlighterInterceptor);
registerInterceptor(new BanUnsupportedHttpMethodsInterceptor()); registerInterceptor(new BanUnsupportedHttpMethodsInterceptor());
/* /*

View File

@ -27,14 +27,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- The JPA project uses a newer API but we'll try to hold to this version as much as possible. See #283. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Testing --> <!-- Testing -->
<!-- <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-structures-dstu</artifactId> <version>0.8</version> <scope>test</scope> </dependency> --> <!-- <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-structures-dstu</artifactId> <version>0.8</version> <scope>test</scope> </dependency> -->
<dependency> <dependency>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server.interceptor; package ca.uhn.fhir.rest.server.interceptor;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.Matchers.stringContainsInOrder;
@ -32,8 +33,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass; import org.junit.*;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -45,28 +45,21 @@ 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.dstu2.composite.HumanNameDt; import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu2.resource.Binary; import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome.Issue; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome.Issue;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.*; import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.*;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
public class ResponseHighlightingInterceptorTest { public class ResponseHighlightingInterceptorTest {
private static ResponseHighlighterInterceptor ourInterceptor = new ResponseHighlighterInterceptor();
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2(); private static FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlightingInterceptorTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlightingInterceptorTest.class);
@ -80,6 +73,12 @@ public class ResponseHighlightingInterceptorTest {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();
} }
@Before
public void before() {
ourInterceptor.setShowRequestHeaders(new ResponseHighlighterInterceptor().isShowRequestHeaders());
ourInterceptor.setShowResponseHeaders(new ResponseHighlighterInterceptor().isShowResponseHeaders());
}
/** /**
* See #464 * See #464
*/ */
@ -148,6 +147,69 @@ public class ResponseHighlightingInterceptorTest {
} }
@Test
public void testShowNeither() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, not(containsStringIgnoringCase("Accept")));
assertThat(responseContent, not(containsStringIgnoringCase("Content-Type")));
}
@Test
public void testShowResponse() throws Exception {
ourInterceptor.setShowResponseHeaders(true);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, not(containsStringIgnoringCase("Accept")));
assertThat(responseContent, (containsStringIgnoringCase("Content-Type")));
}
@Test
public void testShowRequest() throws Exception {
ourInterceptor.setShowRequestHeaders(true);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, (containsStringIgnoringCase("Accept")));
assertThat(responseContent, not(containsStringIgnoringCase("Content-Type")));
}
@Test
public void testShowRequestAndResponse() throws Exception {
ourInterceptor.setShowRequestHeaders(true);
ourInterceptor.setShowResponseHeaders(true);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, (containsStringIgnoringCase("Accept")));
assertThat(responseContent, (containsStringIgnoringCase("Content-Type")));
}
@Test @Test
public void testGetInvalidResource() throws Exception { public void testGetInvalidResource() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123");
@ -196,7 +258,7 @@ public class ResponseHighlightingInterceptorTest {
@Test @Test
public void testHighlightException() throws Exception { public void testHighlightException() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -234,7 +296,7 @@ public class ResponseHighlightingInterceptorTest {
@Test @Test
public void testHighlightNormalResponseForcePrettyPrint() throws Exception { public void testHighlightNormalResponseForcePrettyPrint() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -269,7 +331,7 @@ public class ResponseHighlightingInterceptorTest {
@Test @Test
public void testHighlightForceRaw() throws Exception { public void testHighlightForceRaw() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -303,7 +365,7 @@ public class ResponseHighlightingInterceptorTest {
@Test @Test
public void testDontHighlightWhenOriginHeaderPresent() throws Exception { public void testDontHighlightWhenOriginHeaderPresent() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -343,7 +405,7 @@ public class ResponseHighlightingInterceptorTest {
*/ */
@Test @Test
public void testHighlightForceHtmlCt() throws Exception { public void testHighlightForceHtmlCt() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -377,7 +439,7 @@ public class ResponseHighlightingInterceptorTest {
*/ */
@Test @Test
public void testHighlightForceHtmlFormat() throws Exception { public void testHighlightForceHtmlFormat() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -408,7 +470,7 @@ public class ResponseHighlightingInterceptorTest {
@Test @Test
public void testHighlightNormalResponse() throws Exception { public void testHighlightNormalResponse() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -445,7 +507,7 @@ public class ResponseHighlightingInterceptorTest {
*/ */
@Test @Test
public void testHighlightProducesDefaultJsonWithBrowserRequest() throws Exception { public void testHighlightProducesDefaultJsonWithBrowserRequest() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
@ -483,7 +545,7 @@ public class ResponseHighlightingInterceptorTest {
@Test @Test
public void testHighlightProducesDefaultJsonWithBrowserRequest2() throws Exception { public void testHighlightProducesDefaultJsonWithBrowserRequest2() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class); HttpServletRequest req = mock(HttpServletRequest.class);
@ -718,7 +780,7 @@ public class ResponseHighlightingInterceptorTest {
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS")); config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
ourServlet.registerInterceptor(corsInterceptor); ourServlet.registerInterceptor(corsInterceptor);
ourServlet.registerInterceptor(new ResponseHighlighterInterceptor()); ourServlet.registerInterceptor(ourInterceptor);
ourServlet.setResourceProviders(patientProvider, new DummyBinaryResourceProvider()); ourServlet.setResourceProviders(patientProvider, new DummyBinaryResourceProvider());
ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE); ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(ourServlet); ServletHolder servletHolder = new ServletHolder(ourServlet);
@ -762,9 +824,6 @@ public class ResponseHighlightingInterceptorTest {
} }
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPatientResourceProvider implements IResourceProvider { public static class DummyPatientResourceProvider implements IResourceProvider {
private Patient createPatient1() { private Patient createPatient1() {

View File

@ -136,6 +136,15 @@
body are now converted to actual clickable hyperlinks. Thanks to Eugene Lubarsky body are now converted to actual clickable hyperlinks. Thanks to Eugene Lubarsky
for the pull request! for the pull request!
</action> </action>
<action type="add">
BanUnsupportedHttpMethodsInterceptor has been modified so that it now allows
HTTP PATCH to proceed.
</action>
<action type="add" issue="651">
Enhancement to ResponseHighlighterInterceptor so that it now can be configured
to display the request headers and response headers, and individual lines
may be highlighted.
</action>
</release> </release>
<release version="2.5" date="2017-06-08"> <release version="2.5" date="2017-06-08">
<action type="fix"> <action type="fix">