001: /*
002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java,v 1.21 2004/06/05 16:49:20 olegk Exp $
003: * $Revision: 507134 $
004: * $Date: 2007-02-13 19:18:05 +0100 (Tue, 13 Feb 2007) $
005: *
006: * ====================================================================
007: *
008: * Licensed to the Apache Software Foundation (ASF) under one or more
009: * contributor license agreements. See the NOTICE file distributed with
010: * this work for additional information regarding copyright ownership.
011: * The ASF licenses this file to You under the Apache License, Version 2.0
012: * (the "License"); you may not use this file except in compliance with
013: * the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: * ====================================================================
023: *
024: * This software consists of voluntary contributions made by many
025: * individuals on behalf of the Apache Software Foundation. For more
026: * information on the Apache Software Foundation, please see
027: * <http://www.apache.org/>.
028: *
029: */
030:
031: package org.apache.commons.httpclient.cookie;
032:
033: import org.apache.commons.httpclient.NameValuePair;
034: import org.apache.commons.httpclient.Cookie;
035: import org.apache.commons.httpclient.util.ParameterFormatter;
036:
037: /**
038: * <p>RFC 2109 specific cookie management functions
039: *
040: * @author B.C. Holmes
041: * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
042: * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
043: * @author Rod Waldhoff
044: * @author dIon Gillard
045: * @author Sean C. Sullivan
046: * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
047: * @author Marc A. Saegesser
048: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
049: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
050: *
051: * @since 2.0
052: */
053:
054: public class RFC2109Spec extends CookieSpecBase {
055:
056: private final ParameterFormatter formatter;
057:
058: /**
059: * Cookie Response Header name for cookies processed
060: * by this spec.
061: */
062: public final static String SET_COOKIE_KEY = "set-cookie";
063:
064: /** Default constructor */
065: public RFC2109Spec() {
066: super ();
067: this .formatter = new ParameterFormatter();
068: this .formatter.setAlwaysUseQuotes(true);
069: }
070:
071: /**
072: * Parse RFC 2109 specific cookie attribute and update the corresponsing
073: * {@link Cookie} properties.
074: *
075: * @param attribute {@link NameValuePair} cookie attribute from the
076: * <tt>Set- Cookie</tt>
077: * @param cookie {@link Cookie} to be updated
078: * @throws MalformedCookieException if an exception occurs during parsing
079: */
080: public void parseAttribute(final NameValuePair attribute,
081: final Cookie cookie) throws MalformedCookieException {
082:
083: if (attribute == null) {
084: throw new IllegalArgumentException(
085: "Attribute may not be null.");
086: }
087: if (cookie == null) {
088: throw new IllegalArgumentException(
089: "Cookie may not be null.");
090: }
091: final String paramName = attribute.getName().toLowerCase();
092: final String paramValue = attribute.getValue();
093:
094: if (paramName.equals("path")) {
095: if (paramValue == null) {
096: throw new MalformedCookieException(
097: "Missing value for path attribute");
098: }
099: if (paramValue.trim().equals("")) {
100: throw new MalformedCookieException(
101: "Blank value for path attribute");
102: }
103: cookie.setPath(paramValue);
104: cookie.setPathAttributeSpecified(true);
105: } else if (paramName.equals("version")) {
106:
107: if (paramValue == null) {
108: throw new MalformedCookieException(
109: "Missing value for version attribute");
110: }
111: try {
112: cookie.setVersion(Integer.parseInt(paramValue));
113: } catch (NumberFormatException e) {
114: throw new MalformedCookieException("Invalid version: "
115: + e.getMessage());
116: }
117:
118: } else {
119: super .parseAttribute(attribute, cookie);
120: }
121: }
122:
123: /**
124: * Performs RFC 2109 compliant {@link Cookie} validation
125: *
126: * @param host the host from which the {@link Cookie} was received
127: * @param port the port from which the {@link Cookie} was received
128: * @param path the path from which the {@link Cookie} was received
129: * @param secure <tt>true</tt> when the {@link Cookie} was received using a
130: * secure connection
131: * @param cookie The cookie to validate
132: * @throws MalformedCookieException if an exception occurs during
133: * validation
134: */
135: public void validate(String host, int port, String path,
136: boolean secure, final Cookie cookie)
137: throws MalformedCookieException {
138:
139: LOG.trace("enter RFC2109Spec.validate(String, int, String, "
140: + "boolean, Cookie)");
141:
142: // Perform generic validation
143: super .validate(host, port, path, secure, cookie);
144: // Perform RFC 2109 specific validation
145:
146: if (cookie.getName().indexOf(' ') != -1) {
147: throw new MalformedCookieException(
148: "Cookie name may not contain blanks");
149: }
150: if (cookie.getName().startsWith("$")) {
151: throw new MalformedCookieException(
152: "Cookie name may not start with $");
153: }
154:
155: if (cookie.isDomainAttributeSpecified()
156: && (!cookie.getDomain().equals(host))) {
157:
158: // domain must start with dot
159: if (!cookie.getDomain().startsWith(".")) {
160: throw new MalformedCookieException(
161: "Domain attribute \""
162: + cookie.getDomain()
163: + "\" violates RFC 2109: domain must start with a dot");
164: }
165: // domain must have at least one embedded dot
166: int dotIndex = cookie.getDomain().indexOf('.', 1);
167: if (dotIndex < 0
168: || dotIndex == cookie.getDomain().length() - 1) {
169: throw new MalformedCookieException(
170: "Domain attribute \""
171: + cookie.getDomain()
172: + "\" violates RFC 2109: domain must contain an embedded dot");
173: }
174: host = host.toLowerCase();
175: if (!host.endsWith(cookie.getDomain())) {
176: throw new MalformedCookieException(
177: "Illegal domain attribute \""
178: + cookie.getDomain()
179: + "\". Domain of origin: \"" + host
180: + "\"");
181: }
182: // host minus domain may not contain any dots
183: String hostWithoutDomain = host.substring(0, host.length()
184: - cookie.getDomain().length());
185: if (hostWithoutDomain.indexOf('.') != -1) {
186: throw new MalformedCookieException(
187: "Domain attribute \""
188: + cookie.getDomain()
189: + "\" violates RFC 2109: host minus domain may not contain any dots");
190: }
191: }
192: }
193:
194: /**
195: * Performs domain-match as defined by the RFC2109.
196: * @param host The target host.
197: * @param domain The cookie domain attribute.
198: * @return true if the specified host matches the given domain.
199: *
200: * @since 3.0
201: */
202: public boolean domainMatch(String host, String domain) {
203: boolean match = host.equals(domain)
204: || (domain.startsWith(".") && host.endsWith(domain));
205:
206: return match;
207: }
208:
209: /**
210: * Return a name/value string suitable for sending in a <tt>"Cookie"</tt>
211: * header as defined in RFC 2109 for backward compatibility with cookie
212: * version 0
213: * @param buffer The string buffer to use for output
214: * @param param The parameter.
215: * @param version The cookie version
216: */
217: private void formatParam(final StringBuffer buffer,
218: final NameValuePair param, int version) {
219: if (version < 1) {
220: buffer.append(param.getName());
221: buffer.append("=");
222: if (param.getValue() != null) {
223: buffer.append(param.getValue());
224: }
225: } else {
226: this .formatter.format(buffer, param);
227: }
228: }
229:
230: /**
231: * Return a string suitable for sending in a <tt>"Cookie"</tt> header
232: * as defined in RFC 2109 for backward compatibility with cookie version 0
233: * @param buffer The string buffer to use for output
234: * @param cookie The {@link Cookie} to be formatted as string
235: * @param version The version to use.
236: */
237: private void formatCookieAsVer(final StringBuffer buffer,
238: final Cookie cookie, int version) {
239: String value = cookie.getValue();
240: if (value == null) {
241: value = "";
242: }
243: formatParam(buffer, new NameValuePair(cookie.getName(), value),
244: version);
245: if ((cookie.getPath() != null)
246: && cookie.isPathAttributeSpecified()) {
247: buffer.append("; ");
248: formatParam(buffer, new NameValuePair("$Path", cookie
249: .getPath()), version);
250: }
251: if ((cookie.getDomain() != null)
252: && cookie.isDomainAttributeSpecified()) {
253: buffer.append("; ");
254: formatParam(buffer, new NameValuePair("$Domain", cookie
255: .getDomain()), version);
256: }
257: }
258:
259: /**
260: * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
261: * defined in RFC 2109
262: * @param cookie a {@link Cookie} to be formatted as string
263: * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
264: */
265: public String formatCookie(Cookie cookie) {
266: LOG.trace("enter RFC2109Spec.formatCookie(Cookie)");
267: if (cookie == null) {
268: throw new IllegalArgumentException("Cookie may not be null");
269: }
270: int version = cookie.getVersion();
271: StringBuffer buffer = new StringBuffer();
272: formatParam(buffer, new NameValuePair("$Version", Integer
273: .toString(version)), version);
274: buffer.append("; ");
275: formatCookieAsVer(buffer, cookie, version);
276: return buffer.toString();
277: }
278:
279: /**
280: * Create a RFC 2109 compliant <tt>"Cookie"</tt> header value containing all
281: * {@link Cookie}s in <i>cookies</i> suitable for sending in a <tt>"Cookie"
282: * </tt> header
283: * @param cookies an array of {@link Cookie}s to be formatted
284: * @return a string suitable for sending in a Cookie header.
285: */
286: public String formatCookies(Cookie[] cookies) {
287: LOG.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
288: int version = Integer.MAX_VALUE;
289: // Pick the lowerest common denominator
290: for (int i = 0; i < cookies.length; i++) {
291: Cookie cookie = cookies[i];
292: if (cookie.getVersion() < version) {
293: version = cookie.getVersion();
294: }
295: }
296: final StringBuffer buffer = new StringBuffer();
297: formatParam(buffer, new NameValuePair("$Version", Integer
298: .toString(version)), version);
299: for (int i = 0; i < cookies.length; i++) {
300: buffer.append("; ");
301: formatCookieAsVer(buffer, cookies[i], version);
302: }
303: return buffer.toString();
304: }
305:
306: }
|