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-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.header; 019 020import java.util.ArrayList; 021import java.util.List; 022 023import org.forgerock.openig.http.Cookie; 024import org.forgerock.openig.http.Message; 025 026/** 027 * Processes the <strong>{@code Cookie}</strong> request message header. For more information, see the original 028 * <a href="http://web.archive.org/web/20070805052634/http://wp.netscape.com/newsref/std/cookie_spec.html"> 029 * Netscape specification</a>, 030 * <a href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a> and 031 * <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>. 032 * <p> 033 * Note: This implementation is designed to be forgiving when parsing malformed cookies. 034 */ 035public class CookieHeader implements Header { 036 037 /** The name of the header that this object represents. */ 038 public static final String NAME = "Cookie"; 039 040 /** Request message cookies. */ 041 private final List<Cookie> cookies = new ArrayList<Cookie>(); 042 043 /** 044 * Constructs a new empty header. 045 */ 046 public CookieHeader() { 047 // Nothing to do. 048 } 049 050 /** 051 * Constructs a new header, initialized from the specified message. 052 * 053 * @param message the message to initialize the header from. 054 */ 055 public CookieHeader(Message<?> message) { 056 fromMessage(message); 057 } 058 059 /** 060 * Constructs a new header, initialized from the specified string value. 061 * 062 * @param string the value to initialize the header from. 063 */ 064 public CookieHeader(String string) { 065 fromString(string); 066 } 067 068 /** 069 * Returns the cookies' request list. 070 * 071 * @return The cookies' request list. 072 */ 073 public List<Cookie> getCookies() { 074 return cookies; 075 } 076 077 private void clear() { 078 cookies.clear(); 079 } 080 081 @Override 082 public String getKey() { 083 return NAME; 084 } 085 086 @Override 087 public void fromMessage(Message<?> message) { 088 if (message != null && message.getHeaders() != null) { 089 fromString(HeaderUtil.join(message.getHeaders().get(getKey()), ',')); 090 } 091 } 092 093 @Override 094 public void fromString(String string) { 095 clear(); 096 if (string != null) { 097 Integer version = null; 098 Cookie cookie = new Cookie(); 099 for (String s1 : HeaderUtil.split(string, ',')) { 100 for (String s2 : HeaderUtil.split(s1, ';')) { 101 String[] nvp = HeaderUtil.parseParameter(s2); 102 if (nvp[0].length() > 0 && nvp[0].charAt(0) != '$') { 103 if (cookie.getName() != null) { 104 // existing cookie was being parsed 105 cookies.add(cookie); 106 } 107 cookie = new Cookie(); 108 // inherit previous parsed version 109 cookie.setVersion(version); 110 cookie.setName(nvp[0]); 111 cookie.setValue(nvp[1]); 112 } else if ("$Version".equalsIgnoreCase(nvp[0])) { 113 cookie.setVersion(version = parseInteger(nvp[1])); 114 } else if ("$Path".equalsIgnoreCase(nvp[0])) { 115 cookie.setPath(nvp[1]); 116 } else if ("$Domain".equalsIgnoreCase(nvp[0])) { 117 cookie.setDomain(nvp[1]); 118 } else if ("$Port".equalsIgnoreCase(nvp[0])) { 119 cookie.getPort().clear(); 120 parsePorts(cookie.getPort(), nvp[1]); 121 } 122 } 123 } 124 if (cookie.getName() != null) { 125 // last cookie being parsed 126 cookies.add(cookie); 127 } 128 } 129 } 130 131 @Override 132 public void toMessage(Message<?> message) { 133 String value = toString(); 134 if (value != null) { 135 message.getHeaders().putSingle(getKey(), value); 136 } 137 } 138 139 @Override 140 public String toString() { 141 boolean quoted = false; 142 Integer version = null; 143 for (Cookie cookie : cookies) { 144 if (cookie.getVersion() != null && (version == null || cookie.getVersion() > version)) { 145 version = cookie.getVersion(); 146 } else if (version == null && (cookie.getPath() != null || cookie.getDomain() != null)) { 147 // presence of extended fields makes it version 1 at minimum 148 version = 1; 149 } 150 } 151 StringBuilder sb = new StringBuilder(); 152 if (version != null) { 153 sb.append("$Version=").append(version.toString()); 154 quoted = true; 155 } 156 for (Cookie cookie : cookies) { 157 if (cookie.getName() != null) { 158 if (sb.length() > 0) { 159 sb.append("; "); 160 } 161 sb.append(cookie.getName()).append('='); 162 sb.append(quoted ? HeaderUtil.quote(cookie.getValue()) : cookie.getValue()); 163 if (cookie.getPath() != null) { 164 sb.append("; $Path=").append(HeaderUtil.quote(cookie.getPath())); 165 } 166 if (cookie.getDomain() != null) { 167 sb.append("; $Domain=").append(HeaderUtil.quote(cookie.getDomain())); 168 } 169 if (cookie.getPort().size() > 0) { 170 sb.append("; $Port=").append(HeaderUtil.quote(portList(cookie.getPort()))); 171 } 172 } 173 } 174 // return null if empty 175 return sb.length() > 0 ? sb.toString() : null; 176 } 177 178 @Override 179 public boolean equals(Object o) { 180 return o == this || (o instanceof CookieHeader && cookies.equals(((CookieHeader) o).cookies)); 181 } 182 183 @Override 184 public int hashCode() { 185 return cookies.hashCode(); 186 } 187 188 private void parsePorts(List<Integer> list, String s) { 189 for (String port : s.split(",")) { 190 Integer p = parseInteger(port); 191 if (p != null) { 192 list.add(p); 193 } 194 } 195 } 196 197 private Integer parseInteger(String s) { 198 try { 199 return Integer.valueOf(s); 200 } catch (NumberFormatException nfe) { 201 return null; 202 } 203 } 204 205 private String portList(List<Integer> ports) { 206 StringBuilder sb = new StringBuilder(); 207 for (Integer port : ports) { 208 if (sb.length() > 0) { 209 sb.append(','); 210 } 211 sb.append(port.toString()); 212 } 213 return sb.toString(); 214 } 215}