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.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Date;
32 import java.util.List;
33
34 import org.apache.http.Header;
35 import org.apache.http.HeaderElement;
36 import org.apache.http.HttpEntity;
37 import org.apache.http.HttpEntityEnclosingRequest;
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.annotation.Contract;
43 import org.apache.http.annotation.ThreadingBehavior;
44 import org.apache.http.client.ClientProtocolException;
45 import org.apache.http.client.cache.HeaderConstants;
46 import org.apache.http.client.methods.HttpRequestWrapper;
47 import org.apache.http.client.utils.DateUtils;
48 import org.apache.http.message.BasicHeader;
49 import org.apache.http.protocol.HTTP;
50
51
52
53
54 @Contract(threading = ThreadingBehavior.IMMUTABLE)
55 class ResponseProtocolCompliance {
56
57 private static final String UNEXPECTED_100_CONTINUE = "The incoming request did not contain a "
58 + "100-continue header, but the response was a Status 100, continue.";
59 private static final String UNEXPECTED_PARTIAL_CONTENT = "partial content was returned for a request that did not ask for it";
60
61
62
63
64
65
66
67
68
69
70 public void ensureProtocolCompliance(final HttpRequestWrapper request, final HttpResponse response)
71 throws IOException {
72 if (backendResponseMustNotHaveBody(request, response)) {
73 consumeBody(response);
74 response.setEntity(null);
75 }
76
77 requestDidNotExpect100ContinueButResponseIsOne(request, response);
78
79 transferEncodingIsNotReturnedTo1_0Client(request, response);
80
81 ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(request, response);
82
83 ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response);
84
85 ensure206ContainsDateHeader(response);
86
87 ensure304DoesNotContainExtraEntityHeaders(response);
88
89 identityIsNotUsedInContentEncoding(response);
90
91 warningsWithNonMatchingWarnDatesAreRemoved(response);
92 }
93
94 private void consumeBody(final HttpResponse response) throws IOException {
95 final HttpEntity body = response.getEntity();
96 if (body != null) {
97 IOUtils.consume(body);
98 }
99 }
100
101 private void warningsWithNonMatchingWarnDatesAreRemoved(
102 final HttpResponse response) {
103 final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER).getValue());
104 if (responseDate == null) {
105 return;
106 }
107
108 final Header[] warningHeaders = response.getHeaders(HeaderConstants.WARNING);
109
110 if (warningHeaders == null || warningHeaders.length == 0) {
111 return;
112 }
113
114 final List<Header> newWarningHeaders = new ArrayList<Header>();
115 boolean modified = false;
116 for(final Header h : warningHeaders) {
117 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
118 final Date warnDate = wv.getWarnDate();
119 if (warnDate == null || warnDate.equals(responseDate)) {
120 newWarningHeaders.add(new BasicHeader(HeaderConstants.WARNING,wv.toString()));
121 } else {
122 modified = true;
123 }
124 }
125 }
126 if (modified) {
127 response.removeHeaders(HeaderConstants.WARNING);
128 for(final Header h : newWarningHeaders) {
129 response.addHeader(h);
130 }
131 }
132 }
133
134 private void identityIsNotUsedInContentEncoding(final HttpResponse response) {
135 final Header[] hdrs = response.getHeaders(HTTP.CONTENT_ENCODING);
136 if (hdrs == null || hdrs.length == 0) {
137 return;
138 }
139 final List<Header> newHeaders = new ArrayList<Header>();
140 boolean modified = false;
141 for (final Header h : hdrs) {
142 final StringBuilder buf = new StringBuilder();
143 boolean first = true;
144 for (final HeaderElement elt : h.getElements()) {
145 if ("identity".equalsIgnoreCase(elt.getName())) {
146 modified = true;
147 } else {
148 if (!first) {
149 buf.append(",");
150 }
151 buf.append(elt.toString());
152 first = false;
153 }
154 }
155 final String newHeaderValue = buf.toString();
156 if (!"".equals(newHeaderValue)) {
157 newHeaders.add(new BasicHeader(HTTP.CONTENT_ENCODING, newHeaderValue));
158 }
159 }
160 if (!modified) {
161 return;
162 }
163 response.removeHeaders(HTTP.CONTENT_ENCODING);
164 for (final Header h : newHeaders) {
165 response.addHeader(h);
166 }
167 }
168
169 private void ensure206ContainsDateHeader(final HttpResponse response) {
170 if (response.getFirstHeader(HTTP.DATE_HEADER) == null) {
171 response.addHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date()));
172 }
173
174 }
175
176 private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(final HttpRequest request,
177 final HttpResponse response) throws IOException {
178 if (request.getFirstHeader(HeaderConstants.RANGE) != null
179 || response.getStatusLine().getStatusCode() != HttpStatus.SC_PARTIAL_CONTENT) {
180 return;
181 }
182
183 consumeBody(response);
184 throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT);
185 }
186
187 private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(final HttpRequest request,
188 final HttpResponse response) {
189 if (!request.getRequestLine().getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) {
190 return;
191 }
192
193 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
194 return;
195 }
196
197 if (response.getFirstHeader(HTTP.CONTENT_LEN) == null) {
198 response.addHeader(HTTP.CONTENT_LEN, "0");
199 }
200 }
201
202 private void ensure304DoesNotContainExtraEntityHeaders(final HttpResponse response) {
203 final String[] disallowedEntityHeaders = { HeaderConstants.ALLOW, HTTP.CONTENT_ENCODING,
204 "Content-Language", HTTP.CONTENT_LEN, "Content-MD5",
205 "Content-Range", HTTP.CONTENT_TYPE, HeaderConstants.LAST_MODIFIED
206 };
207 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
208 for(final String hdr : disallowedEntityHeaders) {
209 response.removeHeaders(hdr);
210 }
211 }
212 }
213
214 private boolean backendResponseMustNotHaveBody(final HttpRequest request, final HttpResponse backendResponse) {
215 return HeaderConstants.HEAD_METHOD.equals(request.getRequestLine().getMethod())
216 || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT
217 || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_RESET_CONTENT
218 || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED;
219 }
220
221 private void requestDidNotExpect100ContinueButResponseIsOne(final HttpRequestWrapper request,
222 final HttpResponse response) throws IOException {
223 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
224 return;
225 }
226
227 final HttpRequest originalRequest = request.getOriginal();
228 if (originalRequest instanceof HttpEntityEnclosingRequest) {
229 if (((HttpEntityEnclosingRequest)originalRequest).expectContinue()) {
230 return;
231 }
232 }
233 consumeBody(response);
234 throw new ClientProtocolException(UNEXPECTED_100_CONTINUE);
235 }
236
237 private void transferEncodingIsNotReturnedTo1_0Client(final HttpRequestWrapper request,
238 final HttpResponse response) {
239 final HttpRequest originalRequest = request.getOriginal();
240 if (originalRequest.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) >= 0) {
241 return;
242 }
243
244 removeResponseTransferEncoding(response);
245 }
246
247 private void removeResponseTransferEncoding(final HttpResponse response) {
248 response.removeHeaders("TE");
249 response.removeHeaders(HTTP.TRANSFER_ENCODING);
250 }
251
252 }