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