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.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31
32 import org.apache.http.annotation.Contract;
33 import org.apache.http.annotation.ThreadingBehavior;
34 import org.apache.http.conn.util.PublicSuffixList;
35 import org.apache.http.conn.util.PublicSuffixMatcher;
36 import org.apache.http.cookie.CommonCookieAttributeHandler;
37 import org.apache.http.cookie.Cookie;
38 import org.apache.http.cookie.CookieOrigin;
39 import org.apache.http.cookie.MalformedCookieException;
40 import org.apache.http.cookie.SetCookie;
41 import org.apache.http.util.Args;
42
43 /**
44  * Wraps a {@link org.apache.http.cookie.CookieAttributeHandler} and leverages its match method
45  * to never match a suffix from a black list. May be used to provide additional security for
46  * cross-site attack types by preventing cookies from apparent domains that are not publicly
47  * available.
48  *
49  *  @see org.apache.http.conn.util.PublicSuffixList
50  *  @see org.apache.http.conn.util.PublicSuffixMatcher
51  *
52  * @since 4.4
53  */

54 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
55 public class PublicSuffixDomainFilter implements CommonCookieAttributeHandler {
56
57     private final CommonCookieAttributeHandler handler;
58     private final PublicSuffixMatcher publicSuffixMatcher;
59     private final Map<String, Boolean> localDomainMap;
60
61     private static Map<String, Boolean> createLocalDomainMap() {
62         final ConcurrentHashMap<String, Boolean> map = new ConcurrentHashMap<String, Boolean>();
63         map.put(".localhost.", Boolean.TRUE);  // RFC 6761
64         map.put(".test.", Boolean.TRUE);       // RFC 6761
65         map.put(".local.", Boolean.TRUE);      // RFC 6762
66         map.put(".local", Boolean.TRUE);
67         map.put(".localdomain", Boolean.TRUE);
68         return map;
69     }
70
71     public PublicSuffixDomainFilter(
72             final CommonCookieAttributeHandler handler, final PublicSuffixMatcher publicSuffixMatcher) {
73         this.handler = Args.notNull(handler, "Cookie handler");
74         this.publicSuffixMatcher = Args.notNull(publicSuffixMatcher, "Public suffix matcher");
75         this.localDomainMap = createLocalDomainMap();
76     }
77
78     public PublicSuffixDomainFilter(
79             final CommonCookieAttributeHandler handler, final PublicSuffixList suffixList) {
80         Args.notNull(handler, "Cookie handler");
81         Args.notNull(suffixList, "Public suffix list");
82         this.handler = handler;
83         this.publicSuffixMatcher = new PublicSuffixMatcher(suffixList.getRules(), suffixList.getExceptions());
84         this.localDomainMap = createLocalDomainMap();
85     }
86
87     /**
88      * Never matches if the cookie's domain is from the blacklist.
89      */

90     @Override
91     public boolean match(final Cookie cookie, final CookieOrigin origin) {
92         final String host = cookie.getDomain();
93         if (host == null) {
94             return false;
95         }
96         final int i = host.indexOf('.');
97         if (i >= 0) {
98             final String domain = host.substring(i);
99             if (!this.localDomainMap.containsKey(domain)) {
100                 if (this.publicSuffixMatcher.matches(host)) {
101                     return false;
102                 }
103             }
104         } else {
105             if (!host.equalsIgnoreCase(origin.getHost())) {
106                 if (this.publicSuffixMatcher.matches(host)) {
107                     return false;
108                 }
109             }
110         }
111         return handler.match(cookie, origin);
112     }
113
114     @Override
115     public void parse(final SetCookie cookie, final String value) throws MalformedCookieException {
116         handler.parse(cookie, value);
117     }
118
119     @Override
120     public void validate(final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
121         handler.validate(cookie, origin);
122     }
123
124     @Override
125     public String getAttributeName() {
126         return handler.getAttributeName();
127     }
128
129     public static CommonCookieAttributeHandler decorate(
130             final CommonCookieAttributeHandler handler, final PublicSuffixMatcher publicSuffixMatcher) {
131         Args.notNull(handler, "Cookie attribute handler");
132         return publicSuffixMatcher != null ? new PublicSuffixDomainFilter(handler, publicSuffixMatcher) : handler;
133     }
134
135 }
136