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:
parent
7b21535a49
commit
448ff5f344
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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){
|
||||||
|
|
Loading…
Reference in New Issue