1. /*
  2. * $Header: /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 $
  3. * $Revision: 1.21 $
  4. * $Date: 2004/06/05 16:49:20 $
  5. *
  6. * ====================================================================
  7. *
  8. * Copyright 2002-2004 The Apache Software Foundation
  9. *
  10. * Licensed under the Apache License, Version 2.0 (the "License");
  11. * you may not use this file except in compliance with the License.
  12. * You may obtain a copy of the License at
  13. *
  14. * http://www.apache.org/licenses/LICENSE-2.0
  15. *
  16. * Unless required by applicable law or agreed to in writing, software
  17. * distributed under the License is distributed on an "AS IS" BASIS,
  18. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  19. * See the License for the specific language governing permissions and
  20. * limitations under the License.
  21. * ====================================================================
  22. *
  23. * This software consists of voluntary contributions made by many
  24. * individuals on behalf of the Apache Software Foundation. For more
  25. * information on the Apache Software Foundation, please see
  26. * <http://www.apache.org/>.
  27. *
  28. */
  29. package org.apache.commons.httpclient.cookie;
  30. import org.apache.commons.httpclient.NameValuePair;
  31. import org.apache.commons.httpclient.Cookie;
  32. /**
  33. * <p>RFC 2109 specific cookie management functions
  34. *
  35. * @author B.C. Holmes
  36. * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
  37. * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
  38. * @author Rod Waldhoff
  39. * @author dIon Gillard
  40. * @author Sean C. Sullivan
  41. * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
  42. * @author Marc A. Saegesser
  43. * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
  44. * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
  45. *
  46. * @since 2.0
  47. */
  48. public class RFC2109Spec extends CookieSpecBase {
  49. /** Default constructor */
  50. public RFC2109Spec() {
  51. super();
  52. }
  53. /**
  54. * Parse RFC 2109 specific cookie attribute and update the corresponsing
  55. * {@link Cookie} properties.
  56. *
  57. * @param attribute {@link NameValuePair} cookie attribute from the
  58. * <tt>Set- Cookie</tt>
  59. * @param cookie {@link Cookie} to be updated
  60. * @throws MalformedCookieException if an exception occurs during parsing
  61. */
  62. public void parseAttribute(
  63. final NameValuePair attribute, final Cookie cookie)
  64. throws MalformedCookieException {
  65. if (attribute == null) {
  66. throw new IllegalArgumentException("Attribute may not be null.");
  67. }
  68. if (cookie == null) {
  69. throw new IllegalArgumentException("Cookie may not be null.");
  70. }
  71. final String paramName = attribute.getName().toLowerCase();
  72. final String paramValue = attribute.getValue();
  73. if (paramName.equals("path")) {
  74. if (paramValue == null) {
  75. throw new MalformedCookieException(
  76. "Missing value for path attribute");
  77. }
  78. if (paramValue.trim().equals("")) {
  79. throw new MalformedCookieException(
  80. "Blank value for path attribute");
  81. }
  82. cookie.setPath(paramValue);
  83. cookie.setPathAttributeSpecified(true);
  84. } else if (paramName.equals("version")) {
  85. if (paramValue == null) {
  86. throw new MalformedCookieException(
  87. "Missing value for version attribute");
  88. }
  89. try {
  90. cookie.setVersion(Integer.parseInt(paramValue));
  91. } catch (NumberFormatException e) {
  92. throw new MalformedCookieException("Invalid version: "
  93. + e.getMessage());
  94. }
  95. } else {
  96. super.parseAttribute(attribute, cookie);
  97. }
  98. }
  99. /**
  100. * Performs RFC 2109 compliant {@link Cookie} validation
  101. *
  102. * @param host the host from which the {@link Cookie} was received
  103. * @param port the port from which the {@link Cookie} was received
  104. * @param path the path from which the {@link Cookie} was received
  105. * @param secure <tt>true</tt> when the {@link Cookie} was received using a
  106. * secure connection
  107. * @param cookie The cookie to validate
  108. * @throws MalformedCookieException if an exception occurs during
  109. * validation
  110. */
  111. public void validate(String host, int port, String path,
  112. boolean secure, final Cookie cookie) throws MalformedCookieException {
  113. LOG.trace("enter RFC2109Spec.validate(String, int, String, "
  114. + "boolean, Cookie)");
  115. // Perform generic validation
  116. super.validate(host, port, path, secure, cookie);
  117. // Perform RFC 2109 specific validation
  118. if (cookie.getName().indexOf(' ') != -1) {
  119. throw new MalformedCookieException("Cookie name may not contain blanks");
  120. }
  121. if (cookie.getName().startsWith("$")) {
  122. throw new MalformedCookieException("Cookie name may not start with $");
  123. }
  124. if (cookie.isDomainAttributeSpecified()
  125. && (!cookie.getDomain().equals(host))) {
  126. // domain must start with dot
  127. if (!cookie.getDomain().startsWith(".")) {
  128. throw new MalformedCookieException("Domain attribute \""
  129. + cookie.getDomain()
  130. + "\" violates RFC 2109: domain must start with a dot");
  131. }
  132. // domain must have at least one embedded dot
  133. int dotIndex = cookie.getDomain().indexOf('.', 1);
  134. if (dotIndex < 0 || dotIndex == cookie.getDomain().length() - 1) {
  135. throw new MalformedCookieException("Domain attribute \""
  136. + cookie.getDomain()
  137. + "\" violates RFC 2109: domain must contain an embedded dot");
  138. }
  139. host = host.toLowerCase();
  140. if (!host.endsWith(cookie.getDomain())) {
  141. throw new MalformedCookieException(
  142. "Illegal domain attribute \"" + cookie.getDomain()
  143. + "\". Domain of origin: \"" + host + "\"");
  144. }
  145. // host minus domain may not contain any dots
  146. String hostWithoutDomain = host.substring(0, host.length()
  147. - cookie.getDomain().length());
  148. if (hostWithoutDomain.indexOf('.') != -1) {
  149. throw new MalformedCookieException("Domain attribute \""
  150. + cookie.getDomain()
  151. + "\" violates RFC 2109: host minus domain may not contain any dots");
  152. }
  153. }
  154. }
  155. /**
  156. * Performs domain-match as defined by the RFC2109.
  157. * @param host The target host.
  158. * @param domain The cookie domain attribute.
  159. * @return true if the specified host matches the given domain.
  160. *
  161. * @since 3.0
  162. */
  163. public boolean domainMatch(String host, String domain) {
  164. boolean match = host.equals(domain)
  165. || (domain.startsWith(".") && host.endsWith(domain));
  166. return match;
  167. }
  168. /**
  169. * Return a name/value string suitable for sending in a <tt>"Cookie"</tt>
  170. * header as defined in RFC 2109 for backward compatibility with cookie
  171. * version 0
  172. * @param name The name.
  173. * @param value The value
  174. * @param version The cookie version
  175. * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
  176. */
  177. private String formatNameValuePair(
  178. final String name, final String value, int version) {
  179. final StringBuffer buffer = new StringBuffer();
  180. if (version < 1) {
  181. buffer.append(name);
  182. buffer.append("=");
  183. if (value != null) {
  184. buffer.append(value);
  185. }
  186. } else {
  187. buffer.append(name);
  188. buffer.append("=\"");
  189. if (value != null) {
  190. buffer.append(value);
  191. }
  192. buffer.append("\"");
  193. }
  194. return buffer.toString();
  195. }
  196. /**
  197. * Return a string suitable for sending in a <tt>"Cookie"</tt> header
  198. * as defined in RFC 2109 for backward compatibility with cookie version 0
  199. * @param cookie a {@link Cookie} to be formatted as string
  200. * @param version The version to use.
  201. * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
  202. */
  203. private String formatCookieAsVer(Cookie cookie, int version) {
  204. LOG.trace("enter RFC2109Spec.formatCookieAsVer(Cookie)");
  205. StringBuffer buf = new StringBuffer();
  206. buf.append(formatNameValuePair(cookie.getName(),
  207. cookie.getValue(), version));
  208. if (cookie.getDomain() != null
  209. && cookie.isDomainAttributeSpecified()) {
  210. buf.append("; ");
  211. buf.append(formatNameValuePair("$Domain",
  212. cookie.getDomain(), version));
  213. }
  214. if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
  215. buf.append("; ");
  216. buf.append(formatNameValuePair("$Path", cookie.getPath(), version));
  217. }
  218. return buf.toString();
  219. }
  220. /**
  221. * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
  222. * defined in RFC 2109
  223. * @param cookie a {@link Cookie} to be formatted as string
  224. * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
  225. */
  226. public String formatCookie(Cookie cookie) {
  227. LOG.trace("enter RFC2109Spec.formatCookie(Cookie)");
  228. if (cookie == null) {
  229. throw new IllegalArgumentException("Cookie may not be null");
  230. }
  231. int ver = cookie.getVersion();
  232. StringBuffer buffer = new StringBuffer();
  233. buffer.append(formatNameValuePair("$Version",
  234. Integer.toString(ver), ver));
  235. buffer.append("; ");
  236. buffer.append(formatCookieAsVer(cookie, ver));
  237. return buffer.toString();
  238. }
  239. /**
  240. * Create a RFC 2109 compliant <tt>"Cookie"</tt> header value containing all
  241. * {@link Cookie}s in <i>cookies</i> suitable for sending in a <tt>"Cookie"
  242. * </tt> header
  243. * @param cookies an array of {@link Cookie}s to be formatted
  244. * @return a string suitable for sending in a Cookie header.
  245. */
  246. public String formatCookies(Cookie[] cookies) {
  247. LOG.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
  248. int version = Integer.MAX_VALUE;
  249. // Pick the lowerest common denominator
  250. for (int i = 0; i < cookies.length; i++) {
  251. Cookie cookie = cookies[i];
  252. if (cookie.getVersion() < version) {
  253. version = cookie.getVersion();
  254. }
  255. }
  256. final StringBuffer buffer = new StringBuffer();
  257. buffer.append(formatNameValuePair("$Version",
  258. Integer.toString(version), version));
  259. for (int i = 0; i < cookies.length; i++) {
  260. buffer.append("; ");
  261. buffer.append(formatCookieAsVer(cookies[i], version));
  262. }
  263. return buffer.toString();
  264. }
  265. }