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