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.BitSet;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.Set;
33 import java.util.function.BiConsumer;
34
35 import org.apache.hc.client5.http.cache.HeaderConstants;
36 import org.apache.hc.client5.http.cache.HttpCacheEntry;
37 import org.apache.hc.core5.annotation.Contract;
38 import org.apache.hc.core5.annotation.Internal;
39 import org.apache.hc.core5.annotation.ThreadingBehavior;
40 import org.apache.hc.core5.http.FormattedHeader;
41 import org.apache.hc.core5.http.Header;
42 import org.apache.hc.core5.http.HttpHeaders;
43 import org.apache.hc.core5.http.HttpRequest;
44 import org.apache.hc.core5.http.HttpResponse;
45 import org.apache.hc.core5.http.message.ParserCursor;
46 import org.apache.hc.core5.util.Args;
47 import org.apache.hc.core5.util.CharArrayBuffer;
48 import org.apache.hc.core5.util.TextUtils;
49 import org.apache.hc.core5.util.Tokenizer;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 @Internal
71 @Contract(threading = ThreadingBehavior.SAFE)
72 class CacheControlHeaderParser {
73
74
75
76
77 public static final CacheControlHeaderParser INSTANCE = new CacheControlHeaderParser();
78
79
80
81
82 private static final Logger LOG = LoggerFactory.getLogger(CacheControlHeaderParser.class);
83
84
85 private final static char EQUAL_CHAR = '=';
86
87
88
89
90 private static final BitSet TOKEN_DELIMS = Tokenizer.INIT_BITSET(EQUAL_CHAR, ',');
91
92
93
94
95 private static final BitSet VALUE_DELIMS = Tokenizer.INIT_BITSET(EQUAL_CHAR, ',');
96
97
98
99
100 private final Tokenizer tokenParser;
101
102
103
104
105 protected CacheControlHeaderParser() {
106 super();
107 this.tokenParser = Tokenizer.INSTANCE;
108 }
109
110 public void parse(final Iterator<Header> headerIterator, final BiConsumer<String, String> consumer) {
111 while (headerIterator.hasNext()) {
112 final Header header = headerIterator.next();
113 final CharArrayBuffer buffer;
114 final Tokenizer.Cursor cursor;
115 if (header instanceof FormattedHeader) {
116 buffer = ((FormattedHeader) header).getBuffer();
117 cursor = new Tokenizer.Cursor(((FormattedHeader) header).getValuePos(), buffer.length());
118 } else {
119 final String s = header.getValue();
120 if (s == null) {
121 continue;
122 }
123 buffer = new CharArrayBuffer(s.length());
124 buffer.append(s);
125 cursor = new Tokenizer.Cursor(0, buffer.length());
126 }
127
128
129 while (!cursor.atEnd()) {
130 final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
131 String value = null;
132 if (!cursor.atEnd()) {
133 final int valueDelim = buffer.charAt(cursor.getPos());
134 cursor.updatePos(cursor.getPos() + 1);
135 if (valueDelim == EQUAL_CHAR) {
136 value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
137 if (!cursor.atEnd()) {
138 cursor.updatePos(cursor.getPos() + 1);
139 }
140 }
141 }
142 consumer.accept(name, value);
143 }
144 }
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 public final ResponseCacheControl parseResponse(final Iterator<Header> headerIterator) {
162 Args.notNull(headerIterator, "headerIterator");
163 final ResponseCacheControl.Builder builder = new ResponseCacheControl.Builder();
164 parse(headerIterator, (name, value) -> {
165 if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_S_MAX_AGE)) {
166 builder.setSharedMaxAge(parseSeconds(name, value));
167 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_AGE)) {
168 builder.setMaxAge(parseSeconds(name, value));
169 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE)) {
170 builder.setMustRevalidate(true);
171 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
172 builder.setNoCache(true);
173 if (value != null) {
174 final Tokenizer.Cursor valCursor = new ParserCursor(0, value.length());
175 final Set<String> noCacheFields = new HashSet<>();
176 while (!valCursor.atEnd()) {
177 final String token = tokenParser.parseToken(value, valCursor, VALUE_DELIMS);
178 if (!TextUtils.isBlank(token)) {
179 noCacheFields.add(token);
180 }
181 if (!valCursor.atEnd()) {
182 valCursor.updatePos(valCursor.getPos() + 1);
183 }
184 }
185 builder.setNoCacheFields(noCacheFields);
186 }
187 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
188 builder.setNoStore(true);
189 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PRIVATE)) {
190 builder.setCachePrivate(true);
191 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE)) {
192 builder.setProxyRevalidate(true);
193 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PUBLIC)) {
194 builder.setCachePublic(true);
195 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_WHILE_REVALIDATE)) {
196 builder.setStaleWhileRevalidate(parseSeconds(name, value));
197 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_IF_ERROR)) {
198 builder.setStaleIfError(parseSeconds(name, value));
199 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MUST_UNDERSTAND)) {
200 builder.setMustUnderstand(true);
201 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_IMMUTABLE)) {
202 builder.setImmutable(true);
203 }
204 });
205 return builder.build();
206 }
207
208 public final ResponseCacheControl parse(final HttpResponse response) {
209 return parseResponse(response.headerIterator(HttpHeaders.CACHE_CONTROL));
210 }
211
212 public final ResponseCacheControl parse(final HttpCacheEntry cacheEntry) {
213 return parseResponse(cacheEntry.headerIterator(HttpHeaders.CACHE_CONTROL));
214 }
215
216
217
218
219
220
221
222
223
224
225 public final RequestCacheControl parseRequest(final Iterator<Header> headerIterator) {
226 Args.notNull(headerIterator, "headerIterator");
227 final RequestCacheControl.Builder builder = new RequestCacheControl.Builder();
228 parse(headerIterator, (name, value) -> {
229 if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_AGE)) {
230 builder.setMaxAge(parseSeconds(name, value));
231 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_STALE)) {
232 builder.setMaxStale(parseSeconds(name, value));
233 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MIN_FRESH)) {
234 builder.setMinFresh(parseSeconds(name, value));
235 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
236 builder.setNoStore(true);
237 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
238 builder.setNoCache(true);
239 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_ONLY_IF_CACHED)) {
240 builder.setOnlyIfCached(true);
241 } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_IF_ERROR)) {
242 builder.setStaleIfError(parseSeconds(name, value));
243 }
244 });
245 return builder.build();
246 }
247
248 public final RequestCacheControl parse(final HttpRequest request) {
249 return parseRequest(request.headerIterator(HttpHeaders.CACHE_CONTROL));
250 }
251
252 private static long parseSeconds(final String name, final String value) {
253 final long delta = CacheSupport.deltaSeconds(value);
254 if (delta == -1 && LOG.isDebugEnabled()) {
255 LOG.debug("Directive {} is malformed: {}", name, value);
256 }
257 return delta;
258 }
259
260 }
261
262