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 org.apache.commons.httpclient.NameValuePair;
34 import org.apache.commons.httpclient.Cookie;
35 import org.apache.commons.httpclient.util.ParameterFormatter;
36
37 /***
38 * <p>RFC 2109 specific cookie management functions
39 *
40 * @author B.C. Holmes
41 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
42 * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
43 * @author Rod Waldhoff
44 * @author dIon Gillard
45 * @author Sean C. Sullivan
46 * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
47 * @author Marc A. Saegesser
48 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
49 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
50 *
51 * @since 2.0
52 */
53
54 public class RFC2109Spec extends CookieSpecBase {
55
56 private final ParameterFormatter formatter;
57
58 /***
59 * Cookie Response Header name for cookies processed
60 * by this spec.
61 */
62 public final static String SET_COOKIE_KEY = "set-cookie";
63
64 /*** Default constructor */
65 public RFC2109Spec() {
66 super();
67 this.formatter = new ParameterFormatter();
68 this.formatter.setAlwaysUseQuotes(true);
69 }
70
71 /***
72 * Parse RFC 2109 specific cookie attribute and update the corresponsing
73 * {@link Cookie} properties.
74 *
75 * @param attribute {@link NameValuePair} cookie attribute from the
76 * <tt>Set- Cookie</tt>
77 * @param cookie {@link Cookie} to be updated
78 * @throws MalformedCookieException if an exception occurs during parsing
79 */
80 public void parseAttribute(
81 final NameValuePair attribute, final Cookie cookie)
82 throws MalformedCookieException {
83
84 if (attribute == null) {
85 throw new IllegalArgumentException("Attribute may not be null.");
86 }
87 if (cookie == null) {
88 throw new IllegalArgumentException("Cookie may not be null.");
89 }
90 final String paramName = attribute.getName().toLowerCase();
91 final String paramValue = attribute.getValue();
92
93 if (paramName.equals("path")) {
94 if (paramValue == null) {
95 throw new MalformedCookieException(
96 "Missing value for path attribute");
97 }
98 if (paramValue.trim().equals("")) {
99 throw new MalformedCookieException(
100 "Blank value for path attribute");
101 }
102 cookie.setPath(paramValue);
103 cookie.setPathAttributeSpecified(true);
104 } else if (paramName.equals("version")) {
105
106 if (paramValue == null) {
107 throw new MalformedCookieException(
108 "Missing value for version attribute");
109 }
110 try {
111 cookie.setVersion(Integer.parseInt(paramValue));
112 } catch (NumberFormatException e) {
113 throw new MalformedCookieException("Invalid version: "
114 + e.getMessage());
115 }
116
117 } else {
118 super.parseAttribute(attribute, cookie);
119 }
120 }
121
122 /***
123 * Performs RFC 2109 compliant {@link Cookie} validation
124 *
125 * @param host the host from which the {@link Cookie} was received
126 * @param port the port from which the {@link Cookie} was received
127 * @param path the path from which the {@link Cookie} was received
128 * @param secure <tt>true</tt> when the {@link Cookie} was received using a
129 * secure connection
130 * @param cookie The cookie to validate
131 * @throws MalformedCookieException if an exception occurs during
132 * validation
133 */
134 public void validate(String host, int port, String path,
135 boolean secure, final Cookie cookie) throws MalformedCookieException {
136
137 LOG.trace("enter RFC2109Spec.validate(String, int, String, "
138 + "boolean, Cookie)");
139
140
141 super.validate(host, port, path, secure, cookie);
142
143
144 if (cookie.getName().indexOf(' ') != -1) {
145 throw new MalformedCookieException("Cookie name may not contain blanks");
146 }
147 if (cookie.getName().startsWith("$")) {
148 throw new MalformedCookieException("Cookie name may not start with $");
149 }
150
151 if (cookie.isDomainAttributeSpecified()
152 && (!cookie.getDomain().equals(host))) {
153
154
155 if (!cookie.getDomain().startsWith(".")) {
156 throw new MalformedCookieException("Domain attribute \""
157 + cookie.getDomain()
158 + "\" violates RFC 2109: domain must start with a dot");
159 }
160
161 int dotIndex = cookie.getDomain().indexOf('.', 1);
162 if (dotIndex < 0 || dotIndex == cookie.getDomain().length() - 1) {
163 throw new MalformedCookieException("Domain attribute \""
164 + cookie.getDomain()
165 + "\" violates RFC 2109: domain must contain an embedded dot");
166 }
167 host = host.toLowerCase();
168 if (!host.endsWith(cookie.getDomain())) {
169 throw new MalformedCookieException(
170 "Illegal domain attribute \"" + cookie.getDomain()
171 + "\". Domain of origin: \"" + host + "\"");
172 }
173
174 String hostWithoutDomain = host.substring(0, host.length()
175 - cookie.getDomain().length());
176 if (hostWithoutDomain.indexOf('.') != -1) {
177 throw new MalformedCookieException("Domain attribute \""
178 + cookie.getDomain()
179 + "\" violates RFC 2109: host minus domain may not contain any dots");
180 }
181 }
182 }
183
184 /***
185 * Performs domain-match as defined by the RFC2109.
186 * @param host The target host.
187 * @param domain The cookie domain attribute.
188 * @return true if the specified host matches the given domain.
189 *
190 * @since 3.0
191 */
192 public boolean domainMatch(String host, String domain) {
193 boolean match = host.equals(domain)
194 || (domain.startsWith(".") && host.endsWith(domain));
195
196 return match;
197 }
198
199 /***
200 * Return a name/value string suitable for sending in a <tt>"Cookie"</tt>
201 * header as defined in RFC 2109 for backward compatibility with cookie
202 * version 0
203 * @param buffer The string buffer to use for output
204 * @param param The parameter.
205 * @param version The cookie version
206 */
207 private void formatParam(final StringBuffer buffer, final NameValuePair param, int version) {
208 if (version < 1) {
209 buffer.append(param.getName());
210 buffer.append("=");
211 if (param.getValue() != null) {
212 buffer.append(param.getValue());
213 }
214 } else {
215 this.formatter.format(buffer, param);
216 }
217 }
218
219 /***
220 * Return a string suitable for sending in a <tt>"Cookie"</tt> header
221 * as defined in RFC 2109 for backward compatibility with cookie version 0
222 * @param buffer The string buffer to use for output
223 * @param cookie The {@link Cookie} to be formatted as string
224 * @param version The version to use.
225 */
226 private void formatCookieAsVer(final StringBuffer buffer, final Cookie cookie, int version) {
227 String value = cookie.getValue();
228 if (value == null) {
229 value = "";
230 }
231 formatParam(buffer, new NameValuePair(cookie.getName(), value), version);
232 if ((cookie.getPath() != null) && cookie.isPathAttributeSpecified()) {
233 buffer.append("; ");
234 formatParam(buffer, new NameValuePair("$Path", cookie.getPath()), version);
235 }
236 if ((cookie.getDomain() != null)
237 && cookie.isDomainAttributeSpecified()) {
238 buffer.append("; ");
239 formatParam(buffer, new NameValuePair("$Domain", cookie.getDomain()), version);
240 }
241 }
242
243 /***
244 * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
245 * defined in RFC 2109
246 * @param cookie a {@link Cookie} to be formatted as string
247 * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
248 */
249 public String formatCookie(Cookie cookie) {
250 LOG.trace("enter RFC2109Spec.formatCookie(Cookie)");
251 if (cookie == null) {
252 throw new IllegalArgumentException("Cookie may not be null");
253 }
254 int version = cookie.getVersion();
255 StringBuffer buffer = new StringBuffer();
256 formatParam(buffer,
257 new NameValuePair("$Version", Integer.toString(version)),
258 version);
259 buffer.append("; ");
260 formatCookieAsVer(buffer, cookie, version);
261 return buffer.toString();
262 }
263
264 /***
265 * Create a RFC 2109 compliant <tt>"Cookie"</tt> header value containing all
266 * {@link Cookie}s in <i>cookies</i> suitable for sending in a <tt>"Cookie"
267 * </tt> header
268 * @param cookies an array of {@link Cookie}s to be formatted
269 * @return a string suitable for sending in a Cookie header.
270 */
271 public String formatCookies(Cookie[] cookies) {
272 LOG.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
273 int version = Integer.MAX_VALUE;
274
275 for (int i = 0; i < cookies.length; i++) {
276 Cookie cookie = cookies[i];
277 if (cookie.getVersion() < version) {
278 version = cookie.getVersion();
279 }
280 }
281 final StringBuffer buffer = new StringBuffer();
282 formatParam(buffer,
283 new NameValuePair("$Version", Integer.toString(version)),
284 version);
285 for (int i = 0; i < cookies.length; i++) {
286 buffer.append("; ");
287 formatCookieAsVer(buffer, cookies[i], version);
288 }
289 return buffer.toString();
290 }
291
292 }