diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java index 6ce23f8d008276b0067310403f678a73f65be5d0..f2ef059a9519e5ecce642cde4423ffc53fbc6490 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,17 @@ package org.springframework.test.web.servlet.result; +import java.io.UnsupportedEncodingException; + import com.jayway.jsonpath.JsonPath; import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.core.StringStartsWith; import org.springframework.test.util.JsonPathExpectationsHelper; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.util.StringUtils; /** * Factory for assertions on the response content using @@ -33,12 +38,14 @@ import org.springframework.test.web.servlet.ResultMatcher; * @author Rossen Stoyanchev * @author Craig Andrews * @author Sam Brannen + * @author Brian Clozel * @since 3.2 */ public class JsonPathResultMatchers { private final JsonPathExpectationsHelper jsonPathHelper; + private String prefix; /** * Protected constructor. @@ -48,10 +55,23 @@ public class JsonPathResultMatchers { * @param args arguments to parameterize the {@code JsonPath} expression with, * using formatting specifiers defined in {@link String#format(String, Object...)} */ - protected JsonPathResultMatchers(String expression, Object ... args) { + protected JsonPathResultMatchers(String expression, Object... args) { this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args); } + /** + * Configures the current {@code JsonPathResultMatchers} instance + * to verify that the JSON payload is prepended with the given prefix. + *

Use this method if the JSON payloads are prefixed to avoid + * Cross Site Script Inclusion (XSSI) attacks. + * @param prefix the string prefix prepended to the actual JSON payload + * @since 4.3.0 + */ + public JsonPathResultMatchers prefix(String prefix) { + this.prefix = prefix; + return this; + } + /** * Evaluate the JSON path expression against the response content and @@ -61,7 +81,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValue(content, matcher); } }; @@ -75,7 +95,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - jsonPathHelper.assertValue(result.getResponse().getContentAsString(), expectedValue); + jsonPathHelper.assertValue(getContent(result), expectedValue); } }; } @@ -91,7 +111,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.exists(content); } }; @@ -108,7 +128,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.doesNotExist(content); } }; @@ -128,7 +148,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsEmpty(content); } }; @@ -148,7 +168,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsNotEmpty(content); } }; @@ -163,7 +183,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsString(content); } }; @@ -178,7 +198,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsBoolean(content); } }; @@ -193,7 +213,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsNumber(content); } }; @@ -207,7 +227,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsArray(content); } }; @@ -222,10 +242,29 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsMap(content); } }; } + private String getContent(MvcResult result) throws UnsupportedEncodingException { + String content = result.getResponse().getContentAsString(); + if (StringUtils.hasLength(this.prefix)) { + try { + String reason = String.format("Expected a JSON payload prefixed with \"%s\" but found: %s", this.prefix, + StringUtils.quote(content.substring(0, this.prefix.length()))); + MatcherAssert.assertThat(reason, content, StringStartsWith.startsWith(this.prefix)); + return content.substring(this.prefix.length()); + } + catch (StringIndexOutOfBoundsException oobe) { + String message = "JSON prefix \"" + this.prefix + "\" not found, exception: "; + throw new AssertionError(message + oobe.getMessage()); + } + } + else { + return content; + } + } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java index 32ad49fd6181d5c8ee0286df3b5a0569cbb9878e..a12baf949f53841d8f0d18d13cb60d2d76c2242b 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java index bf0b58850ab222a371be85b0d961a4965b62bbdc..b587a34e5c2e1f0d0ad98cfa97aaa7211386e161 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.StubMvcResult; * @author Rossen Stoyanchev * @author Craig Andrews * @author Sam Brannen + * @author Brian Clozel */ public class JsonPathResultMatchersTests { @@ -41,7 +42,7 @@ public class JsonPathResultMatchersTests { "'emptyString': '', " + // "'emptyArray': [], " + // "'emptyMap': {} " + // - "}"; + "}"; private static final StubMvcResult stubMvcResult; @@ -57,7 +58,6 @@ public class JsonPathResultMatchersTests { } } - @Test public void value() throws Exception { new JsonPathResultMatchers("$.str").value("foo").match(stubMvcResult); @@ -233,4 +233,42 @@ public class JsonPathResultMatchersTests { new JsonPathResultMatchers("$.arr").isString().match(stubMvcResult); } + @Test(expected = AssertionError.class) + public void valueWithJsonPrefixNotConfigured() throws Exception { + String jsonPrefix = "prefix"; + StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); + new JsonPathResultMatchers("$.str").value("foo").match(result); + } + + @Test(expected = AssertionError.class) + public void valueWithJsonWrongPrefix() throws Exception { + String jsonPrefix = "prefix"; + StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); + new JsonPathResultMatchers("$.str").prefix("wrong").value("foo").match(result); + } + + @Test + public void valueWithJsonPrefix() throws Exception { + String jsonPrefix = "prefix"; + StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); + new JsonPathResultMatchers("$.str").prefix(jsonPrefix).value("foo").match(result); + } + + @Test(expected = AssertionError.class) + public void prefixWithPayloadNotLongEnough() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json"); + response.getWriter().print(new String("test".getBytes("ISO-8859-1"))); + StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response); + + new JsonPathResultMatchers("$.str").prefix("prefix").value("foo").match(result); + } + + private StubMvcResult createPrefixedStubMvcResult(String jsonPrefix) throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json"); + response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes("ISO-8859-1"))); + return new StubMvcResult(null, null, null, null, null, null, response); + } + }