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}