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.Map;
030import java.util.concurrent.ConcurrentHashMap;
031
032import org.apache.http.annotation.Contract;
033import org.apache.http.annotation.ThreadingBehavior;
034import org.apache.http.conn.util.PublicSuffixList;
035import org.apache.http.conn.util.PublicSuffixMatcher;
036import org.apache.http.cookie.CommonCookieAttributeHandler;
037import org.apache.http.cookie.Cookie;
038import org.apache.http.cookie.CookieOrigin;
039import org.apache.http.cookie.MalformedCookieException;
040import org.apache.http.cookie.SetCookie;
041import org.apache.http.util.Args;
042
043/**
044 * Wraps a {@link org.apache.http.cookie.CookieAttributeHandler} and leverages its match method
045 * to never match a suffix from a black list. May be used to provide additional security for
046 * cross-site attack types by preventing cookies from apparent domains that are not publicly
047 * available.
048 *
049 *  @see org.apache.http.conn.util.PublicSuffixList
050 *  @see org.apache.http.conn.util.PublicSuffixMatcher
051 *
052 * @since 4.4
053 */
054@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
055public class PublicSuffixDomainFilter implements CommonCookieAttributeHandler {
056
057    private final CommonCookieAttributeHandler handler;
058    private final PublicSuffixMatcher publicSuffixMatcher;
059    private final Map<String, Boolean> localDomainMap;
060
061    private static Map<String, Boolean> createLocalDomainMap() {
062        final ConcurrentHashMap<String, Boolean> map = new ConcurrentHashMap<String, Boolean>();
063        map.put(".localhost.", Boolean.TRUE);  // RFC 6761
064        map.put(".test.", Boolean.TRUE);       // RFC 6761
065        map.put(".local.", Boolean.TRUE);      // RFC 6762
066        map.put(".local", Boolean.TRUE);
067        map.put(".localdomain", Boolean.TRUE);
068        return map;
069    }
070
071    public PublicSuffixDomainFilter(
072            final CommonCookieAttributeHandler handler, final PublicSuffixMatcher publicSuffixMatcher) {
073        this.handler = Args.notNull(handler, "Cookie handler");
074        this.publicSuffixMatcher = Args.notNull(publicSuffixMatcher, "Public suffix matcher");
075        this.localDomainMap = createLocalDomainMap();
076    }
077
078    public PublicSuffixDomainFilter(
079            final CommonCookieAttributeHandler handler, final PublicSuffixList suffixList) {
080        Args.notNull(handler, "Cookie handler");
081        Args.notNull(suffixList, "Public suffix list");
082        this.handler = handler;
083        this.publicSuffixMatcher = new PublicSuffixMatcher(suffixList.getRules(), suffixList.getExceptions());
084        this.localDomainMap = createLocalDomainMap();
085    }
086
087    /**
088     * Never matches if the cookie's domain is from the blacklist.
089     */
090    @Override
091    public boolean match(final Cookie cookie, final CookieOrigin origin) {
092        final String host = cookie.getDomain();
093        final int i = host.indexOf('.');
094        if (i >= 0) {
095            final String domain = host.substring(i);
096            if (!this.localDomainMap.containsKey(domain)) {
097                if (this.publicSuffixMatcher.matches(host)) {
098                    return false;
099                }
100            }
101        } else {
102            if (!host.equalsIgnoreCase(origin.getHost())) {
103                if (this.publicSuffixMatcher.matches(host)) {
104                    return false;
105                }
106            }
107        }
108        return handler.match(cookie, origin);
109    }
110
111    @Override
112    public void parse(final SetCookie cookie, final String value) throws MalformedCookieException {
113        handler.parse(cookie, value);
114    }
115
116    @Override
117    public void validate(final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
118        handler.validate(cookie, origin);
119    }
120
121    @Override
122    public String getAttributeName() {
123        return handler.getAttributeName();
124    }
125
126    public static CommonCookieAttributeHandler decorate(
127            final CommonCookieAttributeHandler handler, final PublicSuffixMatcher publicSuffixMatcher) {
128        Args.notNull(handler, "Cookie attribute handler");
129        return publicSuffixMatcher != null ? new PublicSuffixDomainFilter(handler, publicSuffixMatcher) : handler;
130    }
131
132}