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