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 if (requestProtocol.getMinor() > HttpVersion.HTTP_1_1.getMinor()) {
163 return true;
164 }
165
166 return false;
167 }
168
169 protected boolean requestVersionIsTooLow(final HttpRequest request) {
170 final ProtocolVersion requestProtocol = request.getVersion();
171 return requestProtocol != null && requestProtocol.compareToVersion(HttpVersion.HTTP_1_1) < 0;
172 }
173
174 private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) {
175
176 final String method = request.getMethod();
177 if (!(HeaderConstants.GET_METHOD.equals(method))) {
178 return null;
179 }
180
181 final Header range = request.getFirstHeader(HeaderConstants.RANGE);
182 if (range == null) {
183 return null;
184 }
185
186 final Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE);
187 if (ifRange == null) {
188 return null;
189 }
190
191 final String val = ifRange.getValue();
192 if (val.startsWith("W/")) {
193 return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR;
194 }
195
196 return null;
197 }
198
199 private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request) {
200
201
202 final String method = request.getMethod();
203 if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD.equals(method))) {
204 return null;
205 }
206
207 final Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH);
208 if (ifMatch != null) {
209 final String val = ifMatch.getValue();
210 if (val.startsWith("W/")) {
211 return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
212 }
213 } else {
214 final Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH);
215 if (ifNoneMatch == null) {
216 return null;
217 }
218
219 final String val2 = ifNoneMatch.getValue();
220 if (val2.startsWith("W/")) {
221 return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
222 }
223 }
224
225 return null;
226 }
227
228 private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(final HttpRequest request) {
229 final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
230 while (it.hasNext()) {
231 final HeaderElement elt = it.next();
232 if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName()) && elt.getValue() != null) {
233 return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME;
234 }
235 }
236 return null;
237 }
238 }