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 package org.apache.http.impl.cookie;
28
29 import java.util.Locale;
30
31 import org.apache.http.annotation.Contract;
32 import org.apache.http.annotation.ThreadingBehavior;
33 import org.apache.http.conn.util.InetAddressUtils;
34 import org.apache.http.cookie.ClientCookie;
35 import org.apache.http.cookie.CommonCookieAttributeHandler;
36 import org.apache.http.cookie.Cookie;
37 import org.apache.http.cookie.CookieOrigin;
38 import org.apache.http.cookie.CookieRestrictionViolationException;
39 import org.apache.http.cookie.MalformedCookieException;
40 import org.apache.http.cookie.SetCookie;
41 import org.apache.http.util.Args;
42 import org.apache.http.util.TextUtils;
43
44 /**
45  *
46  * @since 4.0
47  */

48 @Contract(threading = ThreadingBehavior.IMMUTABLE)
49 public class BasicDomainHandler implements CommonCookieAttributeHandler {
50
51     public BasicDomainHandler() {
52         super();
53     }
54
55     @Override
56     public void parse(final SetCookie cookie, final String value)
57             throws MalformedCookieException {
58         Args.notNull(cookie, "Cookie");
59         if (TextUtils.isBlank(value)) {
60             throw new MalformedCookieException("Blank or null value for domain attribute");
61         }
62         // Ignore domain attributes ending with '.' per RFC 6265, 4.1.2.3
63         if (value.endsWith(".")) {
64             return;
65         }
66         String domain = value;
67         if (domain.startsWith(".")) {
68             domain = domain.substring(1);
69         }
70         domain = domain.toLowerCase(Locale.ROOT);
71         cookie.setDomain(domain);
72     }
73
74     @Override
75     public void validate(final Cookie cookie, final CookieOrigin origin)
76             throws MalformedCookieException {
77         Args.notNull(cookie, "Cookie");
78         Args.notNull(origin, "Cookie origin");
79         // Validate the cookies domain attribute.  NOTE:  Domains without
80         // any dots are allowed to support hosts on private LANs that don't
81         // have DNS names.  Since they have no dots, to domain-match the
82         // request-host and domain must be identical for the cookie to sent
83         // back to the origin-server.
84         final String host = origin.getHost();
85         final String domain = cookie.getDomain();
86         if (domain == null) {
87             throw new CookieRestrictionViolationException("Cookie 'domain' may not be null");
88         }
89         if (!host.equals(domain) && !domainMatch(domain, host)) {
90             throw new CookieRestrictionViolationException(
91                     "Illegal 'domain' attribute \"" + domain + "\". Domain of origin: \"" + host + "\"");
92         }
93     }
94
95     static boolean domainMatch(final String domain, final String host) {
96         if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host)) {
97             return false;
98         }
99         final String normalizedDomain = domain.startsWith(".") ? domain.substring(1) : domain;
100         if (host.endsWith(normalizedDomain)) {
101             final int prefix = host.length() - normalizedDomain.length();
102             // Either a full match or a prefix endidng with a '.'
103             if (prefix == 0) {
104                 return true;
105             }
106             if (prefix > 1 && host.charAt(prefix - 1) == '.') {
107                 return true;
108             }
109         }
110         return false;
111     }
112
113     @Override
114     public boolean match(final Cookie cookie, final CookieOrigin origin) {
115         Args.notNull(cookie, "Cookie");
116         Args.notNull(origin, "Cookie origin");
117         final String host = origin.getHost();
118         String domain = cookie.getDomain();
119         if (domain == null) {
120             return false;
121         }
122         if (domain.startsWith(".")) {
123             domain = domain.substring(1);
124         }
125         domain = domain.toLowerCase(Locale.ROOT);
126         if (host.equals(domain)) {
127             return true;
128         }
129         if (cookie instanceof ClientCookie) {
130             if (((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
131                 return domainMatch(domain, host);
132             }
133         }
134         return false;
135     }
136
137     @Override
138     public String getAttributeName() {
139         return ClientCookie.DOMAIN_ATTR;
140     }
141
142 }
143