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