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.hc.client5.http.impl.cache;
28
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Iterator;
32 import java.util.List;
33
34 import org.apache.hc.client5.http.cache.HeaderConstants;
35 import org.apache.hc.core5.http.Header;
36 import org.apache.hc.core5.http.HeaderElement;
37 import org.apache.hc.core5.http.HttpRequest;
38 import org.apache.hc.core5.http.HttpVersion;
39 import org.apache.hc.core5.http.ProtocolVersion;
40 import org.apache.hc.core5.http.message.MessageSupport;
41
42 class RequestProtocolCompliance {
43 private final boolean weakETagOnPutDeleteAllowed;
44
45 public RequestProtocolCompliance() {
46 super();
47 this.weakETagOnPutDeleteAllowed = false;
48 }
49
50 public RequestProtocolCompliance(final boolean weakETagOnPutDeleteAllowed) {
51 super();
52 this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
53 }
54
55 private static final List<String> disallowedWithNoCache =
56 Arrays.asList(HeaderConstants.CACHE_CONTROL_MIN_FRESH, HeaderConstants.CACHE_CONTROL_MAX_STALE, HeaderConstants.CACHE_CONTROL_MAX_AGE);
57
58
59
60
61
62
63
64
65 public List<RequestProtocolError> requestIsFatallyNonCompliant(final HttpRequest request) {
66 final List<RequestProtocolError> theErrors = new ArrayList<>();
67
68 RequestProtocolError anError = requestHasWeakETagAndRange(request);
69 if (anError != null) {
70 theErrors.add(anError);
71 }
72
73 if (!weakETagOnPutDeleteAllowed) {
74 anError = requestHasWeekETagForPUTOrDELETEIfMatch(request);
75 if (anError != null) {
76 theErrors.add(anError);
77 }
78 }
79
80 anError = requestContainsNoCacheDirectiveWithFieldName(request);
81 if (anError != null) {
82 theErrors.add(anError);
83 }
84
85 return theErrors;
86 }
87
88
89
90
91
92
93
94 public void makeRequestCompliant(final HttpRequest request) {
95 decrementOPTIONSMaxForwardsIfGreaterThen0(request);
96 stripOtherFreshnessDirectivesWithNoCache(request);
97
98 if (requestVersionIsTooLow(request) || requestMinorVersionIsTooHighMajorVersionsMatch(request)) {
99 request.setVersion(HttpVersion.HTTP_1_1);
100 }
101 }
102
103 private void stripOtherFreshnessDirectivesWithNoCache(final HttpRequest request) {
104 final List<HeaderElement> outElts = new ArrayList<>();
105 boolean shouldStrip = false;
106 final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
107 while (it.hasNext()) {
108 final HeaderElement elt = it.next();
109 if (!disallowedWithNoCache.contains(elt.getName())) {
110 outElts.add(elt);
111 }
112 if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
113 shouldStrip = true;
114 }
115 }
116 if (!shouldStrip) {
117 return;
118 }
119 request.removeHeaders(HeaderConstants.CACHE_CONTROL);
120 request.setHeader(HeaderConstants.CACHE_CONTROL, buildHeaderFromElements(outElts));
121 }
122
123 private String buildHeaderFromElements(final List<HeaderElement> outElts) {
124 final StringBuilder newHdr = new StringBuilder();
125 boolean first = true;
126 for(final HeaderElement elt : outElts) {
127 if (!first) {
128 newHdr.append(",");
129 } else {
130 first = false;
131 }
132 newHdr.append(elt);
133 }
134 return newHdr.toString();
135 }
136
137 private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) {
138 if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
139 return;
140 }
141
142 final Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS);
143 if (maxForwards == null) {
144 return;
145 }
146
147 request.removeHeaders(HeaderConstants.MAX_FORWARDS);
148 final int currentMaxForwards = Integer.parseInt(maxForwards.getValue());
149
150 request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1));
151 }
152
153 protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) {
154 final ProtocolVersion requestProtocol = request.getVersion();
155 if (requestProtocol == null) {
156 return false;
157 }
158 if (requestProtocol.getMajor() != HttpVersion.HTTP_1_1.getMajor()) {
159 return false;
160 }
161
162 return requestProtocol.getMinor() > HttpVersion.HTTP_1_1.getMinor();
163 }
164
165 protected boolean requestVersionIsTooLow(final HttpRequest request) {
166 final ProtocolVersion requestProtocol = request.getVersion();
167 return requestProtocol != null && requestProtocol.compareToVersion(HttpVersion.HTTP_1_1) < 0;
168 }
169
170 private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) {
171
172 final String method = request.getMethod();
173 if (!(HeaderConstants.GET_METHOD.equals(method))) {
174 return null;
175 }
176
177 final Header range = request.getFirstHeader(HeaderConstants.RANGE);
178 if (range == null) {
179 return null;
180 }
181
182 final Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE);
183 if (ifRange == null) {
184 return null;
185 }
186
187 final String val = ifRange.getValue();
188 if (val.startsWith("W/")) {
189 return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR;
190 }
191
192 return null;
193 }
194
195 private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request) {
196
197
198 final String method = request.getMethod();
199 if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD.equals(method))) {
200 return null;
201 }
202
203 final Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH);
204 if (ifMatch != null) {
205 final String val = ifMatch.getValue();
206 if (val.startsWith("W/")) {
207 return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
208 }
209 } else {
210 final Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH);
211 if (ifNoneMatch == null) {
212 return null;
213 }
214
215 final String val2 = ifNoneMatch.getValue();
216 if (val2.startsWith("W/")) {
217 return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
218 }
219 }
220
221 return null;
222 }
223
224 private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(final HttpRequest request) {
225 final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
226 while (it.hasNext()) {
227 final HeaderElement elt = it.next();
228 if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName()) && elt.getValue() != null) {
229 return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME;
230 }
231 }
232 return null;
233 }
234 }