1
16 package okhttp3;
17
18 import java.util.concurrent.TimeUnit;
19 import javax.annotation.Nullable;
20 import okhttp3.internal.http.HttpHeaders;
21
22
28 public final class CacheControl {
29
33 public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
34
35
40 public static final CacheControl FORCE_CACHE = new Builder()
41 .onlyIfCached()
42 .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
43 .build();
44
45 private final boolean noCache;
46 private final boolean noStore;
47 private final int maxAgeSeconds;
48 private final int sMaxAgeSeconds;
49 private final boolean isPrivate;
50 private final boolean isPublic;
51 private final boolean mustRevalidate;
52 private final int maxStaleSeconds;
53 private final int minFreshSeconds;
54 private final boolean onlyIfCached;
55 private final boolean noTransform;
56 private final boolean immutable;
57
58 @Nullable String headerValue;
59
60 private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
61 boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds,
62 int minFreshSeconds, boolean onlyIfCached, boolean noTransform, boolean immutable,
63 @Nullable String headerValue) {
64 this.noCache = noCache;
65 this.noStore = noStore;
66 this.maxAgeSeconds = maxAgeSeconds;
67 this.sMaxAgeSeconds = sMaxAgeSeconds;
68 this.isPrivate = isPrivate;
69 this.isPublic = isPublic;
70 this.mustRevalidate = mustRevalidate;
71 this.maxStaleSeconds = maxStaleSeconds;
72 this.minFreshSeconds = minFreshSeconds;
73 this.onlyIfCached = onlyIfCached;
74 this.noTransform = noTransform;
75 this.immutable = immutable;
76 this.headerValue = headerValue;
77 }
78
79 CacheControl(Builder builder) {
80 this.noCache = builder.noCache;
81 this.noStore = builder.noStore;
82 this.maxAgeSeconds = builder.maxAgeSeconds;
83 this.sMaxAgeSeconds = -1;
84 this.isPrivate = false;
85 this.isPublic = false;
86 this.mustRevalidate = false;
87 this.maxStaleSeconds = builder.maxStaleSeconds;
88 this.minFreshSeconds = builder.minFreshSeconds;
89 this.onlyIfCached = builder.onlyIfCached;
90 this.noTransform = builder.noTransform;
91 this.immutable = builder.immutable;
92 }
93
94
101 public boolean noCache() {
102 return noCache;
103 }
104
105
106 public boolean noStore() {
107 return noStore;
108 }
109
110
113 public int maxAgeSeconds() {
114 return maxAgeSeconds;
115 }
116
117
121 public int sMaxAgeSeconds() {
122 return sMaxAgeSeconds;
123 }
124
125 public boolean isPrivate() {
126 return isPrivate;
127 }
128
129 public boolean isPublic() {
130 return isPublic;
131 }
132
133 public boolean mustRevalidate() {
134 return mustRevalidate;
135 }
136
137 public int maxStaleSeconds() {
138 return maxStaleSeconds;
139 }
140
141 public int minFreshSeconds() {
142 return minFreshSeconds;
143 }
144
145
151 public boolean onlyIfCached() {
152 return onlyIfCached;
153 }
154
155 public boolean noTransform() {
156 return noTransform;
157 }
158
159 public boolean immutable() {
160 return immutable;
161 }
162
163
167 public static CacheControl parse(Headers headers) {
168 boolean noCache = false;
169 boolean noStore = false;
170 int maxAgeSeconds = -1;
171 int sMaxAgeSeconds = -1;
172 boolean isPrivate = false;
173 boolean isPublic = false;
174 boolean mustRevalidate = false;
175 int maxStaleSeconds = -1;
176 int minFreshSeconds = -1;
177 boolean onlyIfCached = false;
178 boolean noTransform = false;
179 boolean immutable = false;
180
181 boolean canUseHeaderValue = true;
182 String headerValue = null;
183
184 for (int i = 0, size = headers.size(); i < size; i++) {
185 String name = headers.name(i);
186 String value = headers.value(i);
187
188 if (name.equalsIgnoreCase("Cache-Control")) {
189 if (headerValue != null) {
190
191 canUseHeaderValue = false;
192 } else {
193 headerValue = value;
194 }
195 } else if (name.equalsIgnoreCase("Pragma")) {
196
197 canUseHeaderValue = false;
198 } else {
199 continue;
200 }
201
202 int pos = 0;
203 while (pos < value.length()) {
204 int tokenStart = pos;
205 pos = HttpHeaders.skipUntil(value, pos, "=,;");
206 String directive = value.substring(tokenStart, pos).trim();
207 String parameter;
208
209 if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
210 pos++;
211 parameter = null;
212 } else {
213 pos++;
214 pos = HttpHeaders.skipWhitespace(value, pos);
215
216
217 if (pos < value.length() && value.charAt(pos) == '\"') {
218 pos++;
219 int parameterStart = pos;
220 pos = HttpHeaders.skipUntil(value, pos, "\"");
221 parameter = value.substring(parameterStart, pos);
222 pos++;
223
224
225 } else {
226 int parameterStart = pos;
227 pos = HttpHeaders.skipUntil(value, pos, ",;");
228 parameter = value.substring(parameterStart, pos).trim();
229 }
230 }
231
232 if ("no-cache".equalsIgnoreCase(directive)) {
233 noCache = true;
234 } else if ("no-store".equalsIgnoreCase(directive)) {
235 noStore = true;
236 } else if ("max-age".equalsIgnoreCase(directive)) {
237 maxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
238 } else if ("s-maxage".equalsIgnoreCase(directive)) {
239 sMaxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
240 } else if ("private".equalsIgnoreCase(directive)) {
241 isPrivate = true;
242 } else if ("public".equalsIgnoreCase(directive)) {
243 isPublic = true;
244 } else if ("must-revalidate".equalsIgnoreCase(directive)) {
245 mustRevalidate = true;
246 } else if ("max-stale".equalsIgnoreCase(directive)) {
247 maxStaleSeconds = HttpHeaders.parseSeconds(parameter, Integer.MAX_VALUE);
248 } else if ("min-fresh".equalsIgnoreCase(directive)) {
249 minFreshSeconds = HttpHeaders.parseSeconds(parameter, -1);
250 } else if ("only-if-cached".equalsIgnoreCase(directive)) {
251 onlyIfCached = true;
252 } else if ("no-transform".equalsIgnoreCase(directive)) {
253 noTransform = true;
254 } else if ("immutable".equalsIgnoreCase(directive)) {
255 immutable = true;
256 }
257 }
258 }
259
260 if (!canUseHeaderValue) {
261 headerValue = null;
262 }
263 return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
264 mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, immutable,
265 headerValue);
266 }
267
268 @Override public String toString() {
269 String result = headerValue;
270 return result != null ? result : (headerValue = headerValue());
271 }
272
273 private String headerValue() {
274 StringBuilder result = new StringBuilder();
275 if (noCache) result.append("no-cache, ");
276 if (noStore) result.append("no-store, ");
277 if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", ");
278 if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", ");
279 if (isPrivate) result.append("private, ");
280 if (isPublic) result.append("public, ");
281 if (mustRevalidate) result.append("must-revalidate, ");
282 if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", ");
283 if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", ");
284 if (onlyIfCached) result.append("only-if-cached, ");
285 if (noTransform) result.append("no-transform, ");
286 if (immutable) result.append("immutable, ");
287 if (result.length() == 0) return "";
288 result.delete(result.length() - 2, result.length());
289 return result.toString();
290 }
291
292
293 public static final class Builder {
294 boolean noCache;
295 boolean noStore;
296 int maxAgeSeconds = -1;
297 int maxStaleSeconds = -1;
298 int minFreshSeconds = -1;
299 boolean onlyIfCached;
300 boolean noTransform;
301 boolean immutable;
302
303
304 public Builder noCache() {
305 this.noCache = true;
306 return this;
307 }
308
309
310 public Builder noStore() {
311 this.noStore = true;
312 return this;
313 }
314
315
322 public Builder maxAge(int maxAge, TimeUnit timeUnit) {
323 if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge);
324 long maxAgeSecondsLong = timeUnit.toSeconds(maxAge);
325 this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE
326 ? Integer.MAX_VALUE
327 : (int) maxAgeSecondsLong;
328 return this;
329 }
330
331
338 public Builder maxStale(int maxStale, TimeUnit timeUnit) {
339 if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale);
340 long maxStaleSecondsLong = timeUnit.toSeconds(maxStale);
341 this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE
342 ? Integer.MAX_VALUE
343 : (int) maxStaleSecondsLong;
344 return this;
345 }
346
347
355 public Builder minFresh(int minFresh, TimeUnit timeUnit) {
356 if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh);
357 long minFreshSecondsLong = timeUnit.toSeconds(minFresh);
358 this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE
359 ? Integer.MAX_VALUE
360 : (int) minFreshSecondsLong;
361 return this;
362 }
363
364
368 public Builder onlyIfCached() {
369 this.onlyIfCached = true;
370 return this;
371 }
372
373
374 public Builder noTransform() {
375 this.noTransform = true;
376 return this;
377 }
378
379 public Builder immutable() {
380 this.immutable = true;
381 return this;
382 }
383
384 public CacheControl build() {
385 return new CacheControl(this);
386 }
387 }
388 }
389