001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027package org.apache.http.impl.cookie;
028
029import java.util.Locale;
030
031import org.apache.http.annotation.Contract;
032import org.apache.http.annotation.ThreadingBehavior;
033import org.apache.http.conn.util.InetAddressUtils;
034import org.apache.http.cookie.ClientCookie;
035import org.apache.http.cookie.CommonCookieAttributeHandler;
036import org.apache.http.cookie.Cookie;
037import org.apache.http.cookie.CookieOrigin;
038import org.apache.http.cookie.CookieRestrictionViolationException;
039import org.apache.http.cookie.MalformedCookieException;
040import org.apache.http.cookie.SetCookie;
041import org.apache.http.util.Args;
042import org.apache.http.util.TextUtils;
043
044/**
045 *
046 * @since 4.0
047 */
048@Contract(threading = ThreadingBehavior.IMMUTABLE)
049public class BasicDomainHandler implements CommonCookieAttributeHandler {
050
051    public BasicDomainHandler() {
052        super();
053    }
054
055    @Override
056    public void parse(final SetCookie cookie, final String value)
057            throws MalformedCookieException {
058        Args.notNull(cookie, "Cookie");
059        if (TextUtils.isBlank(value)) {
060            throw new MalformedCookieException("Blank or null value for domain attribute");
061        }
062        // Ignore domain attributes ending with '.' per RFC 6265, 4.1.2.3
063        if (value.endsWith(".")) {
064            return;
065        }
066        String domain = value;
067        if (domain.startsWith(".")) {
068            domain = domain.substring(1);
069        }
070        domain = domain.toLowerCase(Locale.ROOT);
071        cookie.setDomain(domain);
072    }
073
074    @Override
075    public void validate(final Cookie cookie, final CookieOrigin origin)
076            throws MalformedCookieException {
077        Args.notNull(cookie, "Cookie");
078        Args.notNull(origin, "Cookie origin");
079        // Validate the cookies domain attribute.  NOTE:  Domains without
080        // any dots are allowed to support hosts on private LANs that don't
081        // have DNS names.  Since they have no dots, to domain-match the
082        // request-host and domain must be identical for the cookie to sent
083        // back to the origin-server.
084        final String host = origin.getHost();
085        final String domain = cookie.getDomain();
086        if (domain == null) {
087            throw new CookieRestrictionViolationException("Cookie 'domain' may not be null");
088        }
089        if (!host.equals(domain) && !domainMatch(domain, host)) {
090            throw new CookieRestrictionViolationException(
091                    "Illegal 'domain' attribute \"" + domain + "\". Domain of origin: \"" + host + "\"");
092        }
093    }
094
095    static boolean domainMatch(final String domain, final String host) {
096        if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host)) {
097            return false;
098        }
099        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}