// Copyright 2016 Yahoo Inc. 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 java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.StreamSupport; /** * A RFC 6265 compliant cookie. * * Note: RFC 2109 and RFC 2965 is no longer supported. All fields that are not part of RFC 6265 are deprecated. * * @author Einar M R Rosenvinge * @author bjorncs */ public class Cookie { private final Set ports = new HashSet<>(); private String name; private String value; private String domain; private String path; private String comment; private String commentUrl; private long maxAgeMillis = TimeUnit.SECONDS.toMillis(Integer.MIN_VALUE); private int version; private boolean secure; private boolean httpOnly; private boolean discard; public Cookie() { } public Cookie(Cookie cookie) { ports.addAll(cookie.ports); name = cookie.name; value = cookie.value; domain = cookie.domain; path = cookie.path; comment = cookie.comment; commentUrl = cookie.commentUrl; maxAgeMillis = cookie.maxAgeMillis; version = cookie.version; secure = cookie.secure; httpOnly = cookie.httpOnly; discard = cookie.discard; } public Cookie(String name, String value) { this.name = name; this.value = value; } public String getName() { return name; } public Cookie setName(String name) { this.name = name; return this; } public String getValue() { return value; } public Cookie setValue(String value) { this.value = value; return this; } public String getDomain() { return domain; } public Cookie setDomain(String domain) { this.domain = domain; return this; } public String getPath() { return path; } public Cookie setPath(String path) { this.path = path; return this; } @Deprecated public String getComment() { return comment; } @Deprecated public Cookie setComment(String comment) { this.comment = comment; return this; } @Deprecated public String getCommentURL() { return getCommentUrl(); } @Deprecated public Cookie setCommentURL(String commentUrl) { return setCommentUrl(commentUrl); } @Deprecated public String getCommentUrl() { return commentUrl; } @Deprecated public Cookie setCommentUrl(String commentUrl) { this.commentUrl = commentUrl; return this; } public int getMaxAge(TimeUnit unit) { return (int)unit.convert(maxAgeMillis, TimeUnit.MILLISECONDS); } public Cookie setMaxAge(int maxAge, TimeUnit unit) { this.maxAgeMillis = unit.toMillis(maxAge); return this; } @Deprecated public int getVersion() { return version; } @Deprecated public Cookie setVersion(int version) { this.version = version; return this; } public boolean isSecure() { return secure; } public Cookie setSecure(boolean secure) { this.secure = secure; return this; } public boolean isHttpOnly() { return httpOnly; } public Cookie setHttpOnly(boolean httpOnly) { this.httpOnly = httpOnly; return this; } @Deprecated public boolean isDiscard() { return discard; } @Deprecated public Cookie setDiscard(boolean discard) { this.discard = discard; return this; } @Deprecated public Set ports() { return ports; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Cookie cookie = (Cookie) o; return maxAgeMillis == cookie.maxAgeMillis && version == cookie.version && secure == cookie.secure && httpOnly == cookie.httpOnly && discard == cookie.discard && Objects.equals(ports, cookie.ports) && Objects.equals(name, cookie.name) && Objects.equals(value, cookie.value) && Objects.equals(domain, cookie.domain) && Objects.equals(path, cookie.path) && Objects.equals(comment, cookie.comment) && Objects.equals(commentUrl, cookie.commentUrl); } @Override public int hashCode() { return Objects.hash(ports, name, value, domain, path, comment, commentUrl, maxAgeMillis, version, secure, httpOnly, discard); } @Override public String toString() { StringBuilder ret = new StringBuilder(); ret.append(name).append("=").append(value); return ret.toString(); } public static String toCookieHeader(Iterable cookies) { ClientCookieEncoder encoder = ClientCookieEncoder.STRICT; List 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); } public static List fromCookieHeader(String headerVal) { if (headerVal == null) return Collections.emptyList(); ServerCookieDecoder decoder = ServerCookieDecoder.STRICT; Set 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()); } /** * @deprecated Use {@link #toSetCookieHeaderAll(Iterable)} instead. */ @Deprecated public static String toSetCookieHeader(Iterable cookies) { List encodedCookies = toSetCookieHeaderAll(cookies); return encodedCookies.isEmpty() ? null : encodedCookies.get(0); } // TODO Rename to toSetCookieHeader for Vespa 7 public static List toSetCookieHeaderAll(Iterable cookies) { ServerCookieEncoder encoder = ServerCookieEncoder.STRICT; List 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); } // TODO Change return type to Cookie for Vespa 7 public static List 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())); } }