CookieHeader.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2010–2011 ApexIdentity Inc.
* Portions Copyright 2011-2015 ForgeRock AS.
*/
package org.forgerock.http.header;
import static java.util.Collections.*;
import static org.forgerock.http.header.HeaderUtil.*;
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 <strong>{@code Cookie}</strong> request message header. For
* more information, see the original <a href=
* "http://web.archive.org/web/20070805052634/http://wp.netscape.com/newsref/std/cookie_spec.html"
* > Netscape specification</a>, <a
* href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a> and <a
* href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>.
* <p>
* Note: This implementation is designed to be forgiving when parsing malformed
* cookies.
*/
public class CookieHeader extends Header {
private static CookieHeader valueOf(final List<String> values) {
List<Cookie> 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 (cookie.getName() != null) {
// last cookie being parsed
cookies.add(cookie);
}
return new CookieHeader(cookies);
}
private static void parsePorts(List<Integer> 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.
*
* @param message
* The request message to initialize the header from.
* @return The parsed header.
*/
public static CookieHeader valueOf(final Request message) {
return valueOf(parseMultiValuedHeader(message, NAME));
}
/**
* Constructs a new header, initialized from the specified string value.
*
* @param string
* The value to initialize the header from.
* @return The parsed header.
*/
public static CookieHeader valueOf(final String string) {
return valueOf(parseMultiValuedHeader(string));
}
/** The name of this header. */
public static final String NAME = "Cookie";
/** Request message cookies. */
private final List<Cookie> cookies;
/**
* Constructs a new empty header.
*/
public CookieHeader() {
this(new ArrayList<Cookie>(1));
}
/**
* Constructs a new header with the provided cookies.
*
* @param cookies
* The cookies.
*/
public CookieHeader(List<Cookie> cookies) {
this.cookies = cookies;
}
/**
* Returns the cookies' request list.
*
* @return The cookies' request list.
*/
public List<Cookie> getCookies() {
return cookies;
}
@Override
public String getName() {
return NAME;
}
@Override
public List<String> 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.<String>emptyList();
}
private String portList(List<Integer> ports) {
StringBuilder sb = new StringBuilder();
for (Integer port : ports) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(port.toString());
}
return sb.toString();
}
static class Factory extends HeaderFactory<CookieHeader> {
@Override
public CookieHeader parse(String value) {
return valueOf(value);
}
@Override
public CookieHeader parse(List<String> values) {
return valueOf(values);
}
}
}