View Javadoc
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 }