Support ETags with special chars in ServletWebRequest

This commit makes sure that HTTP request headers containing ETag values
are properly parsed and not simply tokenized using a "," separator.
Indeed, ETags can legally contain separator characters such as " " and
",".

Issue: SPR-14216
This commit is contained in:
Brian Clozel 2016-04-29 11:17:47 +02:00
parent fdb31cd715
commit 29da44c8dc
2 changed files with 31 additions and 14 deletions

View File

@ -21,6 +21,8 @@ import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -64,6 +66,12 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
private static final String METHOD_DELETE = "DELETE";
/**
* Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match"
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
*/
private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
/** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */
private static final boolean servlet3Present =
@ -217,7 +225,9 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
if (StringUtils.hasLength(etag) && !this.notModified) {
if (isCompatibleWithConditionalRequests(response)) {
etag = addEtagPadding(etag);
this.notModified = isEtagNotModified(etag);
if (hasRequestHeader(HEADER_IF_NONE_MATCH)) {
this.notModified = isEtagNotModified(etag);
}
if (response != null) {
if (this.notModified && supportsNotModifiedStatus()) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
@ -343,18 +353,14 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
}
private boolean isEtagNotModified(String etag) {
if (StringUtils.hasLength(etag)) {
String ifNoneMatch = getHeader(HEADER_IF_NONE_MATCH);
if (StringUtils.hasLength(ifNoneMatch)) {
String[] clientEtags = StringUtils.delimitedListToStringArray(ifNoneMatch, ",", " ");
for (String clientEtag : clientEtags) {
// compare weak/strong ETag as per https://tools.ietf.org/html/rfc7232#section-2.3
if (StringUtils.hasLength(clientEtag) &&
(clientEtag.replaceFirst("^W/", "").equals(etag.replaceFirst("^W/", "")) ||
clientEtag.equals("*"))) {
return true;
}
}
String ifNoneMatch = getHeader(HEADER_IF_NONE_MATCH);
// compare weak/strong ETag as per https://tools.ietf.org/html/rfc7232#section-2.3
String serverETag = etag.replaceFirst("^W/", "");
Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(ifNoneMatch);
while (eTagMatcher.find()) {
if ("*".equals(eTagMatcher.group())
|| serverETag.equals(eTagMatcher.group(3))) {
return true;
}
}
return false;
@ -367,7 +373,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
return etag;
}
@Override
public String getDescription(boolean includeClientInfo) {
HttpServletRequest request = getRequest();

View File

@ -144,6 +144,18 @@ public class ServletWebRequestHttpMethodsTests {
assertEquals(eTag, servletResponse.getHeader("ETag"));
}
@Test
public void checkNotModifiedETagWithSeparatorChars() {
String eTag = "\"Foo, Bar\"";
servletRequest.addHeader("If-None-Match", eTag);
assertTrue(request.checkNotModified(eTag));
assertEquals(304, servletResponse.getStatus());
assertEquals(eTag, servletResponse.getHeader("ETag"));
}
@Test
public void checkModifiedETag() {
String currentETag = "\"Foo\"";