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 */ 027 028package org.apache.http.impl.cookie; 029 030import java.util.Locale; 031 032import org.apache.http.annotation.Contract; 033import org.apache.http.annotation.ThreadingBehavior; 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; 042 043/** 044 * {@code "Domain"} cookie attribute handler for RFC 2965 cookie spec. 045 * 046 * 047 * @since 3.1 048 */ 049@Contract(threading = ThreadingBehavior.IMMUTABLE) 050public class RFC2965DomainAttributeHandler implements CommonCookieAttributeHandler { 051 052 public RFC2965DomainAttributeHandler() { 053 super(); 054 } 055 056 /** 057 * Parse cookie domain attribute. 058 */ 059 @Override 060 public void parse( 061 final SetCookie cookie, final String domain) throws MalformedCookieException { 062 Args.notNull(cookie, "Cookie"); 063 if (domain == null) { 064 throw new MalformedCookieException( 065 "Missing value for domain attribute"); 066 } 067 if (domain.trim().isEmpty()) { 068 throw new MalformedCookieException( 069 "Blank value for domain attribute"); 070 } 071 String s = domain; 072 s = s.toLowerCase(Locale.ROOT); 073 if (!domain.startsWith(".")) { 074 // Per RFC 2965 section 3.2.2 075 // "... If an explicitly specified value does not start with 076 // a dot, the user agent supplies a leading dot ..." 077 // That effectively implies that the domain attribute 078 // MAY NOT be an IP address of a host name 079 s = '.' + s; 080 } 081 cookie.setDomain(s); 082 } 083 084 /** 085 * Performs domain-match as defined by the RFC2965. 086 * <p> 087 * Host A's name domain-matches host B's if 088 * </p> 089 * <ol> 090 * <li>their host name strings string-compare equal; or</li> 091 * <li>A is a HDN string and has the form NB, where N is a non-empty 092 * name string, B has the form .B', and B' is a HDN string. (So, 093 * x.y.com domain-matches .Y.com but not Y.com.)</li> 094 * </ol> 095 * 096 * @param host host name where cookie is received from or being sent to. 097 * @param domain The cookie domain attribute. 098 * @return true if the specified host matches the given domain. 099 */ 100 public boolean domainMatch(final String host, final String domain) { 101 final boolean match = host.equals(domain) 102 || (domain.startsWith(".") && host.endsWith(domain)); 103 104 return match; 105 } 106 107 /** 108 * Validate cookie domain attribute. 109 */ 110 @Override 111 public void validate(final Cookie cookie, final CookieOrigin origin) 112 throws MalformedCookieException { 113 Args.notNull(cookie, "Cookie"); 114 Args.notNull(origin, "Cookie origin"); 115 final String host = origin.getHost().toLowerCase(Locale.ROOT); 116 if (cookie.getDomain() == null) { 117 throw new CookieRestrictionViolationException("Invalid cookie state: " + 118 "domain not specified"); 119 } 120 final String cookieDomain = cookie.getDomain().toLowerCase(Locale.ROOT); 121 122 if (cookie instanceof ClientCookie 123 && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { 124 // Domain attribute must start with a dot 125 if (!cookieDomain.startsWith(".")) { 126 throw new CookieRestrictionViolationException("Domain attribute \"" + 127 cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot"); 128 } 129 130 // Domain attribute must contain at least one embedded dot, 131 // or the value must be equal to .local. 132 final int dotIndex = cookieDomain.indexOf('.', 1); 133 if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1)) 134 && (!cookieDomain.equals(".local"))) { 135 throw new CookieRestrictionViolationException( 136 "Domain attribute \"" + cookie.getDomain() 137 + "\" violates RFC 2965: the value contains no embedded dots " 138 + "and the value is not .local"); 139 } 140 141 // The effective host name must domain-match domain attribute. 142 if (!domainMatch(host, cookieDomain)) { 143 throw new CookieRestrictionViolationException( 144 "Domain attribute \"" + cookie.getDomain() 145 + "\" violates RFC 2965: effective host name does not " 146 + "domain-match domain attribute."); 147 } 148 149 // effective host name minus domain must not contain any dots 150 final String effectiveHostWithoutDomain = host.substring( 151 0, host.length() - cookieDomain.length()); 152 if (effectiveHostWithoutDomain.indexOf('.') != -1) { 153 throw new CookieRestrictionViolationException("Domain attribute \"" 154 + cookie.getDomain() + "\" violates RFC 2965: " 155 + "effective host minus domain may not contain any dots"); 156 } 157 } else { 158 // Domain was not specified in header. In this case, domain must 159 // string match request host (case-insensitive). 160 if (!cookie.getDomain().equals(host)) { 161 throw new CookieRestrictionViolationException("Illegal domain attribute: \"" 162 + cookie.getDomain() + "\"." 163 + "Domain of origin: \"" 164 + host + "\""); 165 } 166 } 167 } 168 169 /** 170 * Match cookie domain attribute. 171 */ 172 @Override 173 public boolean match(final Cookie cookie, final CookieOrigin origin) { 174 Args.notNull(cookie, "Cookie"); 175 Args.notNull(origin, "Cookie origin"); 176 final String host = origin.getHost().toLowerCase(Locale.ROOT); 177 final String cookieDomain = cookie.getDomain(); 178 179 // The effective host name MUST domain-match the Domain 180 // attribute of the cookie. 181 if (!domainMatch(host, cookieDomain)) { 182 return false; 183 } 184 // effective host name minus domain must not contain any dots 185 final String effectiveHostWithoutDomain = host.substring( 186 0, host.length() - cookieDomain.length()); 187 return effectiveHostWithoutDomain.indexOf('.') == -1; 188 } 189 190 @Override 191 public String getAttributeName() { 192 return ClientCookie.DOMAIN_ATTR; 193 } 194 195}