001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2015 ForgeRock AS. 016 */ 017 018package org.forgerock.http.header; 019 020import static java.util.Collections.*; 021import static org.forgerock.http.header.HeaderUtil.*; 022 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026 027import org.forgerock.http.protocol.Cookie; 028import org.forgerock.http.protocol.Header; 029import org.forgerock.http.protocol.Request; 030 031/** 032 * Processes the <strong>{@code Cookie}</strong> request message header. For 033 * more information, see the original <a href= 034 * "http://web.archive.org/web/20070805052634/http://wp.netscape.com/newsref/std/cookie_spec.html" 035 * > Netscape specification</a>, <a 036 * href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a> and <a 037 * href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>. 038 * <p> 039 * Note: This implementation is designed to be forgiving when parsing malformed 040 * cookies. 041 */ 042public class CookieHeader extends Header { 043 private static CookieHeader valueOf(final List<String> values) { 044 List<Cookie> cookies = new ArrayList<>(values.size()); 045 Integer version = null; 046 Cookie cookie = new Cookie(); 047 for (String s1 : values) { 048 for (String s2 : HeaderUtil.split(s1, ';')) { 049 String[] nvp = HeaderUtil.parseParameter(s2); 050 if (nvp[0].length() > 0 && nvp[0].charAt(0) != '$') { 051 if (cookie.getName() != null) { 052 // existing cookie was being parsed 053 cookies.add(cookie); 054 } 055 cookie = new Cookie(); 056 // inherit previous parsed version 057 cookie.setVersion(version); 058 cookie.setName(nvp[0]); 059 cookie.setValue(nvp[1]); 060 } else if ("$Version".equalsIgnoreCase(nvp[0])) { 061 cookie.setVersion(version = parseInteger(nvp[1])); 062 } else if ("$Path".equalsIgnoreCase(nvp[0])) { 063 cookie.setPath(nvp[1]); 064 } else if ("$Domain".equalsIgnoreCase(nvp[0])) { 065 cookie.setDomain(nvp[1]); 066 } else if ("$Port".equalsIgnoreCase(nvp[0])) { 067 cookie.getPort().clear(); 068 parsePorts(cookie.getPort(), nvp[1]); 069 } 070 } 071 } 072 if (cookie.getName() != null) { 073 // last cookie being parsed 074 cookies.add(cookie); 075 } 076 return new CookieHeader(cookies); 077 } 078 079 private static void parsePorts(List<Integer> list, String s) { 080 for (String port : s.split(",")) { 081 Integer p = parseInteger(port); 082 if (p != null) { 083 list.add(p); 084 } 085 } 086 } 087 088 private static Integer parseInteger(String s) { 089 try { 090 return Integer.valueOf(s); 091 } catch (NumberFormatException nfe) { 092 return null; 093 } 094 } 095 096 /** 097 * Constructs a new header, initialized from the specified request message. 098 * 099 * @param message 100 * The request message to initialize the header from. 101 * @return The parsed header. 102 */ 103 public static CookieHeader valueOf(final Request message) { 104 return valueOf(parseMultiValuedHeader(message, NAME)); 105 } 106 107 /** 108 * Constructs a new header, initialized from the specified string value. 109 * 110 * @param string 111 * The value to initialize the header from. 112 * @return The parsed header. 113 */ 114 public static CookieHeader valueOf(final String string) { 115 return valueOf(parseMultiValuedHeader(string)); 116 } 117 118 /** The name of this header. */ 119 public static final String NAME = "Cookie"; 120 121 /** Request message cookies. */ 122 private final List<Cookie> cookies; 123 124 /** 125 * Constructs a new empty header. 126 */ 127 public CookieHeader() { 128 this(new ArrayList<Cookie>(1)); 129 } 130 131 /** 132 * Constructs a new header with the provided cookies. 133 * 134 * @param cookies 135 * The cookies. 136 */ 137 public CookieHeader(List<Cookie> cookies) { 138 this.cookies = cookies; 139 } 140 141 /** 142 * Returns the cookies' request list. 143 * 144 * @return The cookies' request list. 145 */ 146 public List<Cookie> getCookies() { 147 return cookies; 148 } 149 150 @Override 151 public String getName() { 152 return NAME; 153 } 154 155 @Override 156 public List<String> getValues() { 157 boolean quoted = false; 158 Integer version = null; 159 for (Cookie cookie : cookies) { 160 if (cookie.getVersion() != null && (version == null || cookie.getVersion() > version)) { 161 version = cookie.getVersion(); 162 } else if (version == null && (cookie.getPath() != null || cookie.getDomain() != null)) { 163 // presence of extended fields makes it version 1 at minimum 164 version = 1; 165 } 166 } 167 StringBuilder sb = new StringBuilder(); 168 if (version != null) { 169 sb.append("$Version=").append(version.toString()); 170 quoted = true; 171 } 172 for (Cookie cookie : cookies) { 173 if (cookie.getName() != null) { 174 if (sb.length() > 0) { 175 sb.append("; "); 176 } 177 sb.append(cookie.getName()).append('='); 178 sb.append(quoted ? HeaderUtil.quote(cookie.getValue()) : cookie.getValue()); 179 if (cookie.getPath() != null) { 180 sb.append("; $Path=").append(HeaderUtil.quote(cookie.getPath())); 181 } 182 if (cookie.getDomain() != null) { 183 sb.append("; $Domain=").append(HeaderUtil.quote(cookie.getDomain())); 184 } 185 if (cookie.getPort().size() > 0) { 186 sb.append("; $Port=").append(HeaderUtil.quote(portList(cookie.getPort()))); 187 } 188 } 189 } 190 // return null if empty 191 return sb.length() > 0 ? singletonList(sb.toString()) : Collections.<String>emptyList(); 192 } 193 194 private String portList(List<Integer> ports) { 195 StringBuilder sb = new StringBuilder(); 196 for (Integer port : ports) { 197 if (sb.length() > 0) { 198 sb.append(','); 199 } 200 sb.append(port.toString()); 201 } 202 return sb.toString(); 203 } 204 205 static class Factory extends HeaderFactory<CookieHeader> { 206 207 @Override 208 public CookieHeader parse(String value) { 209 return valueOf(value); 210 } 211 212 @Override 213 public CookieHeader parse(List<String> values) { 214 return valueOf(values); 215 } 216 } 217}