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.URI; 021import java.nio.charset.Charset; 022import java.text.ParseException; 023import java.text.SimpleDateFormat; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Date; 028import java.util.EnumSet; 029import java.util.Iterator; 030import java.util.LinkedHashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Locale; 034import java.util.Map; 035import java.util.Set; 036import java.util.TimeZone; 037import java.util.regex.Matcher; 038import java.util.regex.Pattern; 039 040import org.springframework.util.Assert; 041import org.springframework.util.LinkedCaseInsensitiveMap; 042import org.springframework.util.MultiValueMap; 043import org.springframework.util.StringUtils; 044 045/** 046 * A data structure representing HTTP request or response headers, mapping String header names 047 * to a list of String values, also offering accessors for common application-level data types. 048 * 049 * <p>In addition to the regular methods defined by {@link Map}, this class offers many common 050 * convenience methods, for example: 051 * <ul> 052 * <li>{@link #getFirst(String)} returns the first value associated with a given header name</li> 053 * <li>{@link #add(String, String)} adds a header value to the list of values for a header name</li> 054 * <li>{@link #set(String, String)} sets the header value to a single string value</li> 055 * </ul> 056 * 057 * <p>Note that {@code HttpHeaders} generally treats header names in a case-insensitive manner. 058 * 059 * @author Arjen Poutsma 060 * @author Sebastien Deleuze 061 * @author Brian Clozel 062 * @author Juergen Hoeller 063 * @author Josh Long 064 * @since 3.0 065 */ 066public class HttpHeaders implements MultiValueMap<String, String>, Serializable { 067 068 private static final long serialVersionUID = -8578554704772377436L; 069 070 071 /** 072 * The HTTP {@code Accept} header field name. 073 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a> 074 */ 075 public static final String ACCEPT = "Accept"; 076 /** 077 * The HTTP {@code Accept-Charset} header field name. 078 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a> 079 */ 080 public static final String ACCEPT_CHARSET = "Accept-Charset"; 081 /** 082 * The HTTP {@code Accept-Encoding} header field name. 083 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a> 084 */ 085 public static final String ACCEPT_ENCODING = "Accept-Encoding"; 086 /** 087 * The HTTP {@code Accept-Language} header field name. 088 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a> 089 */ 090 public static final String ACCEPT_LANGUAGE = "Accept-Language"; 091 /** 092 * The HTTP {@code Accept-Ranges} header field name. 093 * @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a> 094 */ 095 public static final String ACCEPT_RANGES = "Accept-Ranges"; 096 /** 097 * The CORS {@code Access-Control-Allow-Credentials} response header field name. 098 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 099 */ 100 public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; 101 /** 102 * The CORS {@code Access-Control-Allow-Headers} response header field name. 103 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 104 */ 105 public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; 106 /** 107 * The CORS {@code Access-Control-Allow-Methods} response header field name. 108 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 109 */ 110 public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; 111 /** 112 * The CORS {@code Access-Control-Allow-Origin} response header field name. 113 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 114 */ 115 public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; 116 /** 117 * The CORS {@code Access-Control-Expose-Headers} response header field name. 118 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 119 */ 120 public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; 121 /** 122 * The CORS {@code Access-Control-Max-Age} response header field name. 123 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 124 */ 125 public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; 126 /** 127 * The CORS {@code Access-Control-Request-Headers} request header field name. 128 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 129 */ 130 public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; 131 /** 132 * The CORS {@code Access-Control-Request-Method} request header field name. 133 * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a> 134 */ 135 public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; 136 /** 137 * The HTTP {@code Age} header field name. 138 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a> 139 */ 140 public static final String AGE = "Age"; 141 /** 142 * The HTTP {@code Allow} header field name. 143 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a> 144 */ 145 public static final String ALLOW = "Allow"; 146 /** 147 * The HTTP {@code Authorization} header field name. 148 * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a> 149 */ 150 public static final String AUTHORIZATION = "Authorization"; 151 /** 152 * The HTTP {@code Cache-Control} header field name. 153 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a> 154 */ 155 public static final String CACHE_CONTROL = "Cache-Control"; 156 /** 157 * The HTTP {@code Connection} header field name. 158 * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a> 159 */ 160 public static final String CONNECTION = "Connection"; 161 /** 162 * The HTTP {@code Content-Encoding} header field name. 163 * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a> 164 */ 165 public static final String CONTENT_ENCODING = "Content-Encoding"; 166 /** 167 * The HTTP {@code Content-Disposition} header field name. 168 * @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a> 169 */ 170 public static final String CONTENT_DISPOSITION = "Content-Disposition"; 171 /** 172 * The HTTP {@code Content-Language} header field name. 173 * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a> 174 */ 175 public static final String CONTENT_LANGUAGE = "Content-Language"; 176 /** 177 * The HTTP {@code Content-Length} header field name. 178 * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a> 179 */ 180 public static final String CONTENT_LENGTH = "Content-Length"; 181 /** 182 * The HTTP {@code Content-Location} header field name. 183 * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a> 184 */ 185 public static final String CONTENT_LOCATION = "Content-Location"; 186 /** 187 * The HTTP {@code Content-Range} header field name. 188 * @see <a href="https://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a> 189 */ 190 public static final String CONTENT_RANGE = "Content-Range"; 191 /** 192 * The HTTP {@code Content-Type} header field name. 193 * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a> 194 */ 195 public static final String CONTENT_TYPE = "Content-Type"; 196 /** 197 * The HTTP {@code Cookie} header field name. 198 * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a> 199 */ 200 public static final String COOKIE = "Cookie"; 201 /** 202 * The HTTP {@code Date} header field name. 203 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a> 204 */ 205 public static final String DATE = "Date"; 206 /** 207 * The HTTP {@code ETag} header field name. 208 * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a> 209 */ 210 public static final String ETAG = "ETag"; 211 /** 212 * The HTTP {@code Expect} header field name. 213 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a> 214 */ 215 public static final String EXPECT = "Expect"; 216 /** 217 * The HTTP {@code Expires} header field name. 218 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a> 219 */ 220 public static final String EXPIRES = "Expires"; 221 /** 222 * The HTTP {@code From} header field name. 223 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a> 224 */ 225 public static final String FROM = "From"; 226 /** 227 * The HTTP {@code Host} header field name. 228 * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a> 229 */ 230 public static final String HOST = "Host"; 231 /** 232 * The HTTP {@code If-Match} header field name. 233 * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a> 234 */ 235 public static final String IF_MATCH = "If-Match"; 236 /** 237 * The HTTP {@code If-Modified-Since} header field name. 238 * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a> 239 */ 240 public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; 241 /** 242 * The HTTP {@code If-None-Match} header field name. 243 * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a> 244 */ 245 public static final String IF_NONE_MATCH = "If-None-Match"; 246 /** 247 * The HTTP {@code If-Range} header field name. 248 * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a> 249 */ 250 public static final String IF_RANGE = "If-Range"; 251 /** 252 * The HTTP {@code If-Unmodified-Since} header field name. 253 * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a> 254 */ 255 public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; 256 /** 257 * The HTTP {@code Last-Modified} header field name. 258 * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a> 259 */ 260 public static final String LAST_MODIFIED = "Last-Modified"; 261 /** 262 * The HTTP {@code Link} header field name. 263 * @see <a href="https://tools.ietf.org/html/rfc5988">RFC 5988</a> 264 */ 265 public static final String LINK = "Link"; 266 /** 267 * The HTTP {@code Location} header field name. 268 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a> 269 */ 270 public static final String LOCATION = "Location"; 271 /** 272 * The HTTP {@code Max-Forwards} header field name. 273 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a> 274 */ 275 public static final String MAX_FORWARDS = "Max-Forwards"; 276 /** 277 * The HTTP {@code Origin} header field name. 278 * @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a> 279 */ 280 public static final String ORIGIN = "Origin"; 281 /** 282 * The HTTP {@code Pragma} header field name. 283 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a> 284 */ 285 public static final String PRAGMA = "Pragma"; 286 /** 287 * The HTTP {@code Proxy-Authenticate} header field name. 288 * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a> 289 */ 290 public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; 291 /** 292 * The HTTP {@code Proxy-Authorization} header field name. 293 * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a> 294 */ 295 public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; 296 /** 297 * The HTTP {@code Range} header field name. 298 * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a> 299 */ 300 public static final String RANGE = "Range"; 301 /** 302 * The HTTP {@code Referer} header field name. 303 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a> 304 */ 305 public static final String REFERER = "Referer"; 306 /** 307 * The HTTP {@code Retry-After} header field name. 308 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a> 309 */ 310 public static final String RETRY_AFTER = "Retry-After"; 311 /** 312 * The HTTP {@code Server} header field name. 313 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a> 314 */ 315 public static final String SERVER = "Server"; 316 /** 317 * The HTTP {@code Set-Cookie} header field name. 318 * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a> 319 */ 320 public static final String SET_COOKIE = "Set-Cookie"; 321 /** 322 * The HTTP {@code Set-Cookie2} header field name. 323 * @see <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a> 324 */ 325 public static final String SET_COOKIE2 = "Set-Cookie2"; 326 /** 327 * The HTTP {@code TE} header field name. 328 * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a> 329 */ 330 public static final String TE = "TE"; 331 /** 332 * The HTTP {@code Trailer} header field name. 333 * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a> 334 */ 335 public static final String TRAILER = "Trailer"; 336 /** 337 * The HTTP {@code Transfer-Encoding} header field name. 338 * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a> 339 */ 340 public static final String TRANSFER_ENCODING = "Transfer-Encoding"; 341 /** 342 * The HTTP {@code Upgrade} header field name. 343 * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a> 344 */ 345 public static final String UPGRADE = "Upgrade"; 346 /** 347 * The HTTP {@code User-Agent} header field name. 348 * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a> 349 */ 350 public static final String USER_AGENT = "User-Agent"; 351 /** 352 * The HTTP {@code Vary} header field name. 353 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a> 354 */ 355 public static final String VARY = "Vary"; 356 /** 357 * The HTTP {@code Via} header field name. 358 * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a> 359 */ 360 public static final String VIA = "Via"; 361 /** 362 * The HTTP {@code Warning} header field name. 363 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a> 364 */ 365 public static final String WARNING = "Warning"; 366 /** 367 * The HTTP {@code WWW-Authenticate} header field name. 368 * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a> 369 */ 370 public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; 371 372 /** 373 * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match". 374 * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a> 375 */ 376 private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?"); 377 378 private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 379 380 /** 381 * Date formats as specified in the HTTP RFC. 382 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a> 383 */ 384 private static final String[] DATE_FORMATS = new String[] { 385 "EEE, dd MMM yyyy HH:mm:ss zzz", 386 "EEE, dd-MMM-yy HH:mm:ss zzz", 387 "EEE MMM dd HH:mm:ss yyyy" 388 }; 389 390 391 private final Map<String, List<String>> headers; 392 393 394 /** 395 * Construct a new, empty instance of the {@code HttpHeaders} object. 396 */ 397 public HttpHeaders() { 398 this(new LinkedCaseInsensitiveMap<List<String>>(8, Locale.ENGLISH), false); 399 } 400 401 /** 402 * Private constructor that can create read-only {@code HttpHeader} instances. 403 */ 404 private HttpHeaders(Map<String, List<String>> headers, boolean readOnly) { 405 if (readOnly) { 406 Map<String, List<String>> map = 407 new LinkedCaseInsensitiveMap<List<String>>(headers.size(), Locale.ENGLISH); 408 for (Entry<String, List<String>> entry : headers.entrySet()) { 409 List<String> values = Collections.unmodifiableList(entry.getValue()); 410 map.put(entry.getKey(), values); 411 } 412 this.headers = Collections.unmodifiableMap(map); 413 } 414 else { 415 this.headers = headers; 416 } 417 } 418 419 420 /** 421 * Set the list of acceptable {@linkplain MediaType media types}, 422 * as specified by the {@code Accept} header. 423 */ 424 public void setAccept(List<MediaType> acceptableMediaTypes) { 425 set(ACCEPT, MediaType.toString(acceptableMediaTypes)); 426 } 427 428 /** 429 * Return the list of acceptable {@linkplain MediaType media types}, 430 * as specified by the {@code Accept} header. 431 * <p>Returns an empty list when the acceptable media types are unspecified. 432 */ 433 public List<MediaType> getAccept() { 434 return MediaType.parseMediaTypes(get(ACCEPT)); 435 } 436 437 /** 438 * Set the (new) value of the {@code Access-Control-Allow-Credentials} response header. 439 */ 440 public void setAccessControlAllowCredentials(boolean allowCredentials) { 441 set(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(allowCredentials)); 442 } 443 444 /** 445 * Return the value of the {@code Access-Control-Allow-Credentials} response header. 446 */ 447 public boolean getAccessControlAllowCredentials() { 448 return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS)); 449 } 450 451 /** 452 * Set the (new) value of the {@code Access-Control-Allow-Headers} response header. 453 */ 454 public void setAccessControlAllowHeaders(List<String> allowedHeaders) { 455 set(ACCESS_CONTROL_ALLOW_HEADERS, toCommaDelimitedString(allowedHeaders)); 456 } 457 458 /** 459 * Return the value of the {@code Access-Control-Allow-Headers} response header. 460 */ 461 public List<String> getAccessControlAllowHeaders() { 462 return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS); 463 } 464 465 /** 466 * Set the (new) value of the {@code Access-Control-Allow-Methods} response header. 467 */ 468 public void setAccessControlAllowMethods(List<HttpMethod> allowedMethods) { 469 set(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.collectionToCommaDelimitedString(allowedMethods)); 470 } 471 472 /** 473 * Return the value of the {@code Access-Control-Allow-Methods} response header. 474 */ 475 public List<HttpMethod> getAccessControlAllowMethods() { 476 List<HttpMethod> result = new ArrayList<HttpMethod>(); 477 String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS); 478 if (value != null) { 479 String[] tokens = StringUtils.tokenizeToStringArray(value, ","); 480 for (String token : tokens) { 481 HttpMethod resolved = HttpMethod.resolve(token); 482 if (resolved != null) { 483 result.add(resolved); 484 } 485 } 486 } 487 return result; 488 } 489 490 /** 491 * Set the (new) value of the {@code Access-Control-Allow-Origin} response header. 492 */ 493 public void setAccessControlAllowOrigin(String allowedOrigin) { 494 set(ACCESS_CONTROL_ALLOW_ORIGIN, allowedOrigin); 495 } 496 497 /** 498 * Return the value of the {@code Access-Control-Allow-Origin} response header. 499 */ 500 public String getAccessControlAllowOrigin() { 501 return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN); 502 } 503 504 /** 505 * Set the (new) value of the {@code Access-Control-Expose-Headers} response header. 506 */ 507 public void setAccessControlExposeHeaders(List<String> exposedHeaders) { 508 set(ACCESS_CONTROL_EXPOSE_HEADERS, toCommaDelimitedString(exposedHeaders)); 509 } 510 511 /** 512 * Return the value of the {@code Access-Control-Expose-Headers} response header. 513 */ 514 public List<String> getAccessControlExposeHeaders() { 515 return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS); 516 } 517 518 /** 519 * Set the (new) value of the {@code Access-Control-Max-Age} response header. 520 */ 521 public void setAccessControlMaxAge(long maxAge) { 522 set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge)); 523 } 524 525 /** 526 * Return the value of the {@code Access-Control-Max-Age} response header. 527 * <p>Returns -1 when the max age is unknown. 528 */ 529 public long getAccessControlMaxAge() { 530 String value = getFirst(ACCESS_CONTROL_MAX_AGE); 531 return (value != null ? Long.parseLong(value) : -1); 532 } 533 534 /** 535 * Set the (new) value of the {@code Access-Control-Request-Headers} request header. 536 */ 537 public void setAccessControlRequestHeaders(List<String> requestHeaders) { 538 set(ACCESS_CONTROL_REQUEST_HEADERS, toCommaDelimitedString(requestHeaders)); 539 } 540 541 /** 542 * Return the value of the {@code Access-Control-Request-Headers} request header. 543 */ 544 public List<String> getAccessControlRequestHeaders() { 545 return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS); 546 } 547 548 /** 549 * Set the (new) value of the {@code Access-Control-Request-Method} request header. 550 */ 551 public void setAccessControlRequestMethod(HttpMethod requestMethod) { 552 set(ACCESS_CONTROL_REQUEST_METHOD, requestMethod.name()); 553 } 554 555 /** 556 * Return the value of the {@code Access-Control-Request-Method} request header. 557 */ 558 public HttpMethod getAccessControlRequestMethod() { 559 return HttpMethod.resolve(getFirst(ACCESS_CONTROL_REQUEST_METHOD)); 560 } 561 562 /** 563 * Set the list of acceptable {@linkplain Charset charsets}, 564 * as specified by the {@code Accept-Charset} header. 565 */ 566 public void setAcceptCharset(List<Charset> acceptableCharsets) { 567 StringBuilder builder = new StringBuilder(); 568 for (Iterator<Charset> iterator = acceptableCharsets.iterator(); iterator.hasNext();) { 569 Charset charset = iterator.next(); 570 builder.append(charset.name().toLowerCase(Locale.ENGLISH)); 571 if (iterator.hasNext()) { 572 builder.append(", "); 573 } 574 } 575 set(ACCEPT_CHARSET, builder.toString()); 576 } 577 578 /** 579 * Return the list of acceptable {@linkplain Charset charsets}, 580 * as specified by the {@code Accept-Charset} header. 581 */ 582 public List<Charset> getAcceptCharset() { 583 String value = getFirst(ACCEPT_CHARSET); 584 if (value != null) { 585 String[] tokens = StringUtils.tokenizeToStringArray(value, ","); 586 List<Charset> result = new ArrayList<Charset>(tokens.length); 587 for (String token : tokens) { 588 int paramIdx = token.indexOf(';'); 589 String charsetName; 590 if (paramIdx == -1) { 591 charsetName = token; 592 } 593 else { 594 charsetName = token.substring(0, paramIdx); 595 } 596 if (!charsetName.equals("*")) { 597 result.add(Charset.forName(charsetName)); 598 } 599 } 600 return result; 601 } 602 else { 603 return Collections.emptyList(); 604 } 605 } 606 607 /** 608 * Set the set of allowed {@link HttpMethod HTTP methods}, 609 * as specified by the {@code Allow} header. 610 */ 611 public void setAllow(Set<HttpMethod> allowedMethods) { 612 set(ALLOW, StringUtils.collectionToCommaDelimitedString(allowedMethods)); 613 } 614 615 /** 616 * Return the set of allowed {@link HttpMethod HTTP methods}, 617 * as specified by the {@code Allow} header. 618 * <p>Returns an empty set when the allowed methods are unspecified. 619 */ 620 public Set<HttpMethod> getAllow() { 621 String value = getFirst(ALLOW); 622 if (!StringUtils.isEmpty(value)) { 623 String[] tokens = StringUtils.tokenizeToStringArray(value, ","); 624 List<HttpMethod> result = new ArrayList<HttpMethod>(tokens.length); 625 for (String token : tokens) { 626 HttpMethod resolved = HttpMethod.resolve(token); 627 if (resolved != null) { 628 result.add(resolved); 629 } 630 } 631 return EnumSet.copyOf(result); 632 } 633 else { 634 return EnumSet.noneOf(HttpMethod.class); 635 } 636 } 637 638 /** 639 * Set the (new) value of the {@code Cache-Control} header. 640 */ 641 public void setCacheControl(String cacheControl) { 642 set(CACHE_CONTROL, cacheControl); 643 } 644 645 /** 646 * Return the value of the {@code Cache-Control} header. 647 */ 648 public String getCacheControl() { 649 return getFieldValues(CACHE_CONTROL); 650 } 651 652 /** 653 * Set the (new) value of the {@code Connection} header. 654 */ 655 public void setConnection(String connection) { 656 set(CONNECTION, connection); 657 } 658 659 /** 660 * Set the (new) value of the {@code Connection} header. 661 */ 662 public void setConnection(List<String> connection) { 663 set(CONNECTION, toCommaDelimitedString(connection)); 664 } 665 666 /** 667 * Return the value of the {@code Connection} header. 668 */ 669 public List<String> getConnection() { 670 return getValuesAsList(CONNECTION); 671 } 672 673 /** 674 * Set the {@code Content-Disposition} header when creating a 675 * {@code "multipart/form-data"} request. The given filename is formatted 676 * as a quoted-string, as defined in RFC 2616, section 2.2, and any quote 677 * characters within the filename value will be escaped with a backslash, 678 * e.g. {@code "foo\"bar.txt"} becomes {@code "foo\\\"bar.txt"}. 679 * <p>Applications typically would not set this header directly but 680 * rather prepare a {@code MultiValueMap<String, Object>}, containing an 681 * Object or a {@link org.springframework.core.io.Resource} for each part, 682 * and then pass that to the {@code RestTemplate} or {@code WebClient}. 683 * @param name the control name 684 * @param filename the filename (may be {@code null}) 685 */ 686 public void setContentDispositionFormData(String name, String filename) { 687 Assert.notNull(name, "'name' must not be null"); 688 StringBuilder builder = new StringBuilder("form-data; name=\""); 689 builder.append(name).append('\"'); 690 if (filename != null) { 691 builder.append("; filename=\""); 692 builder.append(escapeQuotationsInFilename(filename)).append('\"'); 693 } 694 set(CONTENT_DISPOSITION, builder.toString()); 695 } 696 697 /** 698 * Set the (new) value of the {@code Content-Disposition} header 699 * for {@code form-data}, optionally encoding the filename using the RFC 5987. 700 * <p>Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported. 701 * @param name the control name 702 * @param filename the filename (may be {@code null}) 703 * @param charset the charset used for the filename (may be {@code null}) 704 * @deprecated deprecated in 4.3.11 and removed from 5.0; as per 705 * <a link="https://tools.ietf.org/html/rfc7578#section-4.2">RFC 7578, Section 4.2</a>, 706 * an RFC 5987 style encoding should not be used for multipart/form-data requests. 707 * Furthermore there should be no reason for applications to set this header 708 * explicitly; for more details also read 709 * {@link #setContentDispositionFormData(String, String)} 710 */ 711 @Deprecated 712 public void setContentDispositionFormData(String name, String filename, Charset charset) { 713 if (filename == null || charset == null || charset.name().equals("US-ASCII")) { 714 setContentDispositionFormData(name, filename); 715 return; 716 } 717 Assert.notNull(name, "'name' must not be null"); 718 String encodedFilename = encodeHeaderFieldParam(filename, charset); 719 set(CONTENT_DISPOSITION, "form-data; name=\"" + name + '\"' + "; filename*=" + encodedFilename); 720 } 721 722 /** 723 * Set the length of the body in bytes, as specified by the 724 * {@code Content-Length} header. 725 */ 726 public void setContentLength(long contentLength) { 727 set(CONTENT_LENGTH, Long.toString(contentLength)); 728 } 729 730 /** 731 * Return the length of the body in bytes, as specified by the 732 * {@code Content-Length} header. 733 * <p>Returns -1 when the content-length is unknown. 734 */ 735 public long getContentLength() { 736 String value = getFirst(CONTENT_LENGTH); 737 return (value != null ? Long.parseLong(value) : -1); 738 } 739 740 /** 741 * Set the {@linkplain MediaType media type} of the body, 742 * as specified by the {@code Content-Type} header. 743 */ 744 public void setContentType(MediaType mediaType) { 745 Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'"); 746 Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'"); 747 set(CONTENT_TYPE, mediaType.toString()); 748 } 749 750 /** 751 * Return the {@linkplain MediaType media type} of the body, as specified 752 * by the {@code Content-Type} header. 753 * <p>Returns {@code null} when the content-type is unknown. 754 */ 755 public MediaType getContentType() { 756 String value = getFirst(CONTENT_TYPE); 757 return (StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null); 758 } 759 760 /** 761 * Set the date and time at which the message was created, as specified 762 * by the {@code Date} header. 763 * <p>The date should be specified as the number of milliseconds since 764 * January 1, 1970 GMT. 765 */ 766 public void setDate(long date) { 767 setDate(DATE, date); 768 } 769 770 /** 771 * Return the date and time at which the message was created, as specified 772 * by the {@code Date} header. 773 * <p>The date is returned as the number of milliseconds since 774 * January 1, 1970 GMT. Returns -1 when the date is unknown. 775 * @throws IllegalArgumentException if the value cannot be converted to a date 776 */ 777 public long getDate() { 778 return getFirstDate(DATE); 779 } 780 781 /** 782 * Set the (new) entity tag of the body, as specified by the {@code ETag} header. 783 */ 784 public void setETag(String etag) { 785 if (etag != null) { 786 Assert.isTrue(etag.startsWith("\"") || etag.startsWith("W/"), 787 "Invalid ETag: does not start with W/ or \""); 788 Assert.isTrue(etag.endsWith("\""), "Invalid ETag: does not end with \""); 789 } 790 set(ETAG, etag); 791 } 792 793 /** 794 * Return the entity tag of the body, as specified by the {@code ETag} header. 795 */ 796 public String getETag() { 797 return getFirst(ETAG); 798 } 799 800 /** 801 * Set the date and time at which the message is no longer valid, 802 * as specified by the {@code Expires} header. 803 * <p>The date should be specified as the number of milliseconds since 804 * January 1, 1970 GMT. 805 */ 806 public void setExpires(long expires) { 807 setDate(EXPIRES, expires); 808 } 809 810 /** 811 * Return the date and time at which the message is no longer valid, 812 * as specified by the {@code Expires} header. 813 * <p>The date is returned as the number of milliseconds since 814 * January 1, 1970 GMT. Returns -1 when the date is unknown. 815 */ 816 public long getExpires() { 817 return getFirstDate(EXPIRES, false); 818 } 819 820 /** 821 * Set the (new) value of the {@code If-Match} header. 822 * @since 4.3 823 */ 824 public void setIfMatch(String ifMatch) { 825 set(IF_MATCH, ifMatch); 826 } 827 828 /** 829 * Set the (new) value of the {@code If-Match} header. 830 * @since 4.3 831 */ 832 public void setIfMatch(List<String> ifMatchList) { 833 set(IF_MATCH, toCommaDelimitedString(ifMatchList)); 834 } 835 836 /** 837 * Return the value of the {@code If-Match} header. 838 * @since 4.3 839 */ 840 public List<String> getIfMatch() { 841 return getETagValuesAsList(IF_MATCH); 842 } 843 844 /** 845 * Set the (new) value of the {@code If-Modified-Since} header. 846 * <p>The date should be specified as the number of milliseconds since 847 * January 1, 1970 GMT. 848 */ 849 public void setIfModifiedSince(long ifModifiedSince) { 850 setDate(IF_MODIFIED_SINCE, ifModifiedSince); 851 } 852 853 /** 854 * Return the value of the {@code If-Modified-Since} header. 855 * <p>The date is returned as the number of milliseconds since 856 * January 1, 1970 GMT. Returns -1 when the date is unknown. 857 */ 858 public long getIfModifiedSince() { 859 return getFirstDate(IF_MODIFIED_SINCE, false); 860 } 861 862 /** 863 * Set the (new) value of the {@code If-None-Match} header. 864 */ 865 public void setIfNoneMatch(String ifNoneMatch) { 866 set(IF_NONE_MATCH, ifNoneMatch); 867 } 868 869 /** 870 * Set the (new) values of the {@code If-None-Match} header. 871 */ 872 public void setIfNoneMatch(List<String> ifNoneMatchList) { 873 set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList)); 874 } 875 876 /** 877 * Return the value of the {@code If-None-Match} header. 878 */ 879 public List<String> getIfNoneMatch() { 880 return getETagValuesAsList(IF_NONE_MATCH); 881 } 882 883 /** 884 * Set the (new) value of the {@code If-Unmodified-Since} header. 885 * <p>The date should be specified as the number of milliseconds since 886 * January 1, 1970 GMT. 887 * @since 4.3 888 */ 889 public void setIfUnmodifiedSince(long ifUnmodifiedSince) { 890 setDate(IF_UNMODIFIED_SINCE, ifUnmodifiedSince); 891 } 892 893 /** 894 * Return the value of the {@code If-Unmodified-Since} header. 895 * <p>The date is returned as the number of milliseconds since 896 * January 1, 1970 GMT. Returns -1 when the date is unknown. 897 * @since 4.3 898 */ 899 public long getIfUnmodifiedSince() { 900 return getFirstDate(IF_UNMODIFIED_SINCE, false); 901 } 902 903 /** 904 * Set the time the resource was last changed, as specified by the 905 * {@code Last-Modified} header. 906 * <p>The date should be specified as the number of milliseconds since 907 * January 1, 1970 GMT. 908 */ 909 public void setLastModified(long lastModified) { 910 setDate(LAST_MODIFIED, lastModified); 911 } 912 913 /** 914 * Return the time the resource was last changed, as specified by the 915 * {@code Last-Modified} header. 916 * <p>The date is returned as the number of milliseconds since 917 * January 1, 1970 GMT. Returns -1 when the date is unknown. 918 */ 919 public long getLastModified() { 920 return getFirstDate(LAST_MODIFIED, false); 921 } 922 923 /** 924 * Set the (new) location of a resource, 925 * as specified by the {@code Location} header. 926 */ 927 public void setLocation(URI location) { 928 set(LOCATION, location.toASCIIString()); 929 } 930 931 /** 932 * Return the (new) location of a resource 933 * as specified by the {@code Location} header. 934 * <p>Returns {@code null} when the location is unknown. 935 */ 936 public URI getLocation() { 937 String value = getFirst(LOCATION); 938 return (value != null ? URI.create(value) : null); 939 } 940 941 /** 942 * Set the (new) value of the {@code Origin} header. 943 */ 944 public void setOrigin(String origin) { 945 set(ORIGIN, origin); 946 } 947 948 /** 949 * Return the value of the {@code Origin} header. 950 */ 951 public String getOrigin() { 952 return getFirst(ORIGIN); 953 } 954 955 /** 956 * Set the (new) value of the {@code Pragma} header. 957 */ 958 public void setPragma(String pragma) { 959 set(PRAGMA, pragma); 960 } 961 962 /** 963 * Return the value of the {@code Pragma} header. 964 */ 965 public String getPragma() { 966 return getFirst(PRAGMA); 967 } 968 969 /** 970 * Sets the (new) value of the {@code Range} header. 971 */ 972 public void setRange(List<HttpRange> ranges) { 973 String value = HttpRange.toString(ranges); 974 set(RANGE, value); 975 } 976 977 /** 978 * Return the value of the {@code Range} header. 979 * <p>Returns an empty list when the range is unknown. 980 */ 981 public List<HttpRange> getRange() { 982 String value = getFirst(RANGE); 983 return HttpRange.parseRanges(value); 984 } 985 986 /** 987 * Set the (new) value of the {@code Upgrade} header. 988 */ 989 public void setUpgrade(String upgrade) { 990 set(UPGRADE, upgrade); 991 } 992 993 /** 994 * Return the value of the {@code Upgrade} header. 995 */ 996 public String getUpgrade() { 997 return getFirst(UPGRADE); 998 } 999 1000 /** 1001 * Set the request header names (e.g. "Accept-Language") for which the 1002 * response is subject to content negotiation and variances based on the 1003 * value of those request headers. 1004 * @param requestHeaders the request header names 1005 * @since 4.3 1006 */ 1007 public void setVary(List<String> requestHeaders) { 1008 set(VARY, toCommaDelimitedString(requestHeaders)); 1009 } 1010 1011 /** 1012 * Return the request header names subject to content negotiation. 1013 * @since 4.3 1014 */ 1015 public List<String> getVary() { 1016 return getValuesAsList(VARY); 1017 } 1018 1019 /** 1020 * Set the given date under the given header name after formatting it as a string 1021 * using the pattern {@code "EEE, dd MMM yyyy HH:mm:ss zzz"}. The equivalent of 1022 * {@link #set(String, String)} but for date headers. 1023 * @since 3.2.4 1024 */ 1025 public void setDate(String headerName, long date) { 1026 SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US); 1027 dateFormat.setTimeZone(GMT); 1028 set(headerName, dateFormat.format(new Date(date))); 1029 } 1030 1031 /** 1032 * Parse the first header value for the given header name as a date, 1033 * return -1 if there is no value, or raise {@link IllegalArgumentException} 1034 * if the value cannot be parsed as a date. 1035 * @param headerName the header name 1036 * @return the parsed date header, or -1 if none 1037 * @since 3.2.4 1038 */ 1039 public long getFirstDate(String headerName) { 1040 return getFirstDate(headerName, true); 1041 } 1042 1043 /** 1044 * Parse the first header value for the given header name as a date, 1045 * return -1 if there is no value or also in case of an invalid value 1046 * (if {@code rejectInvalid=false}), or raise {@link IllegalArgumentException} 1047 * if the value cannot be parsed as a date. 1048 * @param headerName the header name 1049 * @param rejectInvalid whether to reject invalid values with an 1050 * {@link IllegalArgumentException} ({@code true}) or rather return -1 1051 * in that case ({@code false}) 1052 * @return the parsed date header, or -1 if none (or invalid) 1053 */ 1054 private long getFirstDate(String headerName, boolean rejectInvalid) { 1055 String headerValue = getFirst(headerName); 1056 if (headerValue == null) { 1057 // No header value sent at all 1058 return -1; 1059 } 1060 if (headerValue.length() >= 3) { 1061 // Short "0" or "-1" like values are never valid HTTP date headers... 1062 // Let's only bother with SimpleDateFormat parsing for long enough values. 1063 for (String dateFormat : DATE_FORMATS) { 1064 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US); 1065 simpleDateFormat.setTimeZone(GMT); 1066 try { 1067 return simpleDateFormat.parse(headerValue).getTime(); 1068 } 1069 catch (ParseException ex) { 1070 // ignore 1071 } 1072 } 1073 } 1074 if (rejectInvalid) { 1075 throw new IllegalArgumentException("Cannot parse date value \"" + headerValue + 1076 "\" for \"" + headerName + "\" header"); 1077 } 1078 return -1; 1079 } 1080 1081 /** 1082 * Return all values of a given header name, 1083 * even if this header is set multiple times. 1084 * @param headerName the header name 1085 * @return all associated values 1086 * @since 4.3 1087 */ 1088 public List<String> getValuesAsList(String headerName) { 1089 List<String> values = get(headerName); 1090 if (values != null) { 1091 List<String> result = new ArrayList<String>(); 1092 for (String value : values) { 1093 if (value != null) { 1094 String[] tokens = StringUtils.tokenizeToStringArray(value, ","); 1095 for (String token : tokens) { 1096 result.add(token); 1097 } 1098 } 1099 } 1100 return result; 1101 } 1102 return Collections.emptyList(); 1103 } 1104 1105 /** 1106 * Retrieve a combined result from the field values of the ETag header. 1107 * @param headerName the header name 1108 * @return the combined result 1109 * @since 4.3 1110 */ 1111 protected List<String> getETagValuesAsList(String headerName) { 1112 List<String> values = get(headerName); 1113 if (values != null) { 1114 List<String> result = new ArrayList<String>(); 1115 for (String value : values) { 1116 if (value != null) { 1117 Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value); 1118 while (matcher.find()) { 1119 if ("*".equals(matcher.group())) { 1120 result.add(matcher.group()); 1121 } 1122 else { 1123 result.add(matcher.group(1)); 1124 } 1125 } 1126 if (result.isEmpty()) { 1127 throw new IllegalArgumentException( 1128 "Could not parse header '" + headerName + "' with value '" + value + "'"); 1129 } 1130 } 1131 } 1132 return result; 1133 } 1134 return Collections.emptyList(); 1135 } 1136 1137 /** 1138 * Retrieve a combined result from the field values of multi-valued headers. 1139 * @param headerName the header name 1140 * @return the combined result 1141 * @since 4.3 1142 */ 1143 protected String getFieldValues(String headerName) { 1144 List<String> headerValues = get(headerName); 1145 return (headerValues != null ? toCommaDelimitedString(headerValues) : null); 1146 } 1147 1148 /** 1149 * Turn the given list of header values into a comma-delimited result. 1150 * @param headerValues the list of header values 1151 * @return a combined result with comma delimitation 1152 */ 1153 protected String toCommaDelimitedString(List<String> headerValues) { 1154 StringBuilder builder = new StringBuilder(); 1155 for (Iterator<String> it = headerValues.iterator(); it.hasNext(); ) { 1156 String val = it.next(); 1157 builder.append(val); 1158 if (it.hasNext()) { 1159 builder.append(", "); 1160 } 1161 } 1162 return builder.toString(); 1163 } 1164 1165 1166 // MultiValueMap implementation 1167 1168 /** 1169 * Return the first header value for the given header name, if any. 1170 * @param headerName the header name 1171 * @return the first header value, or {@code null} if none 1172 */ 1173 @Override 1174 public String getFirst(String headerName) { 1175 List<String> headerValues = this.headers.get(headerName); 1176 return (headerValues != null ? headerValues.get(0) : null); 1177 } 1178 1179 /** 1180 * Add the given, single header value under the given name. 1181 * @param headerName the header name 1182 * @param headerValue the header value 1183 * @throws UnsupportedOperationException if adding headers is not supported 1184 * @see #put(String, List) 1185 * @see #set(String, String) 1186 */ 1187 @Override 1188 public void add(String headerName, String headerValue) { 1189 List<String> headerValues = this.headers.get(headerName); 1190 if (headerValues == null) { 1191 headerValues = new LinkedList<String>(); 1192 this.headers.put(headerName, headerValues); 1193 } 1194 headerValues.add(headerValue); 1195 } 1196 1197 /** 1198 * Set the given, single header value under the given name. 1199 * @param headerName the header name 1200 * @param headerValue the header value 1201 * @throws UnsupportedOperationException if adding headers is not supported 1202 * @see #put(String, List) 1203 * @see #add(String, String) 1204 */ 1205 @Override 1206 public void set(String headerName, String headerValue) { 1207 List<String> headerValues = new LinkedList<String>(); 1208 headerValues.add(headerValue); 1209 this.headers.put(headerName, headerValues); 1210 } 1211 1212 @Override 1213 public void setAll(Map<String, String> values) { 1214 for (Entry<String, String> entry : values.entrySet()) { 1215 set(entry.getKey(), entry.getValue()); 1216 } 1217 } 1218 1219 @Override 1220 public Map<String, String> toSingleValueMap() { 1221 LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<String,String>(this.headers.size()); 1222 for (Entry<String, List<String>> entry : this.headers.entrySet()) { 1223 singleValueMap.put(entry.getKey(), entry.getValue().get(0)); 1224 } 1225 return singleValueMap; 1226 } 1227 1228 1229 // Map implementation 1230 1231 @Override 1232 public int size() { 1233 return this.headers.size(); 1234 } 1235 1236 @Override 1237 public boolean isEmpty() { 1238 return this.headers.isEmpty(); 1239 } 1240 1241 @Override 1242 public boolean containsKey(Object key) { 1243 return this.headers.containsKey(key); 1244 } 1245 1246 @Override 1247 public boolean containsValue(Object value) { 1248 return this.headers.containsValue(value); 1249 } 1250 1251 @Override 1252 public List<String> get(Object key) { 1253 return this.headers.get(key); 1254 } 1255 1256 @Override 1257 public List<String> put(String key, List<String> value) { 1258 return this.headers.put(key, value); 1259 } 1260 1261 @Override 1262 public List<String> remove(Object key) { 1263 return this.headers.remove(key); 1264 } 1265 1266 @Override 1267 public void putAll(Map<? extends String, ? extends List<String>> map) { 1268 this.headers.putAll(map); 1269 } 1270 1271 @Override 1272 public void clear() { 1273 this.headers.clear(); 1274 } 1275 1276 @Override 1277 public Set<String> keySet() { 1278 return this.headers.keySet(); 1279 } 1280 1281 @Override 1282 public Collection<List<String>> values() { 1283 return this.headers.values(); 1284 } 1285 1286 @Override 1287 public Set<Entry<String, List<String>>> entrySet() { 1288 return this.headers.entrySet(); 1289 } 1290 1291 1292 @Override 1293 public boolean equals(Object other) { 1294 if (this == other) { 1295 return true; 1296 } 1297 if (!(other instanceof HttpHeaders)) { 1298 return false; 1299 } 1300 HttpHeaders otherHeaders = (HttpHeaders) other; 1301 return this.headers.equals(otherHeaders.headers); 1302 } 1303 1304 @Override 1305 public int hashCode() { 1306 return this.headers.hashCode(); 1307 } 1308 1309 @Override 1310 public String toString() { 1311 return this.headers.toString(); 1312 } 1313 1314 1315 /** 1316 * Return an {@code HttpHeaders} object that can only be read, not written to. 1317 */ 1318 public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) { 1319 Assert.notNull(headers, "HttpHeaders must not be null"); 1320 return new HttpHeaders(headers, true); 1321 } 1322 1323 private static String escapeQuotationsInFilename(String filename) { 1324 if (filename.indexOf('"') == -1 && filename.indexOf('\\') == -1) { 1325 return filename; 1326 } 1327 boolean escaped = false; 1328 StringBuilder sb = new StringBuilder(); 1329 for (char c : filename.toCharArray()) { 1330 sb.append((c == '"' && !escaped) ? "\\\"" : c); 1331 escaped = (!escaped && c == '\\'); 1332 } 1333 // Remove backslash at the end.. 1334 if (escaped) { 1335 sb.deleteCharAt(sb.length() - 1); 1336 } 1337 return sb.toString(); 1338 } 1339 1340 /** 1341 * Encode the given header field param as describe in RFC 5987. 1342 * @param input the header field param 1343 * @param charset the charset of the header field param string 1344 * @return the encoded header field param 1345 * @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a> 1346 */ 1347 private static String encodeHeaderFieldParam(String input, Charset charset) { 1348 Assert.notNull(input, "Input String should not be null"); 1349 Assert.notNull(charset, "Charset should not be null"); 1350 Assert.isTrue(!charset.name().equals("US-ASCII"), "ASCII does not require encoding"); 1351 Assert.isTrue(charset.name().equals("UTF-8") || charset.name().equals("ISO-8859-1"), 1352 "Charset should be UTF-8 or ISO-8859-1"); 1353 byte[] source = input.getBytes(charset); 1354 int len = source.length; 1355 StringBuilder sb = new StringBuilder(len << 1); 1356 sb.append(charset.name()); 1357 sb.append("''"); 1358 for (byte b : source) { 1359 if (isRFC5987AttrChar(b)) { 1360 sb.append((char) b); 1361 } 1362 else { 1363 sb.append('%'); 1364 char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); 1365 char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); 1366 sb.append(hex1); 1367 sb.append(hex2); 1368 } 1369 } 1370 return sb.toString(); 1371 } 1372 1373 private static boolean isRFC5987AttrChar(byte c) { 1374 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 1375 c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || c == '-' || 1376 c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; 1377 } 1378 1379}