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.ArrayList; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Locale; 033import java.util.Map; 034 035import org.apache.http.Header; 036import org.apache.http.HeaderElement; 037import org.apache.http.NameValuePair; 038import org.apache.http.annotation.Contract; 039import org.apache.http.annotation.Obsolete; 040import org.apache.http.annotation.ThreadingBehavior; 041import org.apache.http.cookie.ClientCookie; 042import org.apache.http.cookie.CommonCookieAttributeHandler; 043import org.apache.http.cookie.Cookie; 044import org.apache.http.cookie.CookieAttributeHandler; 045import org.apache.http.cookie.CookieOrigin; 046import org.apache.http.cookie.CookieRestrictionViolationException; 047import org.apache.http.cookie.MalformedCookieException; 048import org.apache.http.cookie.SM; 049import org.apache.http.message.BufferedHeader; 050import org.apache.http.util.Args; 051import org.apache.http.util.CharArrayBuffer; 052 053/** 054 * RFC 2965 compliant {@link org.apache.http.cookie.CookieSpec} implementation. 055 * <p> 056 * Rendered obsolete by {@link org.apache.http.impl.cookie.RFC6265StrictSpec}. 057 * 058 * @since 4.0 059 * @see org.apache.http.impl.cookie.RFC6265StrictSpec 060 */ 061@Obsolete 062@Contract(threading = ThreadingBehavior.SAFE) 063public class RFC2965Spec extends RFC2109Spec { 064 065 /** 066 * Default constructor 067 * 068 */ 069 public RFC2965Spec() { 070 this(null, false); 071 } 072 073 public RFC2965Spec(final String[] datepatterns, final boolean oneHeader) { 074 super(oneHeader, 075 new RFC2965VersionAttributeHandler(), 076 new BasicPathHandler() { 077 078 @Override 079 public void validate( 080 final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException { 081 if (!match(cookie, origin)) { 082 throw new CookieRestrictionViolationException( 083 "Illegal 'path' attribute \"" + cookie.getPath() 084 + "\". Path of origin: \"" + origin.getPath() + "\""); 085 } 086 } 087 088 }, 089 new RFC2965DomainAttributeHandler(), 090 new RFC2965PortAttributeHandler(), 091 new BasicMaxAgeHandler(), 092 new BasicSecureHandler(), 093 new BasicCommentHandler(), 094 new BasicExpiresHandler( 095 datepatterns != null ? datepatterns.clone() : DATE_PATTERNS), 096 new RFC2965CommentUrlAttributeHandler(), 097 new RFC2965DiscardAttributeHandler()); 098 } 099 100 RFC2965Spec(final boolean oneHeader, 101 final CommonCookieAttributeHandler... handlers) { 102 super(oneHeader, handlers); 103 } 104 105 @Override 106 public List<Cookie> parse( 107 final Header header, 108 final CookieOrigin origin) throws MalformedCookieException { 109 Args.notNull(header, "Header"); 110 Args.notNull(origin, "Cookie origin"); 111 if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) { 112 throw new MalformedCookieException("Unrecognized cookie header '" 113 + header.toString() + "'"); 114 } 115 final HeaderElement[] elems = header.getElements(); 116 return createCookies(elems, adjustEffectiveHost(origin)); 117 } 118 119 @Override 120 protected List<Cookie> parse( 121 final HeaderElement[] elems, 122 final CookieOrigin origin) throws MalformedCookieException { 123 return createCookies(elems, adjustEffectiveHost(origin)); 124 } 125 126 private List<Cookie> createCookies( 127 final HeaderElement[] elems, 128 final CookieOrigin origin) throws MalformedCookieException { 129 final List<Cookie> cookies = new ArrayList<Cookie>(elems.length); 130 for (final HeaderElement headerelement : elems) { 131 final String name = headerelement.getName(); 132 final String value = headerelement.getValue(); 133 if (name == null || name.isEmpty()) { 134 throw new MalformedCookieException("Cookie name may not be empty"); 135 } 136 137 final BasicClientCookie2 cookie = new BasicClientCookie2(name, value); 138 cookie.setPath(getDefaultPath(origin)); 139 cookie.setDomain(getDefaultDomain(origin)); 140 cookie.setPorts(new int [] { origin.getPort() }); 141 // cycle through the parameters 142 final NameValuePair[] attribs = headerelement.getParameters(); 143 144 // Eliminate duplicate attributes. The first occurrence takes precedence 145 // See RFC2965: 3.2 Origin Server Role 146 final Map<String, NameValuePair> attribmap = 147 new HashMap<String, NameValuePair>(attribs.length); 148 for (int j = attribs.length - 1; j >= 0; j--) { 149 final NameValuePair param = attribs[j]; 150 attribmap.put(param.getName().toLowerCase(Locale.ROOT), param); 151 } 152 for (final Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) { 153 final NameValuePair attrib = entry.getValue(); 154 final String s = attrib.getName().toLowerCase(Locale.ROOT); 155 156 cookie.setAttribute(s, attrib.getValue()); 157 158 final CookieAttributeHandler handler = findAttribHandler(s); 159 if (handler != null) { 160 handler.parse(cookie, attrib.getValue()); 161 } 162 } 163 cookies.add(cookie); 164 } 165 return cookies; 166 } 167 168 @Override 169 public void validate( 170 final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException { 171 Args.notNull(cookie, "Cookie"); 172 Args.notNull(origin, "Cookie origin"); 173 super.validate(cookie, adjustEffectiveHost(origin)); 174 } 175 176 @Override 177 public boolean match(final Cookie cookie, final CookieOrigin origin) { 178 Args.notNull(cookie, "Cookie"); 179 Args.notNull(origin, "Cookie origin"); 180 return super.match(cookie, adjustEffectiveHost(origin)); 181 } 182 183 /** 184 * Adds valid Port attribute value, e.g. "8000,8001,8002" 185 */ 186 @Override 187 protected void formatCookieAsVer(final CharArrayBuffer buffer, 188 final Cookie cookie, final int version) { 189 super.formatCookieAsVer(buffer, cookie, version); 190 // format port attribute 191 if (cookie instanceof ClientCookie) { 192 // Test if the port attribute as set by the origin server is not blank 193 final String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR); 194 if (s != null) { 195 buffer.append("; $Port"); 196 buffer.append("=\""); 197 if (!s.trim().isEmpty()) { 198 final int[] ports = cookie.getPorts(); 199 if (ports != null) { 200 final int len = ports.length; 201 for (int i = 0; i < len; i++) { 202 if (i > 0) { 203 buffer.append(","); 204 } 205 buffer.append(Integer.toString(ports[i])); 206 } 207 } 208 } 209 buffer.append("\""); 210 } 211 } 212 } 213 214 /** 215 * Set 'effective host name' as defined in RFC 2965. 216 * <p> 217 * If a host name contains no dots, the effective host name is 218 * that name with the string .local appended to it. Otherwise 219 * the effective host name is the same as the host name. Note 220 * that all effective host names contain at least one dot. 221 * 222 * @param origin origin where cookie is received from or being sent to. 223 */ 224 private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) { 225 String host = origin.getHost(); 226 227 // Test if the host name appears to be a fully qualified DNS name, 228 // IPv4 address or IPv6 address 229 boolean isLocalHost = true; 230 for (int i = 0; i < host.length(); i++) { 231 final char ch = host.charAt(i); 232 if (ch == '.' || ch == ':') { 233 isLocalHost = false; 234 break; 235 } 236 } 237 if (isLocalHost) { 238 host += ".local"; 239 return new CookieOrigin( 240 host, 241 origin.getPort(), 242 origin.getPath(), 243 origin.isSecure()); 244 } else { 245 return origin; 246 } 247 } 248 249 @Override 250 public int getVersion() { 251 return 1; 252 } 253 254 @Override 255 public Header getVersionHeader() { 256 final CharArrayBuffer buffer = new CharArrayBuffer(40); 257 buffer.append(SM.COOKIE2); 258 buffer.append(": "); 259 buffer.append("$Version="); 260 buffer.append(Integer.toString(getVersion())); 261 return new BufferedHeader(buffer); 262 } 263 264 @Override 265 public String toString() { 266 return "rfc2965"; 267 } 268 269} 270