diff options
Diffstat (limited to 'jdisc_http_service/src')
3 files changed, 133 insertions, 50 deletions
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java index 8f04a870dc9..2256a3299a7 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java @@ -1,20 +1,22 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http; -import org.jboss.netty.handler.codec.http.cookie.ClientCookieDecoder; -import org.jboss.netty.handler.codec.http.cookie.ClientCookieEncoder; -import org.jboss.netty.handler.codec.http.cookie.DefaultCookie; -import org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder; -import org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder; +import org.eclipse.jetty.server.CookieCutter; +import org.eclipse.jetty.server.Response; +import java.net.HttpCookie; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.StringTokenizer; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; -import java.util.stream.StreamSupport; /** * A RFC 6265 compliant cookie. @@ -26,6 +28,8 @@ import java.util.stream.StreamSupport; */ public class Cookie { + private final static Logger log = Logger.getLogger(Cookie.class.getName()); + private final Set<Integer> ports = new HashSet<>(); private String name; private String value; @@ -214,26 +218,18 @@ public class Cookie { ret.append(name).append("=").append(value); return ret.toString(); } + // NOTE cookie encoding and decoding: + // The implementation uses Jetty for server-side (encoding of Set-Cookie and decoding of Cookie header), + // and java.net.HttpCookie for client-side (encoding of Cookie and decoding of Set-Cookie header). + // + // Implementation is RFC-6265 compliant. public static String toCookieHeader(Iterable<? extends Cookie> cookies) { - ClientCookieEncoder encoder = ClientCookieEncoder.STRICT; - List<org.jboss.netty.handler.codec.http.cookie.Cookie> nettyCookies = - StreamSupport.stream(cookies.spliterator(), false) - // NOTE: Only name and value is included in Cookie header as of RFC-6265 - .map(cookie -> new DefaultCookie(cookie.getName(), cookie.getValue())) - .collect(Collectors.toList()); - return encoder.encode(nettyCookies); + return encodeCookieHeaderValue(toCookieList(cookies)); } public static List<Cookie> fromCookieHeader(String headerVal) { - if (headerVal == null) return Collections.emptyList(); - - ServerCookieDecoder decoder = ServerCookieDecoder.STRICT; - Set<org.jboss.netty.handler.codec.http.cookie.Cookie> nettyCookies = decoder.decode(headerVal); - return nettyCookies.stream() - // NOTE: Only name and value is included in Cookie header as of RFC-6265 - .map(nettyCookie -> new Cookie(nettyCookie.name(), nettyCookie.value())) - .collect(Collectors.toList()); + return decodeCookieHeaderValue(headerVal); } /** @@ -241,40 +237,124 @@ public class Cookie { */ @Deprecated public static String toSetCookieHeader(Iterable<? extends Cookie> cookies) { - List<String> encodedCookies = toSetCookieHeaderAll(cookies); + List<String> encodedCookies = encodeSetCookieHeaderValue(cookies); return encodedCookies.isEmpty() ? null : encodedCookies.get(0); } // TODO Rename to toSetCookieHeader for Vespa 7 public static List<String> toSetCookieHeaderAll(Iterable<? extends Cookie> cookies) { - ServerCookieEncoder encoder = ServerCookieEncoder.STRICT; - List<org.jboss.netty.handler.codec.http.cookie.Cookie> nettyCookies = - StreamSupport.stream(cookies.spliterator(), false) - .map(cookie -> { - org.jboss.netty.handler.codec.http.cookie.Cookie nettyCookie - = new DefaultCookie(cookie.getName(), cookie.getValue()); - nettyCookie.setPath(cookie.getPath()); - nettyCookie.setMaxAge(cookie.getMaxAge(TimeUnit.SECONDS)); - nettyCookie.setSecure(cookie.isSecure()); - nettyCookie.setHttpOnly(cookie.isHttpOnly()); - nettyCookie.setDomain(cookie.getDomain()); - return nettyCookie; - }) - .collect(Collectors.toList()); - return encoder.encode(nettyCookies); + return encodeSetCookieHeaderValue(cookies); } // TODO Change return type to Cookie for Vespa 7 public static List<Cookie> fromSetCookieHeader(String headerVal) { - if (headerVal == null) return Collections.emptyList(); - - ClientCookieDecoder encoder = ClientCookieDecoder.STRICT; - org.jboss.netty.handler.codec.http.cookie.Cookie nettyCookie = encoder.decode(headerVal); - return Collections.singletonList(new Cookie(nettyCookie.name(), nettyCookie.value()) - .setHttpOnly(nettyCookie.isHttpOnly()) - .setSecure(nettyCookie.isSecure()) - .setMaxAge(nettyCookie.maxAge(), TimeUnit.SECONDS) - .setPath(nettyCookie.path()) - .setDomain(nettyCookie.domain())); + return decodeSetCookieHeaderValue(headerVal); + } + + + private static List<String> encodeSetCookieHeaderValue(Iterable<? extends Cookie> cookies) { + // Ugly, bot Jetty does not provide a dedicated cookie parser (will be included in Jetty 10) + Response response = new Response(null, null); + for (Cookie cookie : cookies) { + response.addSetRFC6265Cookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(TimeUnit.SECONDS), + cookie.isSecure(), + cookie.isHttpOnly()); + } + return new ArrayList<>(response.getHeaders("Set-Cookie")); + } + + private static String encodeCookieHeaderValue(List<Cookie> cookies) { + return cookies.stream() + .map(cookie -> { + HttpCookie httpCookie = new HttpCookie(cookie.getName(), cookie.getValue()); + httpCookie.setComment(cookie.getComment()); + httpCookie.setCommentURL(cookie.getCommentURL()); + httpCookie.setDiscard(cookie.isDiscard()); + httpCookie.setDomain(cookie.getDomain()); + httpCookie.setHttpOnly(cookie.isHttpOnly()); + httpCookie.setMaxAge(cookie.getMaxAge(TimeUnit.SECONDS)); + httpCookie.setPath(cookie.getPath()); + httpCookie.setSecure(cookie.isSecure()); + httpCookie.setVersion(cookie.getVersion()); + String portList = cookie.ports().stream() + .map(Number::toString) + .collect(Collectors.joining(",")); + httpCookie.setPortlist(portList); + return httpCookie.toString(); + }) + .collect(Collectors.joining(";")); + } + + private static List<Cookie> decodeSetCookieHeaderValue(String headerVal) { + + return HttpCookie.parse(headerVal).stream() + .map(httpCookie -> { + Cookie cookie = new Cookie(); + cookie.setName(httpCookie.getName()); + cookie.setValue(httpCookie.getValue()); + cookie.setComment(httpCookie.getComment()); + cookie.setCommentUrl(httpCookie.getCommentURL()); + cookie.setDiscard(httpCookie.getDiscard()); + cookie.setDomain(httpCookie.getDomain()); + cookie.setHttpOnly(httpCookie.isHttpOnly()); + int maxAge = (int) httpCookie.getMaxAge(); + cookie.setMaxAge(maxAge != -1 ? maxAge : Integer.MIN_VALUE, TimeUnit.SECONDS); + cookie.setPath(httpCookie.getPath()); + cookie.setSecure(httpCookie.getSecure()); + cookie.setVersion(httpCookie.getVersion()); + cookie.ports().addAll(parsePortList(httpCookie.getPortlist())); + return cookie; + }) + .collect(Collectors.toList()); + } + + private static List<Cookie> decodeCookieHeaderValue(String headerVal) { + CookieCutter cookieCutter = new CookieCutter(); + cookieCutter.addCookieField(headerVal); + return Arrays.stream(cookieCutter.getCookies()) + .map(servletCookie -> { + Cookie cookie = new Cookie(); + cookie.setName(servletCookie.getName()); + cookie.setValue(servletCookie.getValue()); + cookie.setComment(servletCookie.getComment()); + cookie.setPath(servletCookie.getPath()); + cookie.setDomain(servletCookie.getDomain()); + int maxAge = servletCookie.getMaxAge(); + cookie.setMaxAge(maxAge != -1 ? maxAge : Integer.MIN_VALUE, TimeUnit.SECONDS); + cookie.setSecure(servletCookie.getSecure()); + cookie.setVersion(servletCookie.getVersion()); + cookie.setHttpOnly(servletCookie.isHttpOnly()); + return cookie; + }) + .collect(Collectors.toList()); + } + + private static List<Integer> parsePortList(String rawPortList) { + if (rawPortList == null) return Collections.emptyList(); + + List<Integer> ports = new ArrayList<>(); + StringTokenizer tokenizer = new StringTokenizer(rawPortList, ","); + while (tokenizer.hasMoreTokens()) { + String rawPort = tokenizer.nextToken().trim(); + if (!rawPort.isEmpty()) { + try { + ports.add(Integer.parseInt(rawPort)); + } catch (NumberFormatException e) { + log.log(Level.FINE, "Unable to parse port: " + rawPort, e); + } + } + } + return ports; + } + + private static ArrayList<Cookie> toCookieList(Iterable<? extends Cookie> cookies) { + ArrayList<Cookie> cookieList = new ArrayList<>(); + cookies.forEach(cookieList::add); + return cookieList; } } diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java index 0c98f294c82..ca12de72ec2 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java @@ -106,14 +106,14 @@ public class CookieTestCase { "foo.name=foo.value", Collections.singletonList(newCookie("foo"))); assertEncodeCookie( - "foo.name=foo.value; bar.name=bar.value", + "foo.name=foo.value;bar.name=bar.value", Arrays.asList(newCookie("foo"), newCookie("bar"))); } @Test public void requireThatSetCookieCanBeEncoded() { assertEncodeSetCookie( - Collections.singletonList("foo.name=foo.value; Path=path; Domain=domain; Secure; HTTPOnly"), + Collections.singletonList("foo.name=foo.value;Path=path;Domain=domain;Secure;HttpOnly"), Collections.singletonList(newSetCookie("foo"))); } @@ -131,6 +131,7 @@ public class CookieTestCase { } @Test + @SuppressWarnings("deprecation") public void requireThatSetCookieCanBeDecoded() { final Cookie foo = new Cookie(); foo.setName("foo.name"); @@ -140,6 +141,7 @@ public class CookieTestCase { foo.setMaxAge(0, TimeUnit.SECONDS); foo.setSecure(true); foo.setHttpOnly(true); + foo.setVersion(1); assertDecodeSetCookie(foo, "foo.name=foo.value;Max-Age=0;Path=path;Domain=domain;Secure;HTTPOnly;"); final Cookie bar = new Cookie(); @@ -148,6 +150,7 @@ public class CookieTestCase { bar.setPath("path"); bar.setDomain("domain"); bar.setMaxAge(0, TimeUnit.SECONDS); + bar.setVersion(1); assertDecodeSetCookie(bar, "bar.name=bar.value;Max-Age=0;Path=path;Domain=domain;"); } diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java index 221a1adc1fe..7ed13decbf6 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java @@ -356,7 +356,7 @@ public class HttpServerTest { driver.client().get("/status.html") .expectStatusCode(is(OK)) .expectHeader("Set-Cookie", - is("foo=bar; Path=/foopath; Domain=.localhost; Secure; HTTPOnly")); + is("foo=bar;Path=/foopath;Domain=.localhost;Secure;HttpOnly")); assertThat(driver.close(), is(true)); } |