1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.apache.commons.httpclient.cookie;
31
32 import java.util.StringTokenizer;
33 import java.util.Date;
34 import java.util.Locale;
35 import java.text.DateFormat;
36 import java.text.SimpleDateFormat;
37 import java.text.ParseException;
38
39 import org.apache.commons.httpclient.HeaderElement;
40 import org.apache.commons.httpclient.NameValuePair;
41 import org.apache.commons.httpclient.Cookie;
42
43 /***
44 * <P>Netscape cookie draft specific cookie management functions
45 *
46 * @author B.C. Holmes
47 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
48 * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
49 * @author Rod Waldhoff
50 * @author dIon Gillard
51 * @author Sean C. Sullivan
52 * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
53 * @author Marc A. Saegesser
54 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
55 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
56 *
57 * @since 2.0
58 */
59
60 public class NetscapeDraftSpec extends CookieSpecBase {
61
62 /*** Default constructor */
63 public NetscapeDraftSpec() {
64 super();
65 }
66
67 /***
68 * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s.
69 *
70 * <p>Syntax of the Set-Cookie HTTP Response Header:</p>
71 *
72 * <p>This is the format a CGI script would use to add to
73 * the HTTP headers a new piece of data which is to be stored by
74 * the client for later retrieval.</p>
75 *
76 * <PRE>
77 * Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
78 * </PRE>
79 *
80 * <p>Please note that Netscape draft specification does not fully
81 * conform to the HTTP header format. Netscape draft does not specify
82 * whether multiple cookies may be sent in one header. Hence, comma
83 * character may be present in unquoted cookie value or unquoted
84 * parameter value.</p>
85 *
86 * @link http://wp.netscape.com/newsref/std/cookie_spec.html
87 *
88 * @param host the host from which the <tt>Set-Cookie</tt> value was
89 * received
90 * @param port the port from which the <tt>Set-Cookie</tt> value was
91 * received
92 * @param path the path from which the <tt>Set-Cookie</tt> value was
93 * received
94 * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was
95 * received over secure conection
96 * @param header the <tt>Set-Cookie</tt> received from the server
97 * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
98 * @throws MalformedCookieException if an exception occurs during parsing
99 *
100 * @since 3.0
101 */
102 public Cookie[] parse(String host, int port, String path,
103 boolean secure, final String header)
104 throws MalformedCookieException {
105
106 LOG.trace("enter NetscapeDraftSpec.parse(String, port, path, boolean, Header)");
107
108 if (host == null) {
109 throw new IllegalArgumentException("Host of origin may not be null");
110 }
111 if (host.trim().equals("")) {
112 throw new IllegalArgumentException("Host of origin may not be blank");
113 }
114 if (port < 0) {
115 throw new IllegalArgumentException("Invalid port: " + port);
116 }
117 if (path == null) {
118 throw new IllegalArgumentException("Path of origin may not be null.");
119 }
120 if (header == null) {
121 throw new IllegalArgumentException("Header may not be null.");
122 }
123
124 if (path.trim().equals("")) {
125 path = PATH_DELIM;
126 }
127 host = host.toLowerCase();
128
129 String defaultPath = path;
130 int lastSlashIndex = defaultPath.lastIndexOf(PATH_DELIM);
131 if (lastSlashIndex >= 0) {
132 if (lastSlashIndex == 0) {
133
134 lastSlashIndex = 1;
135 }
136 defaultPath = defaultPath.substring(0, lastSlashIndex);
137 }
138 HeaderElement headerelement = new HeaderElement(header.toCharArray());
139 Cookie cookie = new Cookie(host,
140 headerelement.getName(),
141 headerelement.getValue(),
142 defaultPath,
143 null,
144 false);
145
146 NameValuePair[] parameters = headerelement.getParameters();
147
148 if (parameters != null) {
149 for (int j = 0; j < parameters.length; j++) {
150 parseAttribute(parameters[j], cookie);
151 }
152 }
153 return new Cookie[] {cookie};
154 }
155
156
157 /***
158 * Parse the cookie attribute and update the corresponsing {@link Cookie}
159 * properties as defined by the Netscape draft specification
160 *
161 * @param attribute {@link NameValuePair} cookie attribute from the
162 * <tt>Set- Cookie</tt>
163 * @param cookie {@link Cookie} to be updated
164 * @throws MalformedCookieException if an exception occurs during parsing
165 */
166 public void parseAttribute(
167 final NameValuePair attribute, final Cookie cookie)
168 throws MalformedCookieException {
169
170 if (attribute == null) {
171 throw new IllegalArgumentException("Attribute may not be null.");
172 }
173 if (cookie == null) {
174 throw new IllegalArgumentException("Cookie may not be null.");
175 }
176 final String paramName = attribute.getName().toLowerCase();
177 final String paramValue = attribute.getValue();
178
179 if (paramName.equals("expires")) {
180
181 if (paramValue == null) {
182 throw new MalformedCookieException(
183 "Missing value for expires attribute");
184 }
185 try {
186 DateFormat expiryFormat = new SimpleDateFormat(
187 "EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US);
188 Date date = expiryFormat.parse(paramValue);
189 cookie.setExpiryDate(date);
190 } catch (ParseException e) {
191 throw new MalformedCookieException("Invalid expires "
192 + "attribute: " + e.getMessage());
193 }
194 } else {
195 super.parseAttribute(attribute, cookie);
196 }
197 }
198
199 /***
200 * Performs domain-match as described in the Netscape draft.
201 * @param host The target host.
202 * @param domain The cookie domain attribute.
203 * @return true if the specified host matches the given domain.
204 */
205 public boolean domainMatch(final String host, final String domain) {
206 return host.endsWith(domain);
207 }
208
209 /***
210 * Performs Netscape draft compliant {@link Cookie} validation
211 *
212 * @param host the host from which the {@link Cookie} was received
213 * @param port the port from which the {@link Cookie} was received
214 * @param path the path from which the {@link Cookie} was received
215 * @param secure <tt>true</tt> when the {@link Cookie} was received
216 * using a secure connection
217 * @param cookie The cookie to validate.
218 * @throws MalformedCookieException if an exception occurs during
219 * validation
220 */
221 public void validate(String host, int port, String path,
222 boolean secure, final Cookie cookie)
223 throws MalformedCookieException {
224
225 LOG.trace("enterNetscapeDraftCookieProcessor "
226 + "RCF2109CookieProcessor.validate(Cookie)");
227
228 super.validate(host, port, path, secure, cookie);
229
230 if (host.indexOf(".") >= 0) {
231 int domainParts = new StringTokenizer(cookie.getDomain(), ".")
232 .countTokens();
233
234 if (isSpecialDomain(cookie.getDomain())) {
235 if (domainParts < 2) {
236 throw new MalformedCookieException("Domain attribute \""
237 + cookie.getDomain()
238 + "\" violates the Netscape cookie specification for "
239 + "special domains");
240 }
241 } else {
242 if (domainParts < 3) {
243 throw new MalformedCookieException("Domain attribute \""
244 + cookie.getDomain()
245 + "\" violates the Netscape cookie specification");
246 }
247 }
248 }
249 }
250
251 /***
252 * Checks if the given domain is in one of the seven special
253 * top level domains defined by the Netscape cookie specification.
254 * @param domain The domain.
255 * @return True if the specified domain is "special"
256 */
257 private static boolean isSpecialDomain(final String domain) {
258 final String ucDomain = domain.toUpperCase();
259 if (ucDomain.endsWith(".COM")
260 || ucDomain.endsWith(".EDU")
261 || ucDomain.endsWith(".NET")
262 || ucDomain.endsWith(".GOV")
263 || ucDomain.endsWith(".MIL")
264 || ucDomain.endsWith(".ORG")
265 || ucDomain.endsWith(".INT")) {
266 return true;
267 }
268 return false;
269 }
270 }