1 /*
2 * Copyright (c) 2002-2026 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.util;
16
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 import org.htmlunit.HttpHeader;
21 import org.htmlunit.WebResponse;
22
23 /**
24 * Utility class for HTTP header analysis.
25 *
26 * @author Anton Demydenko
27 * @author Lai Quang Duong
28 * @author Ronald Brill
29 */
30 public final class HeaderUtils {
31
32 private static final String CACHE_CONTROL_PRIVATE = "private";
33 private static final String CACHE_CONTROL_PUBLIC = "public";
34 private static final String CACHE_CONTROL_NO_STORE = "no-store";
35 private static final String CACHE_CONTROL_NO_CACHE = "no-cache";
36 private static final String CACHE_CONTROL_MAX_AGE = "max-age";
37 private static final String CACHE_CONTROL_S_MAXAGE = "s-maxage";
38 private static final Pattern MAX_AGE_HEADER_PATTERN = Pattern.compile("^.*max-age=(\\d+).*$");
39 private static final Pattern S_MAXAGE_HEADER_PATTERN = Pattern.compile("^.*s-maxage=(\\d+).*$");
40
41 private HeaderUtils() {
42 // utility class
43 }
44
45 /**
46 * Returns whether the {@code Cache-Control} header is present and contains the {@code private} directive.
47 *
48 * @param response the {@link WebResponse} to check
49 * @return {@code true} if the {@code Cache-Control} header contains {@code private}
50 */
51 public static boolean containsPrivate(final WebResponse response) {
52 return containsCacheControlValue(response, CACHE_CONTROL_PRIVATE);
53 }
54
55 /**
56 * Returns whether the {@code Cache-Control} header is present and contains the {@code public} directive.
57 *
58 * @param response the {@link WebResponse} to check
59 * @return {@code true} if the {@code Cache-Control} header contains {@code public}
60 */
61 public static boolean containsPublic(final WebResponse response) {
62 return containsCacheControlValue(response, CACHE_CONTROL_PUBLIC);
63 }
64
65 /**
66 * Returns whether the {@code Cache-Control} header is present and contains the {@code no-store} directive.
67 *
68 * @param response the {@link WebResponse} to check
69 * @return {@code true} if the {@code Cache-Control} header contains {@code no-store}
70 */
71 public static boolean containsNoStore(final WebResponse response) {
72 return containsCacheControlValue(response, CACHE_CONTROL_NO_STORE);
73 }
74
75 /**
76 * Returns whether the {@code Cache-Control} header is present and contains the {@code no-cache} directive.
77 *
78 * @param response the {@link WebResponse} to check
79 * @return {@code true} if the {@code Cache-Control} header contains {@code no-cache}
80 */
81 public static boolean containsNoCache(final WebResponse response) {
82 return containsCacheControlValue(response, CACHE_CONTROL_NO_CACHE);
83 }
84
85 /**
86 * Returns whether the {@code ETag} header is present.
87 *
88 * @param response the {@link WebResponse} to check
89 * @return {@code true} if the {@code ETag} header is present
90 */
91 public static boolean containsETag(final WebResponse response) {
92 return response.getResponseHeaderValue(HttpHeader.ETAG) != null;
93 }
94
95 /**
96 * Returns whether the {@code Last-Modified} header is present.
97 *
98 * @param response the {@link WebResponse} to check
99 * @return {@code true} if the {@code Last-Modified} header is present
100 */
101 public static boolean containsLastModified(final WebResponse response) {
102 return response.getResponseHeaderValue(HttpHeader.LAST_MODIFIED) != null;
103 }
104
105 /**
106 * Returns whether the {@code Cache-Control} header is present and contains the {@code s-maxage} directive.
107 *
108 * @param response the {@link WebResponse} to check
109 * @return {@code true} if the {@code Cache-Control} header contains {@code s-maxage}
110 */
111 public static boolean containsSMaxage(final WebResponse response) {
112 return containsCacheControlValue(response, CACHE_CONTROL_S_MAXAGE);
113 }
114
115 /**
116 * Returns whether the {@code Cache-Control} header is present and contains the {@code max-age} directive.
117 *
118 * @param response the {@link WebResponse} to check
119 * @return {@code true} if the {@code Cache-Control} header contains {@code max-age}
120 */
121 public static boolean containsMaxAge(final WebResponse response) {
122 return containsCacheControlValue(response, CACHE_CONTROL_MAX_AGE);
123 }
124
125 /**
126 * Returns whether the {@code Cache-Control} header is present and contains
127 * either the {@code max-age} or the {@code s-maxage} directive.
128 *
129 * @param response the {@link WebResponse} to check
130 * @return {@code true} if the {@code Cache-Control} header contains {@code max-age} or {@code s-maxage}
131 */
132 public static boolean containsMaxAgeOrSMaxage(final WebResponse response) {
133 final String cacheControl = response.getResponseHeaderValue(HttpHeader.CACHE_CONTROL);
134 if (StringUtils.containsIgnoreCase(cacheControl, CACHE_CONTROL_MAX_AGE)) {
135 return true;
136 }
137 return StringUtils.containsIgnoreCase(cacheControl, CACHE_CONTROL_S_MAXAGE);
138 }
139
140 /**
141 * Returns the value of the {@code s-maxage} directive, or {@code 0} if it is absent.
142 *
143 * @param response the {@link WebResponse} to check
144 * @return the value of the {@code s-maxage} directive, or {@code 0}
145 */
146 public static long sMaxage(final WebResponse response) {
147 if (containsCacheControlValue(response, CACHE_CONTROL_S_MAXAGE)) {
148 return directiveValue(response, S_MAXAGE_HEADER_PATTERN);
149 }
150 return 0;
151 }
152
153 /**
154 * Returns the value of the {@code max-age} directive, or {@code 0} if it is absent.
155 *
156 * @param response the {@link WebResponse} to check
157 * @return the value of the {@code max-age} directive, or {@code 0}
158 */
159 public static long maxAge(final WebResponse response) {
160 if (containsCacheControlValue(response, CACHE_CONTROL_MAX_AGE)) {
161 return directiveValue(response, MAX_AGE_HEADER_PATTERN);
162 }
163
164 return 0;
165 }
166
167 private static long directiveValue(final WebResponse response, final Pattern pattern) {
168 final String value = response.getResponseHeaderValue(HttpHeader.CACHE_CONTROL);
169 if (value != null) {
170 final Matcher matcher = pattern.matcher(value);
171 if (matcher.matches()) {
172 return Long.parseLong(matcher.group(1));
173 }
174 }
175
176 return 0;
177 }
178
179 private static boolean containsCacheControlValue(final WebResponse response, final String value) {
180 final String cacheControl = response.getResponseHeaderValue(HttpHeader.CACHE_CONTROL);
181 return StringUtils.containsIgnoreCase(cacheControl, value);
182 }
183 }