diff --git a/http-framework/http-core/src/main/java/org/forgerock/http/header/CookieHeader.java b/http-framework/http-core/src/main/java/org/forgerock/http/header/CookieHeader.java index 719d7e675..1a71709d9 100644 --- a/http-framework/http-core/src/main/java/org/forgerock/http/header/CookieHeader.java +++ b/http-framework/http-core/src/main/java/org/forgerock/http/header/CookieHeader.java @@ -13,86 +13,47 @@ * * Copyright 2010–2011 ApexIdentity Inc. * Portions Copyright 2011-2015 ForgeRock AS. + * Portions Copyright 2026 Wren Security */ package org.forgerock.http.header; -import static java.util.Collections.*; -import static org.forgerock.http.header.HeaderUtil.*; +import static java.util.Collections.singletonList; +import static org.forgerock.http.header.HeaderUtil.parseMultiValuedHeader; import java.util.ArrayList; import java.util.Collections; import java.util.List; - import org.forgerock.http.protocol.Cookie; import org.forgerock.http.protocol.Header; import org.forgerock.http.protocol.Request; /** - * Processes the {@code Cookie} request message header. For - * more information, see the original Netscape specification, RFC 2109 and RFC 2965. + * Processes the {@code Cookie} request message header. + * + *

+ * For more information see RFC 6265. *

- * Note: This implementation is designed to be forgiving when parsing malformed - * cookies. + * Note: This implementation is designed to be forgiving when parsing malformed cookies. */ public class CookieHeader extends Header { private static CookieHeader valueOf(final List values) { List cookies = new ArrayList<>(values.size()); - Integer version = null; - Cookie cookie = new Cookie(); for (String s1 : values) { for (String s2 : HeaderUtil.split(s1, ';')) { String[] nvp = HeaderUtil.parseParameter(s2); - if (nvp[0].length() > 0 && nvp[0].charAt(0) != '$') { - if (cookie.getName() != null) { - // existing cookie was being parsed - cookies.add(cookie); - } - cookie = new Cookie(); - // inherit previous parsed version - cookie.setVersion(version); - cookie.setName(nvp[0]); - cookie.setValue(nvp[1]); - } else if ("$Version".equalsIgnoreCase(nvp[0])) { - cookie.setVersion(version = parseInteger(nvp[1])); - } else if ("$Path".equalsIgnoreCase(nvp[0])) { - cookie.setPath(nvp[1]); - } else if ("$Domain".equalsIgnoreCase(nvp[0])) { - cookie.setDomain(nvp[1]); - } else if ("$Port".equalsIgnoreCase(nvp[0])) { - cookie.getPort().clear(); - parsePorts(cookie.getPort(), nvp[1]); + if (nvp[0].isEmpty()) { + continue; // ignore empty cookie pair + } else if (nvp[0].startsWith("$")) { + continue; // ignore legacy cookie attributes + } else if (nvp.length > 1){ + cookies.add(new Cookie(nvp[0], nvp[1])); } } } - if (cookie.getName() != null) { - // last cookie being parsed - cookies.add(cookie); - } return new CookieHeader(cookies); } - private static void parsePorts(List list, String s) { - for (String port : s.split(",")) { - Integer p = parseInteger(port); - if (p != null) { - list.add(p); - } - } - } - - private static Integer parseInteger(String s) { - try { - return Integer.valueOf(s); - } catch (NumberFormatException nfe) { - return null; - } - } - /** * Constructs a new header, initialized from the specified request message. * @@ -154,52 +115,17 @@ public String getName() { @Override public List getValues() { - boolean quoted = false; - Integer version = null; - for (Cookie cookie : cookies) { - if (cookie.getVersion() != null && (version == null || cookie.getVersion() > version)) { - version = cookie.getVersion(); - } else if (version == null && (cookie.getPath() != null || cookie.getDomain() != null)) { - // presence of extended fields makes it version 1 at minimum - version = 1; - } - } StringBuilder sb = new StringBuilder(); - if (version != null) { - sb.append("$Version=").append(version.toString()); - quoted = true; - } for (Cookie cookie : cookies) { if (cookie.getName() != null) { if (sb.length() > 0) { sb.append("; "); } sb.append(cookie.getName()).append('='); - sb.append(quoted ? HeaderUtil.quote(cookie.getValue()) : cookie.getValue()); - if (cookie.getPath() != null) { - sb.append("; $Path=").append(HeaderUtil.quote(cookie.getPath())); - } - if (cookie.getDomain() != null) { - sb.append("; $Domain=").append(HeaderUtil.quote(cookie.getDomain())); - } - if (cookie.getPort().size() > 0) { - sb.append("; $Port=").append(HeaderUtil.quote(portList(cookie.getPort()))); - } - } - } - // return null if empty - return sb.length() > 0 ? singletonList(sb.toString()) : Collections.emptyList(); - } - - private String portList(List ports) { - StringBuilder sb = new StringBuilder(); - for (Integer port : ports) { - if (sb.length() > 0) { - sb.append(','); + sb.append(HeaderUtil.quote(cookie.getValue())); } - sb.append(port.toString()); } - return sb.toString(); + return sb.length() > 0 ? singletonList(sb.toString()) : Collections.emptyList(); } static class Factory extends HeaderFactory { diff --git a/http-framework/http-core/src/main/java/org/forgerock/http/header/SetCookieHeader.java b/http-framework/http-core/src/main/java/org/forgerock/http/header/SetCookieHeader.java index b4394e44e..d5b6abd7c 100644 --- a/http-framework/http-core/src/main/java/org/forgerock/http/header/SetCookieHeader.java +++ b/http-framework/http-core/src/main/java/org/forgerock/http/header/SetCookieHeader.java @@ -12,16 +12,18 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2015-2016 ForgeRock AS. + * Portions Copyright 2026 Wren Security */ package org.forgerock.http.header; -import static java.util.Collections.*; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - +import java.util.Map; import org.forgerock.http.protocol.Cookie; import org.forgerock.http.protocol.Header; import org.forgerock.http.protocol.Response; @@ -52,24 +54,13 @@ public static SetCookieHeader valueOf(String value) { private static Cookie parseCookie(String value) { List parts = Arrays.asList(value.split(";")); - Cookie cookie = new Cookie(); + Cookie cookie = null; for (String part : parts) { String[] nvp = part.split("=", 2); - if ("Expires".equalsIgnoreCase(nvp[0].trim())) { - cookie.setExpires(HeaderUtil.parseDate(nvp[1].trim())); - } else if ("Max-Age".equalsIgnoreCase(nvp[0].trim())) { - cookie.setMaxAge(parseInteger(nvp[1].trim())); - } else if ("Path".equalsIgnoreCase(nvp[0].trim())) { - cookie.setPath(nvp[1]); - } else if ("Domain".equalsIgnoreCase(nvp[0].trim())) { - cookie.setDomain(nvp[1]); - } else if ("Secure".equalsIgnoreCase(nvp[0].trim())) { - cookie.setSecure(true); - } else if ("HttpOnly".equalsIgnoreCase(nvp[0].trim())) { - cookie.setHttpOnly(true); - } else if (cookie.getName() == null || cookie.getName().isEmpty()) { - cookie.setName(nvp[0].trim()); - cookie.setValue(nvp[1].trim()); + if (cookie == null) { + cookie = new Cookie(nvp[0].trim(), nvp.length > 1 ? nvp[1].trim() : null); + } else { + cookie.setAttribute(nvp[0].trim(), nvp.length > 1 ? nvp[1].trim() : Boolean.TRUE.toString()); } } if (cookie.getName() == null || cookie.getName().isEmpty()) { @@ -110,14 +101,6 @@ public static SetCookieHeader valueOf(List values) { return new SetCookieHeader(unmodifiableList(cookies)); } - private static Integer parseInteger(String s) { - try { - return Integer.valueOf(s); - } catch (NumberFormatException nfe) { - return null; - } - } - private final List cookies; private final List values; @@ -161,23 +144,16 @@ private String toString(Cookie cookie) { StringBuilder sb = new StringBuilder(); if (cookie.getName() != null) { sb.append(cookie.getName()).append("=").append(cookie.getValue()); - if (cookie.getExpires() != null) { - sb.append("; ").append("Expires").append("=").append(HeaderUtil.formatDate(cookie.getExpires())); - } - if (cookie.getMaxAge() != null ) { - sb.append("; ").append("Max-Age").append("=").append(cookie.getMaxAge()); - } - if (cookie.getPath() != null) { - sb.append("; ").append("Path").append("=").append(cookie.getPath()); - } - if (cookie.getDomain() != null) { - sb.append("; ").append("Domain").append("=").append(cookie.getDomain()); - } - if (cookie.isSecure() != null && cookie.isSecure()) { - sb.append("; ").append("Secure"); - } - if (cookie.isHttpOnly() != null && cookie.isHttpOnly()) { - sb.append("; ").append("HttpOnly"); + Map attributes = cookie.getAttributes(); + for (String name : attributes.keySet()) { + String value = attributes.get(name); + if (value == null) { + continue; // unexpected null value (better safe than sorry) + } else if (name.equalsIgnoreCase("HttpOnly") || name.equalsIgnoreCase("Secure")) { + sb.append("; ").append(name); + } else { + sb.append("; ").append(name).append("=").append(value); + } } } return sb.toString(); diff --git a/http-framework/http-core/src/main/java/org/forgerock/http/protocol/Cookie.java b/http-framework/http-core/src/main/java/org/forgerock/http/protocol/Cookie.java index 8910d651a..1417e426b 100644 --- a/http-framework/http-core/src/main/java/org/forgerock/http/protocol/Cookie.java +++ b/http-framework/http-core/src/main/java/org/forgerock/http/protocol/Cookie.java @@ -13,83 +13,64 @@ * * Copyright 2010–2011 ApexIdentity Inc. * Portions Copyright 2011-2015 ForgeRock AS. + * Portions Copyright 2026 Wren Security */ package org.forgerock.http.protocol; -import java.util.ArrayList; +import java.util.Collections; import java.util.Date; -import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.forgerock.http.header.HeaderUtil; +import org.wrensecurity.guava.common.base.Objects; /** - * An HTTP cookie. For more information, see the original Netscape specification, RFC 2109 and RFC 2965. + * An HTTP cookie. + * + *

+ * For more information see RFC 6265. */ public class Cookie { - /** The name of the cookie. */ - private String name; - /** The value of the cookie. */ - private String value; + private static final String MAX_AGE_ATTR_NAME = "Max-Age"; - /** The intended use of a cookie. */ - private String comment; + private static final String EXPIRES_ATTR_NAME = "Expires"; - /** URL identifying the intended use of a cookie. */ - private String commentURL; + private static final String DOMAIN_ATTR_NAME = "Domain"; - /** - * Directs the user agent to discard the cookie unconditionally when it - * terminates. - */ - private Boolean discard; + private static final String PATH_ATTR_NAME = "Path"; - /** The domain for which the cookie is valid. */ - private String domain; + private static final String SECURE_ATTR_NAME = "Secure"; - /** - * The lifetime of the cookie, expressed as the date and time of expiration. - */ - private Date expires; - - /** - * Directs the user agent to make the cookie inaccessible to client side - * script. - */ - private Boolean httpOnly; + private static final String HTTPONLY_ATTR_NAME = "HttpOnly"; - /** The lifetime of the cookie, expressed in seconds. */ - private Integer maxAge; - - /** The subset of URLs on the origin server to which this cookie applies. */ - private String path; + /** The name of the cookie. */ + private String name; - /** Restricts the port(s) to which a cookie may be returned. */ - private final List port = new ArrayList<>(); + /** The value of the cookie. */ + private String value; - /** - * Directs the user agent to use only secure means to send back this cookie. - */ - private Boolean secure; + /** Additional cookie attribute-value pairs. */ + private Map attributes; /** - * The version of the state management mechanism to which this cookie - * conforms. + * Create a new uninitialized cookie. */ - private Integer version; + public Cookie() { + // Empty cookie. + } /** - * Creates a new uninitialized cookie. + * Create a new cookie with the given name and value. */ - public Cookie() { - // Empty cookie. + public Cookie(String name, String value) { + this.name = name; + this.value = value; } @Override - public boolean equals(final Object obj) { + public boolean equals(Object obj) { if (this == obj) { return true; } @@ -97,379 +78,239 @@ public boolean equals(final Object obj) { return false; } final Cookie other = (Cookie) obj; - return objectsAreEqual(comment, other.comment) - && objectsAreEqual(commentURL, other.commentURL) - && objectsAreEqual(discard, other.discard) - && objectsAreEqual(domain, other.domain) - && objectsAreEqual(expires, other.expires) - && objectsAreEqual(httpOnly, other.httpOnly) - && objectsAreEqual(maxAge, other.maxAge) - && objectsAreEqual(name, other.name) - && objectsAreEqual(path, other.path) - && objectsAreEqual(port, other.port) - && objectsAreEqual(secure, other.secure) - && objectsAreEqual(value, other.value) - && objectsAreEqual(version, other.version); + return Objects.equal(name, other.name) + && Objects.equal(value, other.value) + && Objects.equal(attributes, other.attributes); } - /** - * Returns the intended use of a cookie. - * - * @return The intended use of a cookie. - */ - public String getComment() { - return comment; + @Override + public int hashCode() { + return Objects.hashCode(name, value, attributes); } /** - * Returns the URL identifying the intended use of a cookie. + * Get all cookie attributes. * - * @return The URL identifying the intended use of a cookie. + * @return cookie attributes or empty map if none defined */ - public String getCommentURL() { - return commentURL; + public Map getAttributes() { + return attributes == null ? Collections.emptyMap() : Collections.unmodifiableMap(attributes); } /** - * Returns {@code true} if the user agent should discard the cookie - * unconditionally when it terminates. + * Get cookie attribute value. * - * @return {@code true} if the user agent should discard the cookie - * unconditionally when it terminates. + * @param name cookie attribute name + * @return cookie attribute value or null if no such attribute has been set */ - public Boolean getDiscard() { - return discard; + public String getAttribute(String name) { + return attributes != null ? attributes.get(name) : null; } /** - * Returns the domain for which the cookie is valid. + * Set cookie attribute value or remove existing value by setting null. * - * @return The domain for which the cookie is valid. - */ - public String getDomain() { - return domain; - } - - /** - * Returns the lifetime of the cookie, expressed as the date and time of - * expiration. + * @param name cookie attribute name + * @param value cookie attribute value to set or null to remove any previously set value + * @return this cookie * - * @return The lifetime of the cookie, expressed as the date and time of - * expiration. + * @throws IllegalArgumentException in case the attribute name is null or empty */ - public Date getExpires() { - return expires; - } + public Cookie setAttribute(String name, String value) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Cookie attribute name can not be empty"); + } - /** - * Returns {@code true} if the user agent should make the cookie - * inaccessible to client side script. - * - * @return {@code true} if the user agent should make the cookie - * inaccessible to client side script. - */ - public Boolean isHttpOnly() { - return httpOnly == null ? false : httpOnly; + if (EXPIRES_ATTR_NAME.equalsIgnoreCase(name)) { + return setExpires(HeaderUtil.parseDate(value)); + } else if (MAX_AGE_ATTR_NAME.equalsIgnoreCase(name)) { + return setMaxAge(value != null ? parseInteger(value) : null); + } else if (HTTPONLY_ATTR_NAME.equalsIgnoreCase(name) || SECURE_ATTR_NAME.equalsIgnoreCase(name)) { + return putAttribute(name, Boolean.parseBoolean(value) ? "true" : null); + } else { + return putAttribute(name, value); + } } - /** - * Returns the lifetime of the cookie, expressed in seconds. - * - * @return The lifetime of the cookie, expressed in seconds. - */ - public Integer getMaxAge() { - return maxAge; + private Integer parseInteger(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return null; + } } - /** - * Returns name of the cookie. - * - * @return The name of the cookie. - */ - public String getName() { - return name; - } + private Cookie putAttribute(String name, String value) { + if (attributes == null) { + attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + } - /** - * Returns the subset of URLs on the origin server to which this cookie - * applies. - * - * @return The subset of URLs on the origin server to which this cookie - * applies. - */ - public String getPath() { - return path; + if (value != null) { + attributes.put(name, value); + } else { + attributes.remove(name); + } + return this; } /** - * Returns the restricted list of port(s) to which a cookie may be returned. + * Get name of the cookie. * - * @return The restricted list of port(s) to which a cookie may be returned. + * @return the name of the cookie */ - public List getPort() { - return port; + public String getName() { + return name; } /** - * Returns {@code true} if the user agent should use only secure means to - * send back this cookie. + * Set the name of the cookie. * - * @return {@code true} if the user agent should use only secure means to - * send back this cookie. + * @param name the name of the cookie + * @return this cookie */ - public Boolean isSecure() { - return secure == null ? false : secure; + public Cookie setName(String name) { + this.name = name; + return this; } /** - * Returns the value of the cookie. + * Get the value of the cookie. * - * @return The value of the cookie. + * @return the value of the cookie */ public String getValue() { return value; } /** - * Returns the version of the state management mechanism to which this - * cookie conforms. + * Sets the value of the cookie. * - * @return The version of the state management mechanism to which this - * cookie conforms. + * @param value the value of the cookie + * @return this cookie */ - public Integer getVersion() { - return version; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (comment == null ? 0 : comment.hashCode()); - result = prime * result + (commentURL == null ? 0 : commentURL.hashCode()); - result = prime * result + (discard == null ? 0 : discard.hashCode()); - result = prime * result + (domain == null ? 0 : domain.hashCode()); - result = prime * result + (expires == null ? 0 : expires.hashCode()); - result = prime * result + (httpOnly == null ? 0 : httpOnly.hashCode()); - result = prime * result + (maxAge == null ? 0 : maxAge.hashCode()); - result = prime * result + (name == null ? 0 : name.hashCode()); - result = prime * result + (path == null ? 0 : path.hashCode()); - result = prime * result + (port == null ? 0 : port.hashCode()); - result = prime * result + (secure == null ? 0 : secure.hashCode()); - result = prime * result + (value == null ? 0 : value.hashCode()); - result = prime * result + (version == null ? 0 : version.hashCode()); - return result; + public Cookie setValue(String value) { + this.value = value; + return this; } /** - * Sets the intended use of a cookie. + * Get the domain for which the cookie is valid. * - * @param comment - * The intended use of a cookie. - * @return This cookie. + * @return the domain for which the cookie is valid */ - public Cookie setComment(final String comment) { - this.comment = comment; - return this; + public String getDomain() { + return getAttribute(DOMAIN_ATTR_NAME); } /** - * Sets the URL identifying the intended use of a cookie. + * Set the domain for which the cookie is valid. * - * @param commentURL - * The URL identifying the intended use of a cookie. - * @return This cookie. + * @param domain the domain for which the cookie is valid + * @return this cookie */ - public Cookie setCommentURL(final String commentURL) { - this.commentURL = commentURL; - return this; + public Cookie setDomain(String domain) { + return putAttribute(DOMAIN_ATTR_NAME, domain); } /** - * Sets the value indicating whether the user agent should discard the - * cookie unconditionally when it terminates. + * Get {@code true} if the user agent should make the cookie inaccessible to client side script. * - * @param discard - * {@code true} if the user agent should discard the cookie - * unconditionally when it terminates. - * @return This cookie. + * @return {@code true} if the user agent should make the cookie inaccessible to client side script. */ - public Cookie setDiscard(final Boolean discard) { - this.discard = discard; - return this; + public Boolean isHttpOnly() { + return Boolean.parseBoolean(getAttribute(HTTPONLY_ATTR_NAME)); } /** - * Sets the domain for which the cookie is valid. + * Set the value indicating whether the user agent should make the cookie inaccessible to client side script. * - * @param domain - * The domain for which the cookie is valid. - * @return This cookie. + * @param httpOnly {@code true} if the user agent should make the cookie inaccessible to client side script + * @return this cookie */ - public Cookie setDomain(final String domain) { - this.domain = domain; - return this; + public Cookie setHttpOnly(boolean httpOnly) { + return putAttribute(HTTPONLY_ATTR_NAME, httpOnly ? "true" : null); } /** - * Sets the lifetime of the cookie, expressed as the date and time of - * expiration. + * Get the lifetime of the cookie, expressed as the date and time of expiration. * - * @param expires - * The lifetime of the cookie, expressed as the date and time of - * expiration. - * @return This cookie. + * @return The lifetime of the cookie, expressed as the date and time of expiration. */ - public Cookie setExpires(final Date expires) { - this.expires = expires; - return this; + public Date getExpires() { + return HeaderUtil.parseDate(getAttribute(EXPIRES_ATTR_NAME)); } /** - * Sets the value indicating whether the user agent should make the cookie - * inaccessible to client side script. + * Set the lifetime of the cookie, expressed as the date and time of expiration. * - * @param httpOnly - * {@code true} if the user agent should make the cookie - * inaccessible to client side script. - * @return this; + * @param expires the lifetime of the cookie, expressed as the date and time of expiration + * @return this cookie */ - public Cookie setHttpOnly(final Boolean httpOnly) { - this.httpOnly = httpOnly; - return this; + public Cookie setExpires(Date expires) { + return putAttribute(EXPIRES_ATTR_NAME, expires != null ? HeaderUtil.formatDate(expires) : null); } /** - * Sets the lifetime of the cookie, expressed in seconds. + * Get the lifetime of the cookie, expressed in seconds. * - * @param maxAge - * The lifetime of the cookie, expressed in seconds. - * @return This cookie. + * @return the lifetime of the cookie, expressed in seconds */ - public Cookie setMaxAge(final Integer maxAge) { - this.maxAge = maxAge; - return this; + public Integer getMaxAge() { + String maxAge = getAttribute(MAX_AGE_ATTR_NAME); + return maxAge != null ? Integer.parseInt(maxAge) : null; } /** - * Sets the name of the cookie. + * Set the lifetime of the cookie, expressed in seconds. * - * @param name - * The name of the cookie. - * @return This cookie. + * @param maxAge the lifetime of the cookie, expressed in seconds + * @return this cookie */ - public Cookie setName(final String name) { - this.name = name; - return this; + public Cookie setMaxAge(Integer maxAge) { + return putAttribute(MAX_AGE_ATTR_NAME, maxAge != null ? maxAge.toString() : null); } /** - * Sets the subset of URLs on the origin server to which this cookie - * applies. + * Get the subset of URLs on the origin server to which this cookie applies. * - * @param path - * The subset of URLs on the origin server to which this cookie - * applies. - * @return This cookie. + * @return the subset of URLs on the origin server to which this cookie applies */ - public Cookie setPath(final String path) { - this.path = path; - return this; + public String getPath() { + return getAttribute(PATH_ATTR_NAME); } /** - * Sets the value indicating whether the user agent should use only secure - * means to send back this cookie. + * Set the subset of URLs on the origin server to which this cookie applies. * - * @param secure - * {@code true} if the user agent should use only secure means to - * send back this cookie. - * @return This cookie. + * @param path the subset of URLs on the origin server to which this cookie applies + * @return this cookie */ - public Cookie setSecure(final Boolean secure) { - this.secure = secure; - return this; + public Cookie setPath(String path) { + return putAttribute(PATH_ATTR_NAME, path); } /** - * Sets the value of the cookie. + * Get flag indicating if the user agent should use only secure means to send back this cookie. * - * @param value - * The value of the cookie. - * @return This cookie. + * @return {@code true} if the user agent should use only secure means to send back this cookie */ - public Cookie setValue(final String value) { - this.value = value; - return this; + public Boolean isSecure() { + return Boolean.parseBoolean(getAttribute(SECURE_ATTR_NAME)); } /** - * Sets the version of the state management mechanism to which this cookie - * conforms. + * Set the value indicating whether the user agent should use only secure means to send back this cookie. * - * @param version - * The version of the state management mechanism to which this - * cookie conforms. - * @return This cookie. + * @param secure {@code true} if the user agent should use only secure means to send back this cookie + * @return this cookie */ - public Cookie setVersion(final Integer version) { - this.version = version; - return this; + public Cookie setSecure(boolean secure) { + return putAttribute(SECURE_ATTR_NAME, secure ? "true" : null); } @Override public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("["); - if (name != null) { - builder.append("name=").append(name).append(" "); - } - if (value != null) { - builder.append("value=").append(value).append(" "); - } - if (comment != null) { - builder.append("comment=").append(comment).append(" "); - } - if (commentURL != null) { - builder.append("commentURL=").append(commentURL).append(" "); - } - if (discard != null) { - builder.append("discard=").append(discard).append(" "); - } - if (domain != null) { - builder.append("domain=").append(domain).append(" "); - } - if (expires != null) { - builder.append("expires=").append(expires).append(" "); - } - if (httpOnly != null) { - builder.append("httpOnly=").append(httpOnly).append(" "); - } - if (maxAge != null) { - builder.append("maxAge=").append(maxAge).append(" "); - } - if (path != null) { - builder.append("path=").append(path).append(" "); - } - if (port != null) { - builder.append("port=").append(port).append(" "); - } - if (secure != null) { - builder.append("secure=").append(secure).append(" "); - } - if (version != null) { - builder.append("version=").append(version); - } - builder.append("]"); - return builder.toString(); - } - - private static boolean objectsAreEqual(final Object o1, final Object o2) { - if (o1 == null) { - return o2 == null; - } else { - return o1.equals(o2); - } + return String.format("Cookie[%s=%s,%s]", name, value, attributes); } } diff --git a/http-framework/http-core/src/test/java/org/forgerock/http/header/CookieHeaderTest.java b/http-framework/http-core/src/test/java/org/forgerock/http/header/CookieHeaderTest.java index b4952a0a1..1b5e20f39 100644 --- a/http-framework/http-core/src/test/java/org/forgerock/http/header/CookieHeaderTest.java +++ b/http-framework/http-core/src/test/java/org/forgerock/http/header/CookieHeaderTest.java @@ -12,6 +12,7 @@ * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2014-2016 ForgeRock AS. + * Portions Copyright 2026 Wren Security */ package org.forgerock.http.header; @@ -29,9 +30,9 @@ /** * Unit tests for the cookie header class. + * *

- * See http://www.ietf.org/rfc/rfc2109.txt - *

+ * See https://www.rfc-editor.org/rfc/rfc6265.txt */ @SuppressWarnings("javadoc") public class CookieHeaderTest { @@ -45,12 +46,7 @@ public void testCookieHeaderFromString() throws Exception { final CookieHeader ch = CookieHeader.valueOf(CHEADER_1); assertEquals(ch.getCookies().size(), 1); final Cookie cookie = ch.getCookies().get(0); - assertEquals(cookie.getVersion().intValue(), 1); assertEquals(cookie.getValue(), "BAB_JENSEN"); - assertEquals(cookie.getPath(), "/example"); - assertEquals(cookie.getPort().size(), 2); - assertEquals(cookie.getPort().get(0).intValue(), 42); - assertEquals(cookie.getPort().get(1).intValue(), 13); assertEquals(ch.getName(), NAME); } @@ -59,11 +55,7 @@ public void testCookieHeaderFromString2() throws Exception { final CookieHeader ch = CookieHeader.valueOf(CHEADER_2); assertEquals(ch.getCookies().size(), 1); final Cookie cookie = ch.getCookies().get(0); - assertEquals(cookie.getVersion().intValue(), 2); assertEquals(cookie.getValue(), "SAM_CARTER"); - assertEquals(cookie.getPath(), "/example"); - assertEquals(cookie.getPort().size(), 0); - assertEquals(cookie.getDomain(), "example.com"); assertEquals(ch.getName(), NAME); } @@ -72,7 +64,6 @@ public void testCookieHeaderFromStringAllowsNullVersion() throws Exception { final CookieHeader ch = CookieHeader.valueOf("Customer=\"BAB_JENSEN\"; $Path=\"/example\""); assertEquals(ch.getCookies().size(), 1); final Cookie cookie = ch.getCookies().get(0); - assertNull(cookie.getVersion()); assertEquals(cookie.getValue(), "BAB_JENSEN"); assertEquals(ch.getName(), NAME); } @@ -84,7 +75,6 @@ public void testCookieHeaderFromStringAllowsInvalidVersion() throws Exception { .valueOf("$Version=invalid; Customer=\"BAB_JENSEN\"; $Path=\"/example\""); assertEquals(ch.getCookies().size(), 1); final Cookie cookie = ch.getCookies().get(0); - assertNull(cookie.getVersion()); assertEquals(cookie.getValue(), "BAB_JENSEN"); assertEquals(ch.getName(), NAME); } @@ -103,27 +93,7 @@ public void testCookieHeaderFromStringAllowsNullMessage() { @Test public void testCookieHeaderToString() { - assertThat(CookieHeader.valueOf(CHEADER_1).getValues()).containsOnly(CHEADER_1); - } - - - @Test - public void testCookieHeaderToStringInsertVersionWhenPathOrDomainArePresent() { - CookieHeader ch = CookieHeader.valueOf("Customer=\"SAM_CARTER\";"); - assertNull(ch.getCookies().get(0).getVersion()); - assertThat(ch.toString()).doesNotContain("$Version=1;"); - - ch = CookieHeader.valueOf("Customer=\"SAM_CARTER\"; $Path=\"/example\""); - assertNull(ch.getCookies().get(0).getVersion()); - assertThat(ch.getValues().iterator().next()).contains("$Version=1;"); - - ch = CookieHeader.valueOf("Customer=\"SAM_CARTER\"; $Domain=\"example.com\""); - assertNull(ch.getCookies().get(0).getVersion()); - assertThat(ch.getValues().iterator().next()).contains("$Version=1;"); - - ch = CookieHeader.valueOf("Customer=\"SAM_CARTER\"; $Domain=\"example.com\"; $Version=2"); - assertEquals(ch.getCookies().get(0).getVersion().intValue(), 2); - assertThat(ch.getValues().iterator().next()).doesNotContain("$Version=1;"); + assertThat(CookieHeader.valueOf(CHEADER_1).getValues()).containsOnly("Customer=\"BAB_JENSEN\""); } @Test @@ -135,7 +105,7 @@ public void testCookieHeaderToResponseMessage() { response.getHeaders().add(ch); assertNotNull(response.getHeaders().get("cookie")); assertNull(response.getHeaders().get("Customer")); - assertThat(response.getHeaders().get("cookie").getValues()).containsOnly(CHEADER_1); + assertThat(response.getHeaders().get("cookie").getValues()).containsOnly("Customer=\"BAB_JENSEN\""); } @Test @@ -151,8 +121,6 @@ public void testCookieHeaderToRequestMessage() { final Cookie cookie = request.getCookies().get("Customer").get(0); assertEquals(cookie.getName(), "Customer"); - assertEquals(cookie.getVersion().intValue(), 1); - assertEquals(cookie.getPort().size(), 2); assertEquals(cookie.getValue(), "BAB_JENSEN"); } diff --git a/http-framework/http-core/src/test/java/org/forgerock/http/header/SetCookieHeaderTest.java b/http-framework/http-core/src/test/java/org/forgerock/http/header/SetCookieHeaderTest.java index 2b867e983..b9016e5d9 100644 --- a/http-framework/http-core/src/test/java/org/forgerock/http/header/SetCookieHeaderTest.java +++ b/http-framework/http-core/src/test/java/org/forgerock/http/header/SetCookieHeaderTest.java @@ -12,15 +12,16 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2015-2016 ForgeRock AS. + * Portions Copyright 2026 Wren Security */ package org.forgerock.http.header; -import static java.util.Collections.*; -import static org.assertj.core.api.Assertions.*; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collections; import java.util.Date; - import org.forgerock.http.protocol.Cookie; import org.testng.annotations.Test; @@ -39,9 +40,7 @@ public class SetCookieHeaderTest { public void shouldCreateSetCookieHeaderWithNameAndValue() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE"); + Cookie cookie = new Cookie("NAME", "VALUE"); //When SetCookieHeader setCookieHeader = new SetCookieHeader(singletonList(cookie)); @@ -54,9 +53,7 @@ public void shouldCreateSetCookieHeaderWithNameAndValue() { public void shouldCreateSetCookieHeaderWithExpires() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setExpires(EXPIRES_DATE); //When @@ -70,9 +67,7 @@ public void shouldCreateSetCookieHeaderWithExpires() { public void shouldCreateSetCookieHeaderWithMaxAge() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setMaxAge(100); //When @@ -86,9 +81,7 @@ public void shouldCreateSetCookieHeaderWithMaxAge() { public void shouldCreateSetCookieHeaderWithZeroMaxAge() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setMaxAge(0); //When @@ -102,9 +95,7 @@ public void shouldCreateSetCookieHeaderWithZeroMaxAge() { public void shouldCreateSetCookieHeaderWithPath() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setPath("/path"); //When @@ -118,9 +109,7 @@ public void shouldCreateSetCookieHeaderWithPath() { public void shouldCreateSetCookieHeaderWithDomain() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setDomain("DOMAIN"); //When @@ -134,9 +123,7 @@ public void shouldCreateSetCookieHeaderWithDomain() { public void shouldCreateSetCookieHeaderWithSecure() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setSecure(true); //When @@ -150,9 +137,7 @@ public void shouldCreateSetCookieHeaderWithSecure() { public void shouldCreateSetCookieHeaderWithHttpOnly() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setHttpOnly(true); //When @@ -166,9 +151,7 @@ public void shouldCreateSetCookieHeaderWithHttpOnly() { public void shouldCreateSetCookieHeaderWithAttributes() { //Given - Cookie cookie = new Cookie() - .setName("NAME") - .setValue("VALUE") + Cookie cookie = new Cookie("NAME", "VALUE") .setExpires(EXPIRES_DATE) .setMaxAge(100) .setPath("/path") @@ -180,16 +163,15 @@ public void shouldCreateSetCookieHeaderWithAttributes() { SetCookieHeader setCookieHeader = new SetCookieHeader(singletonList(cookie)); //Then - assertThat(setCookieHeader.getValues()).containsOnly("NAME=VALUE; Expires=" + EXPIRES_DATE_STRING - + "; Max-Age=100; Path=/path; Domain=DOMAIN; Secure; HttpOnly"); + assertThat(setCookieHeader.getValues()).containsOnly("NAME=VALUE; Domain=DOMAIN" + + "; Expires=" + EXPIRES_DATE_STRING + "; HttpOnly; Max-Age=100; Path=/path; Secure"); } @Test public void shouldCreateEmptySetCookieHeaderWhenCookieHasNoName() { //Given - Cookie cookie = new Cookie() - .setValue("VALUE") + Cookie cookie = new Cookie(null, "VALUE") .setExpires(EXPIRES_DATE) .setMaxAge(100) .setPath("/path") @@ -223,6 +205,7 @@ public void shouldParseSetCookieHeaderWithNameAndValue() { assertThat(cookie.getDomain()).isNull(); assertThat(cookie.isSecure()).isFalse(); assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getAttributes().isEmpty()); } @Test @@ -238,12 +221,7 @@ public void shouldParseSetCookieHeaderWithExpires() { Cookie cookie = setCookieHeader.getCookies().iterator().next(); assertThat(cookie.getName()).isEqualTo("NAME"); assertThat(cookie.getValue()).isEqualTo("VALUE"); - assertThat(cookie.getExpires()).isEqualTo(EXPIRES_DATE); - assertThat(cookie.getMaxAge()).isNull(); - assertThat(cookie.getPath()).isNull(); - assertThat(cookie.getDomain()).isNull(); - assertThat(cookie.isSecure()).isFalse(); - assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getAttributes()).isEqualTo(Collections.singletonMap("Expires", EXPIRES_DATE_STRING)); } @Test