001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.http;
018
019import java.io.Serializable;
020import java.net.InetSocketAddress;
021import java.net.URI;
022import java.nio.charset.Charset;
023import java.nio.charset.CharsetEncoder;
024import java.nio.charset.StandardCharsets;
025import java.text.DecimalFormat;
026import java.text.DecimalFormatSymbols;
027import java.time.Duration;
028import java.time.Instant;
029import java.time.ZoneId;
030import java.time.ZonedDateTime;
031import java.time.format.DateTimeFormatter;
032import java.time.format.DateTimeParseException;
033import java.util.ArrayList;
034import java.util.Base64;
035import java.util.Collection;
036import java.util.Collections;
037import java.util.EnumSet;
038import java.util.List;
039import java.util.Locale;
040import java.util.Map;
041import java.util.Set;
042import java.util.StringJoiner;
043import java.util.regex.Matcher;
044import java.util.regex.Pattern;
045import java.util.stream.Collectors;
046
047import org.springframework.lang.Nullable;
048import org.springframework.util.Assert;
049import org.springframework.util.CollectionUtils;
050import org.springframework.util.LinkedCaseInsensitiveMap;
051import org.springframework.util.LinkedMultiValueMap;
052import org.springframework.util.MultiValueMap;
053import org.springframework.util.StringUtils;
054
055/**
056 * A data structure representing HTTP request or response headers, mapping String header names
057 * to a list of String values, also offering accessors for common application-level data types.
058 *
059 * <p>In addition to the regular methods defined by {@link Map}, this class offers many common
060 * convenience methods, for example:
061 * <ul>
062 * <li>{@link #getFirst(String)} returns the first value associated with a given header name</li>
063 * <li>{@link #add(String, String)} adds a header value to the list of values for a header name</li>
064 * <li>{@link #set(String, String)} sets the header value to a single string value</li>
065 * </ul>
066 *
067 * <p>Note that {@code HttpHeaders} generally treats header names in a case-insensitive manner.
068 *
069 * @author Arjen Poutsma
070 * @author Sebastien Deleuze
071 * @author Brian Clozel
072 * @author Juergen Hoeller
073 * @author Josh Long
074 * @author Sam Brannen
075 * @since 3.0
076 */
077public class HttpHeaders implements MultiValueMap<String, String>, Serializable {
078
079        private static final long serialVersionUID = -8578554704772377436L;
080
081
082        /**
083         * The HTTP {@code Accept} header field name.
084         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a>
085         */
086        public static final String ACCEPT = "Accept";
087        /**
088         * The HTTP {@code Accept-Charset} header field name.
089         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
090         */
091        public static final String ACCEPT_CHARSET = "Accept-Charset";
092        /**
093         * The HTTP {@code Accept-Encoding} header field name.
094         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
095         */
096        public static final String ACCEPT_ENCODING = "Accept-Encoding";
097        /**
098         * The HTTP {@code Accept-Language} header field name.
099         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
100         */
101        public static final String ACCEPT_LANGUAGE = "Accept-Language";
102        /**
103         * The HTTP {@code Accept-Ranges} header field name.
104         * @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
105         */
106        public static final String ACCEPT_RANGES = "Accept-Ranges";
107        /**
108         * The CORS {@code Access-Control-Allow-Credentials} response header field name.
109         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
110         */
111        public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
112        /**
113         * The CORS {@code Access-Control-Allow-Headers} response header field name.
114         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
115         */
116        public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
117        /**
118         * The CORS {@code Access-Control-Allow-Methods} response header field name.
119         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
120         */
121        public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
122        /**
123         * The CORS {@code Access-Control-Allow-Origin} response header field name.
124         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
125         */
126        public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
127        /**
128         * The CORS {@code Access-Control-Expose-Headers} response header field name.
129         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
130         */
131        public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
132        /**
133         * The CORS {@code Access-Control-Max-Age} response header field name.
134         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
135         */
136        public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
137        /**
138         * The CORS {@code Access-Control-Request-Headers} request header field name.
139         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
140         */
141        public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
142        /**
143         * The CORS {@code Access-Control-Request-Method} request header field name.
144         * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
145         */
146        public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
147        /**
148         * The HTTP {@code Age} header field name.
149         * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
150         */
151        public static final String AGE = "Age";
152        /**
153         * The HTTP {@code Allow} header field name.
154         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
155         */
156        public static final String ALLOW = "Allow";
157        /**
158         * The HTTP {@code Authorization} header field name.
159         * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
160         */
161        public static final String AUTHORIZATION = "Authorization";
162        /**
163         * The HTTP {@code Cache-Control} header field name.
164         * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
165         */
166        public static final String CACHE_CONTROL = "Cache-Control";
167        /**
168         * The HTTP {@code Connection} header field name.
169         * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
170         */
171        public static final String CONNECTION = "Connection";
172        /**
173         * The HTTP {@code Content-Encoding} header field name.
174         * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
175         */
176        public static final String CONTENT_ENCODING = "Content-Encoding";
177        /**
178         * The HTTP {@code Content-Disposition} header field name.
179         * @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a>
180         */
181        public static final String CONTENT_DISPOSITION = "Content-Disposition";
182        /**
183         * The HTTP {@code Content-Language} header field name.
184         * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
185         */
186        public static final String CONTENT_LANGUAGE = "Content-Language";
187        /**
188         * The HTTP {@code Content-Length} header field name.
189         * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
190         */
191        public static final String CONTENT_LENGTH = "Content-Length";
192        /**
193         * The HTTP {@code Content-Location} header field name.
194         * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
195         */
196        public static final String CONTENT_LOCATION = "Content-Location";
197        /**
198         * The HTTP {@code Content-Range} header field name.
199         * @see <a href="https://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
200         */
201        public static final String CONTENT_RANGE = "Content-Range";
202        /**
203         * The HTTP {@code Content-Type} header field name.
204         * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
205         */
206        public static final String CONTENT_TYPE = "Content-Type";
207        /**
208         * The HTTP {@code Cookie} header field name.
209         * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
210         */
211        public static final String COOKIE = "Cookie";
212        /**
213         * The HTTP {@code Date} header field name.
214         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
215         */
216        public static final String DATE = "Date";
217        /**
218         * The HTTP {@code ETag} header field name.
219         * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
220         */
221        public static final String ETAG = "ETag";
222        /**
223         * The HTTP {@code Expect} header field name.
224         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
225         */
226        public static final String EXPECT = "Expect";
227        /**
228         * The HTTP {@code Expires} header field name.
229         * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
230         */
231        public static final String EXPIRES = "Expires";
232        /**
233         * The HTTP {@code From} header field name.
234         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
235         */
236        public static final String FROM = "From";
237        /**
238         * The HTTP {@code Host} header field name.
239         * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
240         */
241        public static final String HOST = "Host";
242        /**
243         * The HTTP {@code If-Match} header field name.
244         * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
245         */
246        public static final String IF_MATCH = "If-Match";
247        /**
248         * The HTTP {@code If-Modified-Since} header field name.
249         * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
250         */
251        public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
252        /**
253         * The HTTP {@code If-None-Match} header field name.
254         * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
255         */
256        public static final String IF_NONE_MATCH = "If-None-Match";
257        /**
258         * The HTTP {@code If-Range} header field name.
259         * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
260         */
261        public static final String IF_RANGE = "If-Range";
262        /**
263         * The HTTP {@code If-Unmodified-Since} header field name.
264         * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
265         */
266        public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
267        /**
268         * The HTTP {@code Last-Modified} header field name.
269         * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
270         */
271        public static final String LAST_MODIFIED = "Last-Modified";
272        /**
273         * The HTTP {@code Link} header field name.
274         * @see <a href="https://tools.ietf.org/html/rfc5988">RFC 5988</a>
275         */
276        public static final String LINK = "Link";
277        /**
278         * The HTTP {@code Location} header field name.
279         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
280         */
281        public static final String LOCATION = "Location";
282        /**
283         * The HTTP {@code Max-Forwards} header field name.
284         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
285         */
286        public static final String MAX_FORWARDS = "Max-Forwards";
287        /**
288         * The HTTP {@code Origin} header field name.
289         * @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
290         */
291        public static final String ORIGIN = "Origin";
292        /**
293         * The HTTP {@code Pragma} header field name.
294         * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
295         */
296        public static final String PRAGMA = "Pragma";
297        /**
298         * The HTTP {@code Proxy-Authenticate} header field name.
299         * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
300         */
301        public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
302        /**
303         * The HTTP {@code Proxy-Authorization} header field name.
304         * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
305         */
306        public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
307        /**
308         * The HTTP {@code Range} header field name.
309         * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
310         */
311        public static final String RANGE = "Range";
312        /**
313         * The HTTP {@code Referer} header field name.
314         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
315         */
316        public static final String REFERER = "Referer";
317        /**
318         * The HTTP {@code Retry-After} header field name.
319         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
320         */
321        public static final String RETRY_AFTER = "Retry-After";
322        /**
323         * The HTTP {@code Server} header field name.
324         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
325         */
326        public static final String SERVER = "Server";
327        /**
328         * The HTTP {@code Set-Cookie} header field name.
329         * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
330         */
331        public static final String SET_COOKIE = "Set-Cookie";
332        /**
333         * The HTTP {@code Set-Cookie2} header field name.
334         * @see <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a>
335         */
336        public static final String SET_COOKIE2 = "Set-Cookie2";
337        /**
338         * The HTTP {@code TE} header field name.
339         * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
340         */
341        public static final String TE = "TE";
342        /**
343         * The HTTP {@code Trailer} header field name.
344         * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
345         */
346        public static final String TRAILER = "Trailer";
347        /**
348         * The HTTP {@code Transfer-Encoding} header field name.
349         * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
350         */
351        public static final String TRANSFER_ENCODING = "Transfer-Encoding";
352        /**
353         * The HTTP {@code Upgrade} header field name.
354         * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
355         */
356        public static final String UPGRADE = "Upgrade";
357        /**
358         * The HTTP {@code User-Agent} header field name.
359         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
360         */
361        public static final String USER_AGENT = "User-Agent";
362        /**
363         * The HTTP {@code Vary} header field name.
364         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
365         */
366        public static final String VARY = "Vary";
367        /**
368         * The HTTP {@code Via} header field name.
369         * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
370         */
371        public static final String VIA = "Via";
372        /**
373         * The HTTP {@code Warning} header field name.
374         * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
375         */
376        public static final String WARNING = "Warning";
377        /**
378         * The HTTP {@code WWW-Authenticate} header field name.
379         * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
380         */
381        public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
382
383
384        /**
385         * An empty {@code HttpHeaders} instance (immutable).
386         * @since 5.0
387         */
388        public static final HttpHeaders EMPTY = new ReadOnlyHttpHeaders(new LinkedMultiValueMap<>());
389
390        /**
391         * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match".
392         * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
393         */
394        private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
395
396        private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols(Locale.ENGLISH);
397
398        private static final ZoneId GMT = ZoneId.of("GMT");
399
400        /**
401         * Date formats with time zone as specified in the HTTP RFC to use for formatting.
402         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
403         */
404        private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).withZone(GMT);
405
406        /**
407         * Date formats with time zone as specified in the HTTP RFC to use for parsing.
408         * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
409         */
410        private static final DateTimeFormatter[] DATE_PARSERS = new DateTimeFormatter[] {
411                        DateTimeFormatter.RFC_1123_DATE_TIME,
412                        DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
413                        DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(GMT)
414        };
415
416
417        final MultiValueMap<String, String> headers;
418
419
420        /**
421         * Construct a new, empty instance of the {@code HttpHeaders} object.
422         * <p>This is the common constructor, using a case-insensitive map structure.
423         */
424        public HttpHeaders() {
425                this(CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)));
426        }
427
428        /**
429         * Construct a new {@code HttpHeaders} instance backed by an existing map.
430         * <p>This constructor is available as an optimization for adapting to existing
431         * headers map structures, primarily for internal use within the framework.
432         * @param headers the headers map (expected to operate with case-insensitive keys)
433         * @since 5.1
434         */
435        public HttpHeaders(MultiValueMap<String, String> headers) {
436                Assert.notNull(headers, "MultiValueMap must not be null");
437                this.headers = headers;
438        }
439
440
441        /**
442         * Get the list of header values for the given header name, if any.
443         * @param headerName the header name
444         * @return the list of header values, or an empty list
445         * @since 5.2
446         */
447        public List<String> getOrEmpty(Object headerName) {
448                List<String> values = get(headerName);
449                return (values != null ? values : Collections.emptyList());
450        }
451
452        /**
453         * Set the list of acceptable {@linkplain MediaType media types},
454         * as specified by the {@code Accept} header.
455         */
456        public void setAccept(List<MediaType> acceptableMediaTypes) {
457                set(ACCEPT, MediaType.toString(acceptableMediaTypes));
458        }
459
460        /**
461         * Return the list of acceptable {@linkplain MediaType media types},
462         * as specified by the {@code Accept} header.
463         * <p>Returns an empty list when the acceptable media types are unspecified.
464         */
465        public List<MediaType> getAccept() {
466                return MediaType.parseMediaTypes(get(ACCEPT));
467        }
468
469        /**
470         * Set the acceptable language ranges, as specified by the
471         * {@literal Accept-Language} header.
472         * @since 5.0
473         */
474        public void setAcceptLanguage(List<Locale.LanguageRange> languages) {
475                Assert.notNull(languages, "LanguageRange List must not be null");
476                DecimalFormat decimal = new DecimalFormat("0.0", DECIMAL_FORMAT_SYMBOLS);
477                List<String> values = languages.stream()
478                                .map(range ->
479                                                range.getWeight() == Locale.LanguageRange.MAX_WEIGHT ?
480                                                                range.getRange() :
481                                                                range.getRange() + ";q=" + decimal.format(range.getWeight()))
482                                .collect(Collectors.toList());
483                set(ACCEPT_LANGUAGE, toCommaDelimitedString(values));
484        }
485
486        /**
487         * Return the language ranges from the {@literal "Accept-Language"} header.
488         * <p>If you only need sorted, preferred locales only use
489         * {@link #getAcceptLanguageAsLocales()} or if you need to filter based on
490         * a list of supported locales you can pass the returned list to
491         * {@link Locale#filter(List, Collection)}.
492         * @throws IllegalArgumentException if the value cannot be converted to a language range
493         * @since 5.0
494         */
495        public List<Locale.LanguageRange> getAcceptLanguage() {
496                String value = getFirst(ACCEPT_LANGUAGE);
497                return (StringUtils.hasText(value) ? Locale.LanguageRange.parse(value) : Collections.emptyList());
498        }
499
500        /**
501         * Variant of {@link #setAcceptLanguage(List)} using {@link Locale}'s.
502         * @since 5.0
503         */
504        public void setAcceptLanguageAsLocales(List<Locale> locales) {
505                setAcceptLanguage(locales.stream()
506                                .map(locale -> new Locale.LanguageRange(locale.toLanguageTag()))
507                                .collect(Collectors.toList()));
508        }
509
510        /**
511         * A variant of {@link #getAcceptLanguage()} that converts each
512         * {@link java.util.Locale.LanguageRange} to a {@link Locale}.
513         * @return the locales or an empty list
514         * @throws IllegalArgumentException if the value cannot be converted to a locale
515         * @since 5.0
516         */
517        public List<Locale> getAcceptLanguageAsLocales() {
518                List<Locale.LanguageRange> ranges = getAcceptLanguage();
519                if (ranges.isEmpty()) {
520                        return Collections.emptyList();
521                }
522                return ranges.stream()
523                                .map(range -> Locale.forLanguageTag(range.getRange()))
524                                .filter(locale -> StringUtils.hasText(locale.getDisplayName()))
525                                .collect(Collectors.toList());
526        }
527
528        /**
529         * Set the (new) value of the {@code Access-Control-Allow-Credentials} response header.
530         */
531        public void setAccessControlAllowCredentials(boolean allowCredentials) {
532                set(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(allowCredentials));
533        }
534
535        /**
536         * Return the value of the {@code Access-Control-Allow-Credentials} response header.
537         */
538        public boolean getAccessControlAllowCredentials() {
539                return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
540        }
541
542        /**
543         * Set the (new) value of the {@code Access-Control-Allow-Headers} response header.
544         */
545        public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
546                set(ACCESS_CONTROL_ALLOW_HEADERS, toCommaDelimitedString(allowedHeaders));
547        }
548
549        /**
550         * Return the value of the {@code Access-Control-Allow-Headers} response header.
551         */
552        public List<String> getAccessControlAllowHeaders() {
553                return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
554        }
555
556        /**
557         * Set the (new) value of the {@code Access-Control-Allow-Methods} response header.
558         */
559        public void setAccessControlAllowMethods(List<HttpMethod> allowedMethods) {
560                set(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.collectionToCommaDelimitedString(allowedMethods));
561        }
562
563        /**
564         * Return the value of the {@code Access-Control-Allow-Methods} response header.
565         */
566        public List<HttpMethod> getAccessControlAllowMethods() {
567                List<HttpMethod> result = new ArrayList<>();
568                String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
569                if (value != null) {
570                        String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
571                        for (String token : tokens) {
572                                HttpMethod resolved = HttpMethod.resolve(token);
573                                if (resolved != null) {
574                                        result.add(resolved);
575                                }
576                        }
577                }
578                return result;
579        }
580
581        /**
582         * Set the (new) value of the {@code Access-Control-Allow-Origin} response header.
583         */
584        public void setAccessControlAllowOrigin(@Nullable String allowedOrigin) {
585                setOrRemove(ACCESS_CONTROL_ALLOW_ORIGIN, allowedOrigin);
586        }
587
588        /**
589         * Return the value of the {@code Access-Control-Allow-Origin} response header.
590         */
591        @Nullable
592        public String getAccessControlAllowOrigin() {
593                return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN);
594        }
595
596        /**
597         * Set the (new) value of the {@code Access-Control-Expose-Headers} response header.
598         */
599        public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
600                set(ACCESS_CONTROL_EXPOSE_HEADERS, toCommaDelimitedString(exposedHeaders));
601        }
602
603        /**
604         * Return the value of the {@code Access-Control-Expose-Headers} response header.
605         */
606        public List<String> getAccessControlExposeHeaders() {
607                return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
608        }
609
610        /**
611         * Set the (new) value of the {@code Access-Control-Max-Age} response header.
612         * @since 5.2
613         */
614        public void setAccessControlMaxAge(Duration maxAge) {
615                set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge.getSeconds()));
616        }
617
618        /**
619         * Set the (new) value of the {@code Access-Control-Max-Age} response header.
620         */
621        public void setAccessControlMaxAge(long maxAge) {
622                set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge));
623        }
624
625        /**
626         * Return the value of the {@code Access-Control-Max-Age} response header.
627         * <p>Returns -1 when the max age is unknown.
628         */
629        public long getAccessControlMaxAge() {
630                String value = getFirst(ACCESS_CONTROL_MAX_AGE);
631                return (value != null ? Long.parseLong(value) : -1);
632        }
633
634        /**
635         * Set the (new) value of the {@code Access-Control-Request-Headers} request header.
636         */
637        public void setAccessControlRequestHeaders(List<String> requestHeaders) {
638                set(ACCESS_CONTROL_REQUEST_HEADERS, toCommaDelimitedString(requestHeaders));
639        }
640
641        /**
642         * Return the value of the {@code Access-Control-Request-Headers} request header.
643         */
644        public List<String> getAccessControlRequestHeaders() {
645                return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);
646        }
647
648        /**
649         * Set the (new) value of the {@code Access-Control-Request-Method} request header.
650         */
651        public void setAccessControlRequestMethod(@Nullable HttpMethod requestMethod) {
652                setOrRemove(ACCESS_CONTROL_REQUEST_METHOD, (requestMethod != null ? requestMethod.name() : null));
653        }
654
655        /**
656         * Return the value of the {@code Access-Control-Request-Method} request header.
657         */
658        @Nullable
659        public HttpMethod getAccessControlRequestMethod() {
660                return HttpMethod.resolve(getFirst(ACCESS_CONTROL_REQUEST_METHOD));
661        }
662
663        /**
664         * Set the list of acceptable {@linkplain Charset charsets},
665         * as specified by the {@code Accept-Charset} header.
666         */
667        public void setAcceptCharset(List<Charset> acceptableCharsets) {
668                StringJoiner joiner = new StringJoiner(", ");
669                for (Charset charset : acceptableCharsets) {
670                        joiner.add(charset.name().toLowerCase(Locale.ENGLISH));
671                }
672                set(ACCEPT_CHARSET, joiner.toString());
673        }
674
675        /**
676         * Return the list of acceptable {@linkplain Charset charsets},
677         * as specified by the {@code Accept-Charset} header.
678         */
679        public List<Charset> getAcceptCharset() {
680                String value = getFirst(ACCEPT_CHARSET);
681                if (value != null) {
682                        String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
683                        List<Charset> result = new ArrayList<>(tokens.length);
684                        for (String token : tokens) {
685                                int paramIdx = token.indexOf(';');
686                                String charsetName;
687                                if (paramIdx == -1) {
688                                        charsetName = token;
689                                }
690                                else {
691                                        charsetName = token.substring(0, paramIdx);
692                                }
693                                if (!charsetName.equals("*")) {
694                                        result.add(Charset.forName(charsetName));
695                                }
696                        }
697                        return result;
698                }
699                else {
700                        return Collections.emptyList();
701                }
702        }
703
704        /**
705         * Set the set of allowed {@link HttpMethod HTTP methods},
706         * as specified by the {@code Allow} header.
707         */
708        public void setAllow(Set<HttpMethod> allowedMethods) {
709                set(ALLOW, StringUtils.collectionToCommaDelimitedString(allowedMethods));
710        }
711
712        /**
713         * Return the set of allowed {@link HttpMethod HTTP methods},
714         * as specified by the {@code Allow} header.
715         * <p>Returns an empty set when the allowed methods are unspecified.
716         */
717        public Set<HttpMethod> getAllow() {
718                String value = getFirst(ALLOW);
719                if (StringUtils.hasLength(value)) {
720                        String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
721                        List<HttpMethod> result = new ArrayList<>(tokens.length);
722                        for (String token : tokens) {
723                                HttpMethod resolved = HttpMethod.resolve(token);
724                                if (resolved != null) {
725                                        result.add(resolved);
726                                }
727                        }
728                        return EnumSet.copyOf(result);
729                }
730                else {
731                        return EnumSet.noneOf(HttpMethod.class);
732                }
733        }
734
735        /**
736         * Set the value of the {@linkplain #AUTHORIZATION Authorization} header to
737         * Basic Authentication based on the given username and password.
738         * <p>Note that this method only supports characters in the
739         * {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set.
740         * @param username the username
741         * @param password the password
742         * @throws IllegalArgumentException if either {@code user} or
743         * {@code password} contain characters that cannot be encoded to ISO-8859-1
744         * @since 5.1
745         * @see #setBasicAuth(String)
746         * @see #setBasicAuth(String, String, Charset)
747         * @see #encodeBasicAuth(String, String, Charset)
748         * @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
749         */
750        public void setBasicAuth(String username, String password) {
751                setBasicAuth(username, password, null);
752        }
753
754        /**
755         * Set the value of the {@linkplain #AUTHORIZATION Authorization} header to
756         * Basic Authentication based on the given username and password.
757         * @param username the username
758         * @param password the password
759         * @param charset the charset to use to convert the credentials into an octet
760         * sequence. Defaults to {@linkplain StandardCharsets#ISO_8859_1 ISO-8859-1}.
761         * @throws IllegalArgumentException if {@code username} or {@code password}
762         * contains characters that cannot be encoded to the given charset
763         * @since 5.1
764         * @see #setBasicAuth(String)
765         * @see #setBasicAuth(String, String)
766         * @see #encodeBasicAuth(String, String, Charset)
767         * @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
768         */
769        public void setBasicAuth(String username, String password, @Nullable Charset charset) {
770                setBasicAuth(encodeBasicAuth(username, password, charset));
771        }
772
773        /**
774         * Set the value of the {@linkplain #AUTHORIZATION Authorization} header to
775         * Basic Authentication based on the given {@linkplain #encodeBasicAuth
776         * encoded credentials}.
777         * <p>Favor this method over {@link #setBasicAuth(String, String)} and
778         * {@link #setBasicAuth(String, String, Charset)} if you wish to cache the
779         * encoded credentials.
780         * @param encodedCredentials the encoded credentials
781         * @throws IllegalArgumentException if supplied credentials string is
782         * {@code null} or blank
783         * @since 5.2
784         * @see #setBasicAuth(String, String)
785         * @see #setBasicAuth(String, String, Charset)
786         * @see #encodeBasicAuth(String, String, Charset)
787         * @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
788         */
789        public void setBasicAuth(String encodedCredentials) {
790                Assert.hasText(encodedCredentials, "'encodedCredentials' must not be null or blank");
791                set(AUTHORIZATION, "Basic " + encodedCredentials);
792        }
793
794        /**
795         * Set the value of the {@linkplain #AUTHORIZATION Authorization} header to
796         * the given Bearer token.
797         * @param token the Base64 encoded token
798         * @since 5.1
799         * @see <a href="https://tools.ietf.org/html/rfc6750">RFC 6750</a>
800         */
801        public void setBearerAuth(String token) {
802                set(AUTHORIZATION, "Bearer " + token);
803        }
804
805        /**
806         * Set a configured {@link CacheControl} instance as the
807         * new value of the {@code Cache-Control} header.
808         * @since 5.0.5
809         */
810        public void setCacheControl(CacheControl cacheControl) {
811                setOrRemove(CACHE_CONTROL, cacheControl.getHeaderValue());
812        }
813
814        /**
815         * Set the (new) value of the {@code Cache-Control} header.
816         */
817        public void setCacheControl(@Nullable String cacheControl) {
818                setOrRemove(CACHE_CONTROL, cacheControl);
819        }
820
821        /**
822         * Return the value of the {@code Cache-Control} header.
823         */
824        @Nullable
825        public String getCacheControl() {
826                return getFieldValues(CACHE_CONTROL);
827        }
828
829        /**
830         * Set the (new) value of the {@code Connection} header.
831         */
832        public void setConnection(String connection) {
833                set(CONNECTION, connection);
834        }
835
836        /**
837         * Set the (new) value of the {@code Connection} header.
838         */
839        public void setConnection(List<String> connection) {
840                set(CONNECTION, toCommaDelimitedString(connection));
841        }
842
843        /**
844         * Return the value of the {@code Connection} header.
845         */
846        public List<String> getConnection() {
847                return getValuesAsList(CONNECTION);
848        }
849
850        /**
851         * Set the {@code Content-Disposition} header when creating a
852         * {@code "multipart/form-data"} request.
853         * <p>Applications typically would not set this header directly but
854         * rather prepare a {@code MultiValueMap<String, Object>}, containing an
855         * Object or a {@link org.springframework.core.io.Resource} for each part,
856         * and then pass that to the {@code RestTemplate} or {@code WebClient}.
857         * @param name the control name
858         * @param filename the filename (may be {@code null})
859         * @see #getContentDisposition()
860         */
861        public void setContentDispositionFormData(String name, @Nullable String filename) {
862                Assert.notNull(name, "Name must not be null");
863                ContentDisposition.Builder disposition = ContentDisposition.builder("form-data").name(name);
864                if (StringUtils.hasText(filename)) {
865                        disposition.filename(filename);
866                }
867                setContentDisposition(disposition.build());
868        }
869
870        /**
871         * Set the {@literal Content-Disposition} header.
872         * <p>This could be used on a response to indicate if the content is
873         * expected to be displayed inline in the browser or as an attachment to be
874         * saved locally.
875         * <p>It can also be used for a {@code "multipart/form-data"} request.
876         * For more details see notes on {@link #setContentDispositionFormData}.
877         * @since 5.0
878         * @see #getContentDisposition()
879         */
880        public void setContentDisposition(ContentDisposition contentDisposition) {
881                set(CONTENT_DISPOSITION, contentDisposition.toString());
882        }
883
884        /**
885         * Return a parsed representation of the {@literal Content-Disposition} header.
886         * @since 5.0
887         * @see #setContentDisposition(ContentDisposition)
888         */
889        public ContentDisposition getContentDisposition() {
890                String contentDisposition = getFirst(CONTENT_DISPOSITION);
891                if (StringUtils.hasText(contentDisposition)) {
892                        return ContentDisposition.parse(contentDisposition);
893                }
894                return ContentDisposition.empty();
895        }
896
897        /**
898         * Set the {@link Locale} of the content language,
899         * as specified by the {@literal Content-Language} header.
900         * <p>Use {@code put(CONTENT_LANGUAGE, list)} if you need
901         * to set multiple content languages.</p>
902         * @since 5.0
903         */
904        public void setContentLanguage(@Nullable Locale locale) {
905                setOrRemove(CONTENT_LANGUAGE, (locale != null ? locale.toLanguageTag() : null));
906        }
907
908        /**
909         * Get the first {@link Locale} of the content languages, as specified by the
910         * {@code Content-Language} header.
911         * <p>Use {@link #getValuesAsList(String)} if you need to get multiple content
912         * languages.
913         * @return the first {@code Locale} of the content languages, or {@code null}
914         * if unknown
915         * @since 5.0
916         */
917        @Nullable
918        public Locale getContentLanguage() {
919                return getValuesAsList(CONTENT_LANGUAGE)
920                                .stream()
921                                .findFirst()
922                                .map(Locale::forLanguageTag)
923                                .orElse(null);
924        }
925
926        /**
927         * Set the length of the body in bytes, as specified by the
928         * {@code Content-Length} header.
929         */
930        public void setContentLength(long contentLength) {
931                set(CONTENT_LENGTH, Long.toString(contentLength));
932        }
933
934        /**
935         * Return the length of the body in bytes, as specified by the
936         * {@code Content-Length} header.
937         * <p>Returns -1 when the content-length is unknown.
938         */
939        public long getContentLength() {
940                String value = getFirst(CONTENT_LENGTH);
941                return (value != null ? Long.parseLong(value) : -1);
942        }
943
944        /**
945         * Set the {@linkplain MediaType media type} of the body,
946         * as specified by the {@code Content-Type} header.
947         */
948        public void setContentType(@Nullable MediaType mediaType) {
949                if (mediaType != null) {
950                        Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'");
951                        Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'");
952                        set(CONTENT_TYPE, mediaType.toString());
953                }
954                else {
955                        remove(CONTENT_TYPE);
956                }
957        }
958
959        /**
960         * Return the {@linkplain MediaType media type} of the body, as specified
961         * by the {@code Content-Type} header.
962         * <p>Returns {@code null} when the content-type is unknown.
963         */
964        @Nullable
965        public MediaType getContentType() {
966                String value = getFirst(CONTENT_TYPE);
967                return (StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null);
968        }
969
970        /**
971         * Set the date and time at which the message was created, as specified
972         * by the {@code Date} header.
973         * @since 5.2
974         */
975        public void setDate(ZonedDateTime date) {
976                setZonedDateTime(DATE, date);
977        }
978
979        /**
980         * Set the date and time at which the message was created, as specified
981         * by the {@code Date} header.
982         * @since 5.2
983         */
984        public void setDate(Instant date) {
985                setInstant(DATE, date);
986        }
987
988        /**
989         * Set the date and time at which the message was created, as specified
990         * by the {@code Date} header.
991         * <p>The date should be specified as the number of milliseconds since
992         * January 1, 1970 GMT.
993         */
994        public void setDate(long date) {
995                setDate(DATE, date);
996        }
997
998        /**
999         * Return the date and time at which the message was created, as specified
1000         * by the {@code Date} header.
1001         * <p>The date is returned as the number of milliseconds since
1002         * January 1, 1970 GMT. Returns -1 when the date is unknown.
1003         * @throws IllegalArgumentException if the value cannot be converted to a date
1004         */
1005        public long getDate() {
1006                return getFirstDate(DATE);
1007        }
1008
1009        /**
1010         * Set the (new) entity tag of the body, as specified by the {@code ETag} header.
1011         */
1012        public void setETag(@Nullable String etag) {
1013                if (etag != null) {
1014                        Assert.isTrue(etag.startsWith("\"") || etag.startsWith("W/"),
1015                                        "Invalid ETag: does not start with W/ or \"");
1016                        Assert.isTrue(etag.endsWith("\""), "Invalid ETag: does not end with \"");
1017                        set(ETAG, etag);
1018                }
1019                else {
1020                        remove(ETAG);
1021                }
1022        }
1023
1024        /**
1025         * Return the entity tag of the body, as specified by the {@code ETag} header.
1026         */
1027        @Nullable
1028        public String getETag() {
1029                return getFirst(ETAG);
1030        }
1031
1032        /**
1033         * Set the duration after which the message is no longer valid,
1034         * as specified by the {@code Expires} header.
1035         * @since 5.0.5
1036         */
1037        public void setExpires(ZonedDateTime expires) {
1038                setZonedDateTime(EXPIRES, expires);
1039        }
1040
1041        /**
1042         * Set the date and time at which the message is no longer valid,
1043         * as specified by the {@code Expires} header.
1044         * @since 5.2
1045         */
1046        public void setExpires(Instant expires) {
1047                setInstant(EXPIRES, expires);
1048        }
1049
1050        /**
1051         * Set the date and time at which the message is no longer valid,
1052         * as specified by the {@code Expires} header.
1053         * <p>The date should be specified as the number of milliseconds since
1054         * January 1, 1970 GMT.
1055         */
1056        public void setExpires(long expires) {
1057                setDate(EXPIRES, expires);
1058        }
1059
1060        /**
1061         * Return the date and time at which the message is no longer valid,
1062         * as specified by the {@code Expires} header.
1063         * <p>The date is returned as the number of milliseconds since
1064         * January 1, 1970 GMT. Returns -1 when the date is unknown.
1065         * @see #getFirstZonedDateTime(String)
1066         */
1067        public long getExpires() {
1068                return getFirstDate(EXPIRES, false);
1069        }
1070
1071        /**
1072         * Set the (new) value of the {@code Host} header.
1073         * <p>If the given {@linkplain InetSocketAddress#getPort() port} is {@code 0},
1074         * the host header will only contain the
1075         * {@linkplain InetSocketAddress#getHostString() host name}.
1076         * @since 5.0
1077         */
1078        public void setHost(@Nullable InetSocketAddress host) {
1079                if (host != null) {
1080                        String value = host.getHostString();
1081                        int port = host.getPort();
1082                        if (port != 0) {
1083                                value = value + ":" + port;
1084                        }
1085                        set(HOST, value);
1086                }
1087                else {
1088                        remove(HOST, null);
1089                }
1090        }
1091
1092        /**
1093         * Return the value of the {@code Host} header, if available.
1094         * <p>If the header value does not contain a port, the
1095         * {@linkplain InetSocketAddress#getPort() port} in the returned address will
1096         * be {@code 0}.
1097         * @since 5.0
1098         */
1099        @Nullable
1100        public InetSocketAddress getHost() {
1101                String value = getFirst(HOST);
1102                if (value == null) {
1103                        return null;
1104                }
1105
1106                String host = null;
1107                int port = 0;
1108                int separator = (value.startsWith("[") ? value.indexOf(':', value.indexOf(']')) : value.lastIndexOf(':'));
1109                if (separator != -1) {
1110                        host = value.substring(0, separator);
1111                        String portString = value.substring(separator + 1);
1112                        try {
1113                                port = Integer.parseInt(portString);
1114                        }
1115                        catch (NumberFormatException ex) {
1116                                // ignore
1117                        }
1118                }
1119
1120                if (host == null) {
1121                        host = value;
1122                }
1123                return InetSocketAddress.createUnresolved(host, port);
1124        }
1125
1126        /**
1127         * Set the (new) value of the {@code If-Match} header.
1128         * @since 4.3
1129         */
1130        public void setIfMatch(String ifMatch) {
1131                set(IF_MATCH, ifMatch);
1132        }
1133
1134        /**
1135         * Set the (new) value of the {@code If-Match} header.
1136         * @since 4.3
1137         */
1138        public void setIfMatch(List<String> ifMatchList) {
1139                set(IF_MATCH, toCommaDelimitedString(ifMatchList));
1140        }
1141
1142        /**
1143         * Return the value of the {@code If-Match} header.
1144         * @throws IllegalArgumentException if parsing fails
1145         * @since 4.3
1146         */
1147        public List<String> getIfMatch() {
1148                return getETagValuesAsList(IF_MATCH);
1149        }
1150
1151        /**
1152         * Set the time the resource was last changed, as specified by the
1153         * {@code Last-Modified} header.
1154         * @since 5.1.4
1155         */
1156        public void setIfModifiedSince(ZonedDateTime ifModifiedSince) {
1157                setZonedDateTime(IF_MODIFIED_SINCE, ifModifiedSince.withZoneSameInstant(GMT));
1158        }
1159
1160        /**
1161         * Set the time the resource was last changed, as specified by the
1162         * {@code Last-Modified} header.
1163         * @since 5.1.4
1164         */
1165        public void setIfModifiedSince(Instant ifModifiedSince) {
1166                setInstant(IF_MODIFIED_SINCE, ifModifiedSince);
1167        }
1168
1169        /**
1170         * Set the (new) value of the {@code If-Modified-Since} header.
1171         * <p>The date should be specified as the number of milliseconds since
1172         * January 1, 1970 GMT.
1173         */
1174        public void setIfModifiedSince(long ifModifiedSince) {
1175                setDate(IF_MODIFIED_SINCE, ifModifiedSince);
1176        }
1177
1178        /**
1179         * Return the value of the {@code If-Modified-Since} header.
1180         * <p>The date is returned as the number of milliseconds since
1181         * January 1, 1970 GMT. Returns -1 when the date is unknown.
1182         * @see #getFirstZonedDateTime(String)
1183         */
1184        public long getIfModifiedSince() {
1185                return getFirstDate(IF_MODIFIED_SINCE, false);
1186        }
1187
1188        /**
1189         * Set the (new) value of the {@code If-None-Match} header.
1190         */
1191        public void setIfNoneMatch(String ifNoneMatch) {
1192                set(IF_NONE_MATCH, ifNoneMatch);
1193        }
1194
1195        /**
1196         * Set the (new) values of the {@code If-None-Match} header.
1197         */
1198        public void setIfNoneMatch(List<String> ifNoneMatchList) {
1199                set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList));
1200        }
1201
1202        /**
1203         * Return the value of the {@code If-None-Match} header.
1204         * @throws IllegalArgumentException if parsing fails
1205         */
1206        public List<String> getIfNoneMatch() {
1207                return getETagValuesAsList(IF_NONE_MATCH);
1208        }
1209
1210        /**
1211         * Set the time the resource was last changed, as specified by the
1212         * {@code Last-Modified} header.
1213         * @since 5.1.4
1214         */
1215        public void setIfUnmodifiedSince(ZonedDateTime ifUnmodifiedSince) {
1216                setZonedDateTime(IF_UNMODIFIED_SINCE, ifUnmodifiedSince.withZoneSameInstant(GMT));
1217        }
1218
1219        /**
1220         * Set the time the resource was last changed, as specified by the
1221         * {@code Last-Modified} header.
1222         * @since 5.1.4
1223         */
1224        public void setIfUnmodifiedSince(Instant ifUnmodifiedSince) {
1225                setInstant(IF_UNMODIFIED_SINCE, ifUnmodifiedSince);
1226        }
1227
1228        /**
1229         * Set the (new) value of the {@code If-Unmodified-Since} header.
1230         * <p>The date should be specified as the number of milliseconds since
1231         * January 1, 1970 GMT.
1232         * @since 4.3
1233         */
1234        public void setIfUnmodifiedSince(long ifUnmodifiedSince) {
1235                setDate(IF_UNMODIFIED_SINCE, ifUnmodifiedSince);
1236        }
1237
1238        /**
1239         * Return the value of the {@code If-Unmodified-Since} header.
1240         * <p>The date is returned as the number of milliseconds since
1241         * January 1, 1970 GMT. Returns -1 when the date is unknown.
1242         * @since 4.3
1243         * @see #getFirstZonedDateTime(String)
1244         */
1245        public long getIfUnmodifiedSince() {
1246                return getFirstDate(IF_UNMODIFIED_SINCE, false);
1247        }
1248
1249        /**
1250         * Set the time the resource was last changed, as specified by the
1251         * {@code Last-Modified} header.
1252         * @since 5.1.4
1253         */
1254        public void setLastModified(ZonedDateTime lastModified) {
1255                setZonedDateTime(LAST_MODIFIED, lastModified.withZoneSameInstant(GMT));
1256        }
1257
1258        /**
1259         * Set the time the resource was last changed, as specified by the
1260         * {@code Last-Modified} header.
1261         * @since 5.1.4
1262         */
1263        public void setLastModified(Instant lastModified) {
1264                setInstant(LAST_MODIFIED, lastModified);
1265        }
1266
1267        /**
1268         * Set the time the resource was last changed, as specified by the
1269         * {@code Last-Modified} header.
1270         * <p>The date should be specified as the number of milliseconds since
1271         * January 1, 1970 GMT.
1272         */
1273        public void setLastModified(long lastModified) {
1274                setDate(LAST_MODIFIED, lastModified);
1275        }
1276
1277        /**
1278         * Return the time the resource was last changed, as specified by the
1279         * {@code Last-Modified} header.
1280         * <p>The date is returned as the number of milliseconds since
1281         * January 1, 1970 GMT. Returns -1 when the date is unknown.
1282         * @see #getFirstZonedDateTime(String)
1283         */
1284        public long getLastModified() {
1285                return getFirstDate(LAST_MODIFIED, false);
1286        }
1287
1288        /**
1289         * Set the (new) location of a resource,
1290         * as specified by the {@code Location} header.
1291         */
1292        public void setLocation(@Nullable URI location) {
1293                setOrRemove(LOCATION, (location != null ? location.toASCIIString() : null));
1294        }
1295
1296        /**
1297         * Return the (new) location of a resource
1298         * as specified by the {@code Location} header.
1299         * <p>Returns {@code null} when the location is unknown.
1300         */
1301        @Nullable
1302        public URI getLocation() {
1303                String value = getFirst(LOCATION);
1304                return (value != null ? URI.create(value) : null);
1305        }
1306
1307        /**
1308         * Set the (new) value of the {@code Origin} header.
1309         */
1310        public void setOrigin(@Nullable String origin) {
1311                setOrRemove(ORIGIN, origin);
1312        }
1313
1314        /**
1315         * Return the value of the {@code Origin} header.
1316         */
1317        @Nullable
1318        public String getOrigin() {
1319                return getFirst(ORIGIN);
1320        }
1321
1322        /**
1323         * Set the (new) value of the {@code Pragma} header.
1324         */
1325        public void setPragma(@Nullable String pragma) {
1326                setOrRemove(PRAGMA, pragma);
1327        }
1328
1329        /**
1330         * Return the value of the {@code Pragma} header.
1331         */
1332        @Nullable
1333        public String getPragma() {
1334                return getFirst(PRAGMA);
1335        }
1336
1337        /**
1338         * Sets the (new) value of the {@code Range} header.
1339         */
1340        public void setRange(List<HttpRange> ranges) {
1341                String value = HttpRange.toString(ranges);
1342                set(RANGE, value);
1343        }
1344
1345        /**
1346         * Return the value of the {@code Range} header.
1347         * <p>Returns an empty list when the range is unknown.
1348         */
1349        public List<HttpRange> getRange() {
1350                String value = getFirst(RANGE);
1351                return HttpRange.parseRanges(value);
1352        }
1353
1354        /**
1355         * Set the (new) value of the {@code Upgrade} header.
1356         */
1357        public void setUpgrade(@Nullable String upgrade) {
1358                setOrRemove(UPGRADE, upgrade);
1359        }
1360
1361        /**
1362         * Return the value of the {@code Upgrade} header.
1363         */
1364        @Nullable
1365        public String getUpgrade() {
1366                return getFirst(UPGRADE);
1367        }
1368
1369        /**
1370         * Set the request header names (e.g. "Accept-Language") for which the
1371         * response is subject to content negotiation and variances based on the
1372         * value of those request headers.
1373         * @param requestHeaders the request header names
1374         * @since 4.3
1375         */
1376        public void setVary(List<String> requestHeaders) {
1377                set(VARY, toCommaDelimitedString(requestHeaders));
1378        }
1379
1380        /**
1381         * Return the request header names subject to content negotiation.
1382         * @since 4.3
1383         */
1384        public List<String> getVary() {
1385                return getValuesAsList(VARY);
1386        }
1387
1388        /**
1389         * Set the given date under the given header name after formatting it as a string
1390         * using the RFC-1123 date-time formatter. The equivalent of
1391         * {@link #set(String, String)} but for date headers.
1392         * @since 5.0
1393         */
1394        public void setZonedDateTime(String headerName, ZonedDateTime date) {
1395                set(headerName, DATE_FORMATTER.format(date));
1396        }
1397
1398        /**
1399         * Set the given date under the given header name after formatting it as a string
1400         * using the RFC-1123 date-time formatter. The equivalent of
1401         * {@link #set(String, String)} but for date headers.
1402         * @since 5.1.4
1403         */
1404        public void setInstant(String headerName, Instant date) {
1405                setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT));
1406        }
1407
1408        /**
1409         * Set the given date under the given header name after formatting it as a string
1410         * using the RFC-1123 date-time formatter. The equivalent of
1411         * {@link #set(String, String)} but for date headers.
1412         * @since 3.2.4
1413         * @see #setZonedDateTime(String, ZonedDateTime)
1414         */
1415        public void setDate(String headerName, long date) {
1416                setInstant(headerName, Instant.ofEpochMilli(date));
1417        }
1418
1419        /**
1420         * Parse the first header value for the given header name as a date,
1421         * return -1 if there is no value, or raise {@link IllegalArgumentException}
1422         * if the value cannot be parsed as a date.
1423         * @param headerName the header name
1424         * @return the parsed date header, or -1 if none
1425         * @since 3.2.4
1426         * @see #getFirstZonedDateTime(String)
1427         */
1428        public long getFirstDate(String headerName) {
1429                return getFirstDate(headerName, true);
1430        }
1431
1432        /**
1433         * Parse the first header value for the given header name as a date,
1434         * return -1 if there is no value or also in case of an invalid value
1435         * (if {@code rejectInvalid=false}), or raise {@link IllegalArgumentException}
1436         * if the value cannot be parsed as a date.
1437         * @param headerName the header name
1438         * @param rejectInvalid whether to reject invalid values with an
1439         * {@link IllegalArgumentException} ({@code true}) or rather return -1
1440         * in that case ({@code false})
1441         * @return the parsed date header, or -1 if none (or invalid)
1442         * @see #getFirstZonedDateTime(String, boolean)
1443         */
1444        private long getFirstDate(String headerName, boolean rejectInvalid) {
1445                ZonedDateTime zonedDateTime = getFirstZonedDateTime(headerName, rejectInvalid);
1446                return (zonedDateTime != null ? zonedDateTime.toInstant().toEpochMilli() : -1);
1447        }
1448
1449        /**
1450         * Parse the first header value for the given header name as a date,
1451         * return {@code null} if there is no value, or raise {@link IllegalArgumentException}
1452         * if the value cannot be parsed as a date.
1453         * @param headerName the header name
1454         * @return the parsed date header, or {@code null} if none
1455         * @since 5.0
1456         */
1457        @Nullable
1458        public ZonedDateTime getFirstZonedDateTime(String headerName) {
1459                return getFirstZonedDateTime(headerName, true);
1460        }
1461
1462        /**
1463         * Parse the first header value for the given header name as a date,
1464         * return {@code null} if there is no value or also in case of an invalid value
1465         * (if {@code rejectInvalid=false}), or raise {@link IllegalArgumentException}
1466         * if the value cannot be parsed as a date.
1467         * @param headerName the header name
1468         * @param rejectInvalid whether to reject invalid values with an
1469         * {@link IllegalArgumentException} ({@code true}) or rather return {@code null}
1470         * in that case ({@code false})
1471         * @return the parsed date header, or {@code null} if none (or invalid)
1472         */
1473        @Nullable
1474        private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid) {
1475                String headerValue = getFirst(headerName);
1476                if (headerValue == null) {
1477                        // No header value sent at all
1478                        return null;
1479                }
1480                if (headerValue.length() >= 3) {
1481                        // Short "0" or "-1" like values are never valid HTTP date headers...
1482                        // Let's only bother with DateTimeFormatter parsing for long enough values.
1483
1484                        // See https://stackoverflow.com/questions/12626699/if-modified-since-http-header-passed-by-ie9-includes-length
1485                        int parametersIndex = headerValue.indexOf(';');
1486                        if (parametersIndex != -1) {
1487                                headerValue = headerValue.substring(0, parametersIndex);
1488                        }
1489
1490                        for (DateTimeFormatter dateFormatter : DATE_PARSERS) {
1491                                try {
1492                                        return ZonedDateTime.parse(headerValue, dateFormatter);
1493                                }
1494                                catch (DateTimeParseException ex) {
1495                                        // ignore
1496                                }
1497                        }
1498
1499                }
1500                if (rejectInvalid) {
1501                        throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
1502                                        "\" for \"" + headerName + "\" header");
1503                }
1504                return null;
1505        }
1506
1507        /**
1508         * Return all values of a given header name,
1509         * even if this header is set multiple times.
1510         * @param headerName the header name
1511         * @return all associated values
1512         * @since 4.3
1513         */
1514        public List<String> getValuesAsList(String headerName) {
1515                List<String> values = get(headerName);
1516                if (values != null) {
1517                        List<String> result = new ArrayList<>();
1518                        for (String value : values) {
1519                                if (value != null) {
1520                                        Collections.addAll(result, StringUtils.tokenizeToStringArray(value, ","));
1521                                }
1522                        }
1523                        return result;
1524                }
1525                return Collections.emptyList();
1526        }
1527
1528        /**
1529         * Remove the well-known {@code "Content-*"} HTTP headers.
1530         * <p>Such headers should be cleared from the response if the intended
1531         * body can't be written due to errors.
1532         * @since 5.2.3
1533         */
1534        public void clearContentHeaders() {
1535                this.headers.remove(HttpHeaders.CONTENT_DISPOSITION);
1536                this.headers.remove(HttpHeaders.CONTENT_ENCODING);
1537                this.headers.remove(HttpHeaders.CONTENT_LANGUAGE);
1538                this.headers.remove(HttpHeaders.CONTENT_LENGTH);
1539                this.headers.remove(HttpHeaders.CONTENT_LOCATION);
1540                this.headers.remove(HttpHeaders.CONTENT_RANGE);
1541                this.headers.remove(HttpHeaders.CONTENT_TYPE);
1542        }
1543
1544        /**
1545         * Retrieve a combined result from the field values of the ETag header.
1546         * @param headerName the header name
1547         * @return the combined result
1548         * @throws IllegalArgumentException if parsing fails
1549         * @since 4.3
1550         */
1551        protected List<String> getETagValuesAsList(String headerName) {
1552                List<String> values = get(headerName);
1553                if (values != null) {
1554                        List<String> result = new ArrayList<>();
1555                        for (String value : values) {
1556                                if (value != null) {
1557                                        Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value);
1558                                        while (matcher.find()) {
1559                                                if ("*".equals(matcher.group())) {
1560                                                        result.add(matcher.group());
1561                                                }
1562                                                else {
1563                                                        result.add(matcher.group(1));
1564                                                }
1565                                        }
1566                                        if (result.isEmpty()) {
1567                                                throw new IllegalArgumentException(
1568                                                                "Could not parse header '" + headerName + "' with value '" + value + "'");
1569                                        }
1570                                }
1571                        }
1572                        return result;
1573                }
1574                return Collections.emptyList();
1575        }
1576
1577        /**
1578         * Retrieve a combined result from the field values of multi-valued headers.
1579         * @param headerName the header name
1580         * @return the combined result
1581         * @since 4.3
1582         */
1583        @Nullable
1584        protected String getFieldValues(String headerName) {
1585                List<String> headerValues = get(headerName);
1586                return (headerValues != null ? toCommaDelimitedString(headerValues) : null);
1587        }
1588
1589        /**
1590         * Turn the given list of header values into a comma-delimited result.
1591         * @param headerValues the list of header values
1592         * @return a combined result with comma delimitation
1593         */
1594        protected String toCommaDelimitedString(List<String> headerValues) {
1595                StringJoiner joiner = new StringJoiner(", ");
1596                for (String val : headerValues) {
1597                        if (val != null) {
1598                                joiner.add(val);
1599                        }
1600                }
1601                return joiner.toString();
1602        }
1603
1604        /**
1605         * Set the given header value, or remove the header if {@code null}.
1606         * @param headerName the header name
1607         * @param headerValue the header value, or {@code null} for none
1608         */
1609        private void setOrRemove(String headerName, @Nullable String headerValue) {
1610                if (headerValue != null) {
1611                        set(headerName, headerValue);
1612                }
1613                else {
1614                        remove(headerName);
1615                }
1616        }
1617
1618
1619        // MultiValueMap implementation
1620
1621        /**
1622         * Return the first header value for the given header name, if any.
1623         * @param headerName the header name
1624         * @return the first header value, or {@code null} if none
1625         */
1626        @Override
1627        @Nullable
1628        public String getFirst(String headerName) {
1629                return this.headers.getFirst(headerName);
1630        }
1631
1632        /**
1633         * Add the given, single header value under the given name.
1634         * @param headerName the header name
1635         * @param headerValue the header value
1636         * @throws UnsupportedOperationException if adding headers is not supported
1637         * @see #put(String, List)
1638         * @see #set(String, String)
1639         */
1640        @Override
1641        public void add(String headerName, @Nullable String headerValue) {
1642                this.headers.add(headerName, headerValue);
1643        }
1644
1645        @Override
1646        public void addAll(String key, List<? extends String> values) {
1647                this.headers.addAll(key, values);
1648        }
1649
1650        @Override
1651        public void addAll(MultiValueMap<String, String> values) {
1652                this.headers.addAll(values);
1653        }
1654
1655        /**
1656         * Set the given, single header value under the given name.
1657         * @param headerName the header name
1658         * @param headerValue the header value
1659         * @throws UnsupportedOperationException if adding headers is not supported
1660         * @see #put(String, List)
1661         * @see #add(String, String)
1662         */
1663        @Override
1664        public void set(String headerName, @Nullable String headerValue) {
1665                this.headers.set(headerName, headerValue);
1666        }
1667
1668        @Override
1669        public void setAll(Map<String, String> values) {
1670                this.headers.setAll(values);
1671        }
1672
1673        @Override
1674        public Map<String, String> toSingleValueMap() {
1675                return this.headers.toSingleValueMap();
1676        }
1677
1678
1679        // Map implementation
1680
1681        @Override
1682        public int size() {
1683                return this.headers.size();
1684        }
1685
1686        @Override
1687        public boolean isEmpty() {
1688                return this.headers.isEmpty();
1689        }
1690
1691        @Override
1692        public boolean containsKey(Object key) {
1693                return this.headers.containsKey(key);
1694        }
1695
1696        @Override
1697        public boolean containsValue(Object value) {
1698                return this.headers.containsValue(value);
1699        }
1700
1701        @Override
1702        @Nullable
1703        public List<String> get(Object key) {
1704                return this.headers.get(key);
1705        }
1706
1707        @Override
1708        public List<String> put(String key, List<String> value) {
1709                return this.headers.put(key, value);
1710        }
1711
1712        @Override
1713        public List<String> remove(Object key) {
1714                return this.headers.remove(key);
1715        }
1716
1717        @Override
1718        public void putAll(Map<? extends String, ? extends List<String>> map) {
1719                this.headers.putAll(map);
1720        }
1721
1722        @Override
1723        public void clear() {
1724                this.headers.clear();
1725        }
1726
1727        @Override
1728        public Set<String> keySet() {
1729                return this.headers.keySet();
1730        }
1731
1732        @Override
1733        public Collection<List<String>> values() {
1734                return this.headers.values();
1735        }
1736
1737        @Override
1738        public Set<Entry<String, List<String>>> entrySet() {
1739                return this.headers.entrySet();
1740        }
1741
1742
1743        @Override
1744        public boolean equals(@Nullable Object other) {
1745                if (this == other) {
1746                        return true;
1747                }
1748                if (!(other instanceof HttpHeaders)) {
1749                        return false;
1750                }
1751                return unwrap(this).equals(unwrap((HttpHeaders) other));
1752        }
1753
1754        private static MultiValueMap<String, String> unwrap(HttpHeaders headers) {
1755                while (headers.headers instanceof HttpHeaders) {
1756                        headers = (HttpHeaders) headers.headers;
1757                }
1758                return headers.headers;
1759        }
1760
1761        @Override
1762        public int hashCode() {
1763                return this.headers.hashCode();
1764        }
1765
1766        @Override
1767        public String toString() {
1768                return formatHeaders(this.headers);
1769        }
1770
1771
1772        /**
1773         * Apply a read-only {@code HttpHeaders} wrapper around the given headers,
1774         * if necessary.
1775         * @param headers the headers to expose
1776         * @return a read-only variant of the headers, or the original headers as-is
1777         */
1778        public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
1779                Assert.notNull(headers, "HttpHeaders must not be null");
1780                return (headers instanceof ReadOnlyHttpHeaders ? headers : new ReadOnlyHttpHeaders(headers.headers));
1781        }
1782
1783        /**
1784         * Remove any read-only wrapper that may have been previously applied around
1785         * the given headers via {@link #readOnlyHttpHeaders(HttpHeaders)}.
1786         * @param headers the headers to expose
1787         * @return a writable variant of the headers, or the original headers as-is
1788         * @since 5.1.1
1789         */
1790        public static HttpHeaders writableHttpHeaders(HttpHeaders headers) {
1791                Assert.notNull(headers, "HttpHeaders must not be null");
1792                if (headers == EMPTY) {
1793                        return new HttpHeaders();
1794                }
1795                return (headers instanceof ReadOnlyHttpHeaders ? new HttpHeaders(headers.headers) : headers);
1796        }
1797
1798        /**
1799         * Helps to format HTTP header values, as HTTP header values themselves can
1800         * contain comma-separated values, can become confusing with regular
1801         * {@link Map} formatting that also uses commas between entries.
1802         * @param headers the headers to format
1803         * @return the headers to a String
1804         * @since 5.1.4
1805         */
1806        public static String formatHeaders(MultiValueMap<String, String> headers) {
1807                return headers.entrySet().stream()
1808                                .map(entry -> {
1809                                        List<String> values = entry.getValue();
1810                                        return entry.getKey() + ":" + (values.size() == 1 ?
1811                                                        "\"" + values.get(0) + "\"" :
1812                                                        values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
1813                                })
1814                                .collect(Collectors.joining(", ", "[", "]"));
1815        }
1816
1817        /**
1818         * Encode the given username and password into Basic Authentication credentials.
1819         * <p>The encoded credentials returned by this method can be supplied to
1820         * {@link #setBasicAuth(String)} to set the Basic Authentication header.
1821         * @param username the username
1822         * @param password the password
1823         * @param charset the charset to use to convert the credentials into an octet
1824         * sequence. Defaults to {@linkplain StandardCharsets#ISO_8859_1 ISO-8859-1}.
1825         * @throws IllegalArgumentException if {@code username} or {@code password}
1826         * contains characters that cannot be encoded to the given charset
1827         * @since 5.2
1828         * @see #setBasicAuth(String)
1829         * @see #setBasicAuth(String, String)
1830         * @see #setBasicAuth(String, String, Charset)
1831         * @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
1832         */
1833        public static String encodeBasicAuth(String username, String password, @Nullable Charset charset) {
1834                Assert.notNull(username, "Username must not be null");
1835                Assert.doesNotContain(username, ":", "Username must not contain a colon");
1836                Assert.notNull(password, "Password must not be null");
1837                if (charset == null) {
1838                        charset = StandardCharsets.ISO_8859_1;
1839                }
1840
1841                CharsetEncoder encoder = charset.newEncoder();
1842                if (!encoder.canEncode(username) || !encoder.canEncode(password)) {
1843                        throw new IllegalArgumentException(
1844                                        "Username or password contains characters that cannot be encoded to " + charset.displayName());
1845                }
1846
1847                String credentialsString = username + ":" + password;
1848                byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
1849                return new String(encodedBytes, charset);
1850        }
1851
1852        // Package-private: used in ResponseCookie
1853        static String formatDate(long date) {
1854                Instant instant = Instant.ofEpochMilli(date);
1855                ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT);
1856                return DATE_FORMATTER.format(time);
1857        }
1858
1859}