1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.http.impl.client.cache;
28
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32
33 import org.apache.http.Header;
34 import org.apache.http.HeaderElement;
35 import org.apache.http.HttpEntity;
36 import org.apache.http.HttpEntityEnclosingRequest;
37 import org.apache.http.HttpHeaders;
38 import org.apache.http.HttpRequest;
39 import org.apache.http.HttpResponse;
40 import org.apache.http.HttpStatus;
41 import org.apache.http.HttpVersion;
42 import org.apache.http.ProtocolVersion;
43 import org.apache.http.annotation.Contract;
44 import org.apache.http.annotation.ThreadingBehavior;
45 import org.apache.http.client.ClientProtocolException;
46 import org.apache.http.client.cache.HeaderConstants;
47 import org.apache.http.client.methods.HttpRequestWrapper;
48 import org.apache.http.entity.ContentType;
49 import org.apache.http.entity.HttpEntityWrapper;
50 import org.apache.http.message.BasicHeader;
51 import org.apache.http.message.BasicHttpResponse;
52 import org.apache.http.message.BasicStatusLine;
53 import org.apache.http.protocol.HTTP;
54
55
56
57
58 @Contract(threading = ThreadingBehavior.IMMUTABLE)
59 class RequestProtocolCompliance {
60 private final boolean weakETagOnPutDeleteAllowed;
61
62 public RequestProtocolCompliance() {
63 super();
64 this.weakETagOnPutDeleteAllowed = false;
65 }
66
67 public RequestProtocolCompliance(final boolean weakETagOnPutDeleteAllowed) {
68 super();
69 this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
70 }
71
72 private static final List<String> disallowedWithNoCache =
73 Arrays.asList(HeaderConstants.CACHE_CONTROL_MIN_FRESH, HeaderConstants.CACHE_CONTROL_MAX_STALE, HeaderConstants.CACHE_CONTROL_MAX_AGE);
74
75
76
77
78
79
80
81
82 public List<RequestProtocolError> requestIsFatallyNonCompliant(final HttpRequest request) {
83 final List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>();
84
85 RequestProtocolError anError = requestHasWeakETagAndRange(request);
86 if (anError != null) {
87 theErrors.add(anError);
88 }
89
90 if (!weakETagOnPutDeleteAllowed) {
91 anError = requestHasWeekETagForPUTOrDELETEIfMatch(request);
92 if (anError != null) {
93 theErrors.add(anError);
94 }
95 }
96
97 anError = requestContainsNoCacheDirectiveWithFieldName(request);
98 if (anError != null) {
99 theErrors.add(anError);
100 }
101
102 return theErrors;
103 }
104
105
106
107
108
109
110
111
112 public void makeRequestCompliant(final HttpRequestWrapper request)
113 throws ClientProtocolException {
114
115 if (requestMustNotHaveEntity(request)) {
116 ((HttpEntityEnclosingRequest) request).setEntity(null);
117 }
118
119 verifyRequestWithExpectContinueFlagHas100continueHeader(request);
120 verifyOPTIONSRequestWithBodyHasContentType(request);
121 decrementOPTIONSMaxForwardsIfGreaterThen0(request);
122 stripOtherFreshnessDirectivesWithNoCache(request);
123
124 if (requestVersionIsTooLow(request)
125 || requestMinorVersionIsTooHighMajorVersionsMatch(request)) {
126 request.setProtocolVersion(HttpVersion.HTTP_1_1);
127 }
128 }
129
130 private void stripOtherFreshnessDirectivesWithNoCache(final HttpRequest request) {
131 final List<HeaderElement> outElts = new ArrayList<HeaderElement>();
132 boolean shouldStrip = false;
133 for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
134 for(final HeaderElement elt : h.getElements()) {
135 if (!disallowedWithNoCache.contains(elt.getName())) {
136 outElts.add(elt);
137 }
138 if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
139 shouldStrip = true;
140 }
141 }
142 }
143 if (!shouldStrip) {
144 return;
145 }
146 request.removeHeaders(HeaderConstants.CACHE_CONTROL);
147 request.setHeader(HeaderConstants.CACHE_CONTROL, buildHeaderFromElements(outElts));
148 }
149
150 private String buildHeaderFromElements(final List<HeaderElement> outElts) {
151 final StringBuilder newHdr = new StringBuilder("");
152 boolean first = true;
153 for(final HeaderElement elt : outElts) {
154 if (!first) {
155 newHdr.append(",");
156 } else {
157 first = false;
158 }
159 newHdr.append(elt.toString());
160 }
161 return newHdr.toString();
162 }
163
164 private boolean requestMustNotHaveEntity(final HttpRequest request) {
165 return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod())
166 && request instanceof HttpEntityEnclosingRequest;
167 }
168
169 private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) {
170 if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
171 return;
172 }
173
174 final Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS);
175 if (maxForwards == null) {
176 return;
177 }
178
179 request.removeHeaders(HeaderConstants.MAX_FORWARDS);
180 final int currentMaxForwards = Integer.parseInt(maxForwards.getValue());
181
182 request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1));
183 }
184
185 private void verifyOPTIONSRequestWithBodyHasContentType(final HttpRequest request) {
186 if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
187 return;
188 }
189
190 if (!(request instanceof HttpEntityEnclosingRequest)) {
191 return;
192 }
193
194 addContentTypeHeaderIfMissing((HttpEntityEnclosingRequest) request);
195 }
196
197 private void addContentTypeHeaderIfMissing(final HttpEntityEnclosingRequest request) {
198 final HttpEntity entity = request.getEntity();
199 if (entity != null && entity.getContentType() == null) {
200 final HttpEntityWrapper entityWrapper = new HttpEntityWrapper(entity) {
201
202 @Override
203 public Header getContentType() {
204 return new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_OCTET_STREAM.getMimeType());
205 }
206
207 };
208 request.setEntity(entityWrapper);
209 }
210 }
211
212 private void verifyRequestWithExpectContinueFlagHas100continueHeader(final HttpRequest request) {
213 if (request instanceof HttpEntityEnclosingRequest) {
214
215 if (((HttpEntityEnclosingRequest) request).expectContinue()
216 && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
217 add100ContinueHeaderIfMissing(request);
218 } else {
219 remove100ContinueHeaderIfExists(request);
220 }
221 } else {
222 remove100ContinueHeaderIfExists(request);
223 }
224 }
225
226 private void remove100ContinueHeaderIfExists(final HttpRequest request) {
227 boolean hasHeader = false;
228
229 final Header[] expectHeaders = request.getHeaders(HTTP.EXPECT_DIRECTIVE);
230 List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
231
232 for (final Header h : expectHeaders) {
233 for (final HeaderElement elt : h.getElements()) {
234 if (!(HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName()))) {
235 expectElementsThatAreNot100Continue.add(elt);
236 } else {
237 hasHeader = true;
238 }
239 }
240
241 if (hasHeader) {
242 request.removeHeader(h);
243 for (final HeaderElement elt : expectElementsThatAreNot100Continue) {
244 final BasicHeader newHeader = new BasicHeader(HTTP.EXPECT_DIRECTIVE, elt.getName());
245 request.addHeader(newHeader);
246 }
247 return;
248 } else {
249 expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
250 }
251 }
252 }
253
254 private void add100ContinueHeaderIfMissing(final HttpRequest request) {
255 boolean hasHeader = false;
256
257 for (final Header h : request.getHeaders(HTTP.EXPECT_DIRECTIVE)) {
258 for (final HeaderElement elt : h.getElements()) {
259 if (HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName())) {
260 hasHeader = true;
261 }
262 }
263 }
264
265 if (!hasHeader) {
266 request.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE);
267 }
268 }
269
270 protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) {
271 final ProtocolVersion requestProtocol = request.getProtocolVersion();
272 if (requestProtocol.getMajor() != HttpVersion.HTTP_1_1.getMajor()) {
273 return false;
274 }
275
276 if (requestProtocol.getMinor() > HttpVersion.HTTP_1_1.getMinor()) {
277 return true;
278 }
279
280 return false;
281 }
282
283 protected boolean requestVersionIsTooLow(final HttpRequest request) {
284 return request.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) < 0;
285 }
286
287
288
289
290
291
292
293
294 public HttpResponse getErrorForRequest(final RequestProtocolError errorCheck) {
295 switch (errorCheck) {
296 case BODY_BUT_NO_LENGTH_ERROR:
297 return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
298 HttpStatus.SC_LENGTH_REQUIRED, ""));
299
300 case WEAK_ETAG_AND_RANGE_ERROR:
301 return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
302 HttpStatus.SC_BAD_REQUEST, "Weak eTag not compatible with byte range"));
303
304 case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR:
305 return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
306 HttpStatus.SC_BAD_REQUEST,
307 "Weak eTag not compatible with PUT or DELETE requests"));
308
309 case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME:
310 return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
311 HttpStatus.SC_BAD_REQUEST,
312 "No-Cache directive MUST NOT include a field name"));
313
314 default:
315 throw new IllegalStateException(
316 "The request was compliant, therefore no error can be generated for it.");
317
318 }
319 }
320
321 private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) {
322
323 final String method = request.getRequestLine().getMethod();
324 if (!(HeaderConstants.GET_METHOD.equals(method))) {
325 return null;
326 }
327
328 final Header range = request.getFirstHeader(HeaderConstants.RANGE);
329 if (range == null) {
330 return null;
331 }
332
333 final Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE);
334 if (ifRange == null) {
335 return null;
336 }
337
338 final String val = ifRange.getValue();
339 if (val.startsWith("W/")) {
340 return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR;
341 }
342
343 return null;
344 }
345
346 private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request) {
347
348
349 final String method = request.getRequestLine().getMethod();
350 if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD
351 .equals(method))) {
352 return null;
353 }
354
355 final Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH);
356 if (ifMatch != null) {
357 final String val = ifMatch.getValue();
358 if (val.startsWith("W/")) {
359 return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
360 }
361 } else {
362 final Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH);
363 if (ifNoneMatch == null) {
364 return null;
365 }
366
367 final String val2 = ifNoneMatch.getValue();
368 if (val2.startsWith("W/")) {
369 return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
370 }
371 }
372
373 return null;
374 }
375
376 private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(final HttpRequest request) {
377 for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
378 for(final HeaderElement elt : h.getElements()) {
379 if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName())
380 && elt.getValue() != null) {
381 return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME;
382 }
383 }
384 }
385 return null;
386 }
387 }