HTTPCLIENT-964: no-cache directives with field names are no longer transmitted downstream

Contributed by Jonathan Moore <jonathan_moore at comcast.com>


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@961422 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2010-07-07 16:01:48 +00:00
parent 7b21535a49
commit 448ff5f344
4 changed files with 113 additions and 1 deletions

View File

@ -1,6 +1,10 @@
Changes since 4.1 ALPHA2 Changes since 4.1 ALPHA2
------------------- -------------------
* [HTTPCLIENT-964] no-cache directives with field names are no longer transmitted
downstream.
Contributed by Jonathan Moore <jonathan_moore at comcast.com>
* [HTTPCLIENT-962] Fixed handling of Authorization headers in shared cache mode. * [HTTPCLIENT-962] Fixed handling of Authorization headers in shared cache mode.
Contributed by Jonathan Moore <jonathan_moore at comcast.com> Contributed by Jonathan Moore <jonathan_moore at comcast.com>

View File

@ -76,6 +76,11 @@ public class RequestProtocolCompliance {
theErrors.add(anError); theErrors.add(anError);
} }
anError = requestContainsNoCacheDirectiveWithFieldName(request);
if (anError != null) {
theErrors.add(anError);
}
return theErrors; return theErrors;
} }
@ -259,6 +264,11 @@ public class RequestProtocolCompliance {
HttpStatus.SC_BAD_REQUEST, HttpStatus.SC_BAD_REQUEST,
"Weak eTag not compatible with PUT or DELETE requests")); "Weak eTag not compatible with PUT or DELETE requests"));
case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME:
return new BasicHttpResponse(new BasicStatusLine(CachingHttpClient.HTTP_1_1,
HttpStatus.SC_BAD_REQUEST,
"No-Cache directive MUST NOT include a field name"));
default: default:
throw new IllegalStateException( throw new IllegalStateException(
"The request was compliant, therefore no error can be generated for it."); "The request was compliant, therefore no error can be generated for it.");
@ -329,4 +339,16 @@ public class RequestProtocolCompliance {
return RequestProtocolError.BODY_BUT_NO_LENGTH_ERROR; return RequestProtocolError.BODY_BUT_NO_LENGTH_ERROR;
} }
private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(HttpRequest request) {
for(Header h : request.getHeaders("Cache-Control")) {
for(HeaderElement elt : h.getElements()) {
if ("no-cache".equalsIgnoreCase(elt.getName())
&& elt.getValue() != null) {
return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME;
}
}
}
return null;
}
} }

View File

@ -34,6 +34,7 @@ public enum RequestProtocolError {
UNKNOWN, UNKNOWN,
BODY_BUT_NO_LENGTH_ERROR, BODY_BUT_NO_LENGTH_ERROR,
WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR, WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR,
WEAK_ETAG_AND_RANGE_ERROR WEAK_ETAG_AND_RANGE_ERROR,
NO_CACHE_DIRECTIVE_WITH_FIELD_NAME
} }

View File

@ -4778,6 +4778,91 @@ public class TestProtocolRequirements {
testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1); testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
} }
/* "If a cache returns a stale response, either because of a max-stale
* directive on a request, or because the cache is configured to
* override the expiration time of a response, the cache MUST attach a
* Warning header to the stale response, using Warning 110 (Response
* is stale).
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3
*/
@Test
public void testWarning110IsAddedToStaleResponses()
throws Exception {
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
HttpRequest req1 = new BasicHttpRequest("GET","/",HTTP_1_1);
HttpResponse resp1 = make200Response();
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5");
resp1.setHeader("Etag","\"etag\"");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = new BasicHttpRequest("GET","/",HTTP_1_1);
req2.setHeader("Cache-Control","max-stale=60");
HttpResponse resp2 = make200Response();
Capture<HttpRequest> cap = new Capture<HttpRequest>();
EasyMock.expect(mockBackend.execute(EasyMock.eq(host),
EasyMock.capture(cap),
(HttpContext)EasyMock.isNull()))
.andReturn(resp2).times(0,1);
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
if (!cap.hasCaptured()) {
boolean found110Warning = false;
for(Header h : result.getHeaders("Warning")) {
for(HeaderElement elt : h.getElements()) {
String[] parts = elt.getName().split("\\s");
if ("110".equals(parts[0])) {
found110Warning = true;
break;
}
}
}
Assert.assertTrue(found110Warning);
}
}
/* "Field names MUST NOT be included with the no-cache directive in a
* request."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4
*/
@Test
public void testDoesNotTransmitNoCacheDirectivesWithFieldsDownstream()
throws Exception {
request.setHeader("Cache-Control","no-cache=\"X-Field\"");
Capture<HttpRequest> cap = new Capture<HttpRequest>();
EasyMock.expect(mockBackend.execute(EasyMock.eq(host),
EasyMock.capture(cap),
(HttpContext)EasyMock.isNull()))
.andReturn(originResponse).times(0,1);
replayMocks();
try {
impl.execute(host, request);
} catch (ClientProtocolException acceptable) {
}
verifyMocks();
if (cap.hasCaptured()) {
HttpRequest captured = cap.getValue();
for(Header h : captured.getHeaders("Cache-Control")) {
for(HeaderElement elt : h.getElements()) {
if ("no-cache".equals(elt.getName())) {
Assert.assertNull(elt.getValue());
}
}
}
}
}
private class FakeHeaderGroup extends HeaderGroup{ private class FakeHeaderGroup extends HeaderGroup{
public void addHeader(String name, String value){ public void addHeader(String name, String value){