From a599859595eccb20529dc7553d4be3bd87845450 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 12 Mar 2020 12:21:19 +0000 Subject: [PATCH] ServerCookie ignores empty domain with double quotes Closes gh-24663 --- .../springframework/http/ResponseCookie.java | 38 +++++++++++++++++-- .../reactive/JettyClientHttpResponse.java | 20 +++++----- .../reactive/ReactorClientHttpResponse.java | 16 ++++---- .../http/ResponseCookieTests.java | 12 +++++- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java index 2226b1db6fe..484d65b4806 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -185,9 +185,28 @@ public final class ResponseCookie extends HttpCookie { * with a name-value pair and may also include attributes. * @param name the cookie name * @param value the cookie value - * @return the created cookie instance + * @return a builder to create the cookie with */ public static ResponseCookieBuilder from(final String name, final String value) { + return from(name, value, false); + } + + /** + * Factory method to obtain a builder for a server-defined cookie. Unlike + * {@link #from(String, String)} this option assumes input from a remote + * server, which can be handled more leniently, e.g. ignoring a empty domain + * name with double quotes. + * @param name the cookie name + * @param value the cookie value + * @return a builder to create the cookie with + * @since 5.2.5 + */ + public static ResponseCookieBuilder fromClientResponse(final String name, final String value) { + return from(name, value, true); + } + + + private static ResponseCookieBuilder from(final String name, final String value, boolean lenient) { return new ResponseCookieBuilder() { @@ -220,10 +239,23 @@ public final class ResponseCookie extends HttpCookie { @Override public ResponseCookieBuilder domain(String domain) { - this.domain = domain; + this.domain = initDomain(domain); return this; } + @Nullable + private String initDomain(String domain) { + if (lenient) { + String s = domain.trim(); + if (s.startsWith("\"") && s.endsWith("\"")) { + if (s.substring(1, domain.length() - 1).trim().isEmpty()) { + return null; + } + } + } + return domain; + } + @Override public ResponseCookieBuilder path(String path) { this.path = path; diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java index dacffc04982..67c6a7aced6 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,16 +66,14 @@ class JettyClientHttpResponse implements ClientHttpResponse { MultiValueMap result = new LinkedMultiValueMap<>(); List cookieHeader = getHeaders().get(HttpHeaders.SET_COOKIE); if (cookieHeader != null) { - cookieHeader.forEach(header -> - HttpCookie.parse(header) - .forEach(cookie -> result.add(cookie.getName(), - ResponseCookie.from(cookie.getName(), cookie.getValue()) - .domain(cookie.getDomain()) - .path(cookie.getPath()) - .maxAge(cookie.getMaxAge()) - .secure(cookie.getSecure()) - .httpOnly(cookie.isHttpOnly()) - .build())) + cookieHeader.forEach(header -> HttpCookie.parse(header) + .forEach(c -> result.add(c.getName(), ResponseCookie.fromClientResponse(c.getName(), c.getValue()) + .domain(c.getDomain()) + .path(c.getPath()) + .maxAge(c.getMaxAge()) + .secure(c.getSecure()) + .httpOnly(c.isHttpOnly()) + .build())) ); } return CollectionUtils.unmodifiableMultiValueMap(result); diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java index 63292f462fa..d8a1ef03982 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,13 +100,13 @@ class ReactorClientHttpResponse implements ClientHttpResponse { public MultiValueMap getCookies() { MultiValueMap result = new LinkedMultiValueMap<>(); this.response.cookies().values().stream().flatMap(Collection::stream) - .forEach(cookie -> - result.add(cookie.name(), ResponseCookie.from(cookie.name(), cookie.value()) - .domain(cookie.domain()) - .path(cookie.path()) - .maxAge(cookie.maxAge()) - .secure(cookie.isSecure()) - .httpOnly(cookie.isHttpOnly()) + .forEach(c -> + result.add(c.name(), ResponseCookie.fromClientResponse(c.name(), c.value()) + .domain(c.domain()) + .path(c.path()) + .maxAge(c.maxAge()) + .secure(c.isSecure()) + .httpOnly(c.isHttpOnly()) .build())); return CollectionUtils.unmodifiableMultiValueMap(result); } diff --git a/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java index 5320500b0f1..d40657766c0 100644 --- a/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java +++ b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,4 +81,14 @@ public class ResponseCookieTests { .hasMessageContaining("invalid cookie domain char")); } + @Test // gh-24663 + public void domainWithEmptyDoubleQuotes() { + + Arrays.asList("\"\"", "\t\"\" ", " \" \t \"\t") + .forEach(domain -> { + ResponseCookie cookie = ResponseCookie.fromClientResponse("id", "1fWa").domain("\"\"").build(); + assertThat(cookie.getDomain()).isNull(); + }); + + } }