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      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 ending with a '.'
103             if (prefix == 0) {
104                 return true;
105             }
106             return prefix > 1 && host.charAt(prefix - 1) == '.';
107         }
108         return false;
109     }
110 
111     @Override
112     public boolean match(final Cookie cookie, final CookieOrigin origin) {
113         Args.notNull(cookie, "Cookie");
114         Args.notNull(origin, "Cookie origin");
115         final String host = origin.getHost();
116         String domain = cookie.getDomain();
117         if (domain == null) {
118             return false;
119         }
120         if (domain.startsWith(".")) {
121             domain = domain.substring(1);
122         }
123         domain = domain.toLowerCase(Locale.ROOT);
124         if (host.equals(domain)) {
125             return true;
126         }
127         if ((cookie.containsAttribute(Cookie.DOMAIN_ATTR))) {
128             return domainMatch(domain, host);
129         }
130         return false;
131     }
132 
133     @Override
134     public String getAttributeName() {
135         return Cookie.DOMAIN_ATTR;
136     }
137 
138 }