1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
27
28 package org.apache.http.impl.cookie;
29
30 import java.util.Locale;
31
32 import org.apache.http.annotation.Immutable;
33 import org.apache.http.cookie.ClientCookie;
34 import org.apache.http.cookie.Cookie;
35 import org.apache.http.cookie.CookieAttributeHandler;
36 import org.apache.http.cookie.CookieOrigin;
37 import org.apache.http.cookie.CookieRestrictionViolationException;
38 import org.apache.http.cookie.MalformedCookieException;
39 import org.apache.http.cookie.SetCookie;
40 import org.apache.http.util.Args;
41
42 /**
43 * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec.
44 *
45 *
46 * @since 3.1
47 */
48 @Immutable
49 public class RFC2965DomainAttributeHandler implements CookieAttributeHandler {
50
51 public RFC2965DomainAttributeHandler() {
52 super();
53 }
54
55 /**
56 * Parse cookie domain attribute.
57 */
58 public void parse(final SetCookie cookie, String domain)
59 throws MalformedCookieException {
60 Args.notNull(cookie, "Cookie");
61 if (domain == null) {
62 throw new MalformedCookieException(
63 "Missing value for domain attribute");
64 }
65 if (domain.trim().length() == 0) {
66 throw new MalformedCookieException(
67 "Blank value for domain attribute");
68 }
69 domain = domain.toLowerCase(Locale.ENGLISH);
70 if (!domain.startsWith(".")) {
71 // Per RFC 2965 section 3.2.2
72 // "... If an explicitly specified value does not start with
73 // a dot, the user agent supplies a leading dot ..."
74 // That effectively implies that the domain attribute
75 // MAY NOT be an IP address of a host name
76 domain = '.' + domain;
77 }
78 cookie.setDomain(domain);
79 }
80
81 /**
82 * Performs domain-match as defined by the RFC2965.
83 * <p>
84 * Host A's name domain-matches host B's if
85 * <ol>
86 * <ul>their host name strings string-compare equal; or</ul>
87 * <ul>A is a HDN string and has the form NB, where N is a non-empty
88 * name string, B has the form .B', and B' is a HDN string. (So,
89 * x.y.com domain-matches .Y.com but not Y.com.)</ul>
90 * </ol>
91 *
92 * @param host host name where cookie is received from or being sent to.
93 * @param domain The cookie domain attribute.
94 * @return true if the specified host matches the given domain.
95 */
96 public boolean domainMatch(final String host, final String domain) {
97 final boolean match = host.equals(domain)
98 || (domain.startsWith(".") && host.endsWith(domain));
99
100 return match;
101 }
102
103 /**
104 * Validate cookie domain attribute.
105 */
106 public void validate(final Cookie cookie, final CookieOrigin origin)
107 throws MalformedCookieException {
108 Args.notNull(cookie, "Cookie");
109 Args.notNull(origin, "Cookie origin");
110 final String host = origin.getHost().toLowerCase(Locale.ENGLISH);
111 if (cookie.getDomain() == null) {
112 throw new CookieRestrictionViolationException("Invalid cookie state: " +
113 "domain not specified");
114 }
115 final String cookieDomain = cookie.getDomain().toLowerCase(Locale.ENGLISH);
116
117 if (cookie instanceof ClientCookie
118 && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
119 // Domain attribute must start with a dot
120 if (!cookieDomain.startsWith(".")) {
121 throw new CookieRestrictionViolationException("Domain attribute \"" +
122 cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot");
123 }
124
125 // Domain attribute must contain at least one embedded dot,
126 // or the value must be equal to .local.
127 final int dotIndex = cookieDomain.indexOf('.', 1);
128 if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1))
129 && (!cookieDomain.equals(".local"))) {
130 throw new CookieRestrictionViolationException(
131 "Domain attribute \"" + cookie.getDomain()
132 + "\" violates RFC 2965: the value contains no embedded dots "
133 + "and the value is not .local");
134 }
135
136 // The effective host name must domain-match domain attribute.
137 if (!domainMatch(host, cookieDomain)) {
138 throw new CookieRestrictionViolationException(
139 "Domain attribute \"" + cookie.getDomain()
140 + "\" violates RFC 2965: effective host name does not "
141 + "domain-match domain attribute.");
142 }
143
144 // effective host name minus domain must not contain any dots
145 final String effectiveHostWithoutDomain = host.substring(
146 0, host.length() - cookieDomain.length());
147 if (effectiveHostWithoutDomain.indexOf('.') != -1) {
148 throw new CookieRestrictionViolationException("Domain attribute \""
149 + cookie.getDomain() + "\" violates RFC 2965: "
150 + "effective host minus domain may not contain any dots");
151 }
152 } else {
153 // Domain was not specified in header. In this case, domain must
154 // string match request host (case-insensitive).
155 if (!cookie.getDomain().equals(host)) {
156 throw new CookieRestrictionViolationException("Illegal domain attribute: \""
157 + cookie.getDomain() + "\"."
158 + "Domain of origin: \""
159 + host + "\"");
160 }
161 }
162 }
163
164 /**
165 * Match cookie domain attribute.
166 */
167 public boolean match(final Cookie cookie, final CookieOrigin origin) {
168 Args.notNull(cookie, "Cookie");
169 Args.notNull(origin, "Cookie origin");
170 final String host = origin.getHost().toLowerCase(Locale.ENGLISH);
171 final String cookieDomain = cookie.getDomain();
172
173 // The effective host name MUST domain-match the Domain
174 // attribute of the cookie.
175 if (!domainMatch(host, cookieDomain)) {
176 return false;
177 }
178 // effective host name minus domain must not contain any dots
179 final String effectiveHostWithoutDomain = host.substring(
180 0, host.length() - cookieDomain.length());
181 return effectiveHostWithoutDomain.indexOf('.') == -1;
182 }
183
184 }