michael@0: /* michael@0: * ==================================================================== michael@0: * michael@0: * Licensed to the Apache Software Foundation (ASF) under one or more michael@0: * contributor license agreements. See the NOTICE file distributed with michael@0: * this work for additional information regarding copyright ownership. michael@0: * The ASF licenses this file to You under the Apache License, Version 2.0 michael@0: * (the "License"); you may not use this file except in compliance with michael@0: * the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.impl.cookie; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.HashMap; michael@0: import java.util.List; michael@0: import java.util.Locale; michael@0: import java.util.Map; michael@0: michael@0: import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; michael@0: michael@0: import ch.boye.httpclientandroidlib.Header; michael@0: import ch.boye.httpclientandroidlib.HeaderElement; michael@0: import ch.boye.httpclientandroidlib.NameValuePair; michael@0: import ch.boye.httpclientandroidlib.cookie.ClientCookie; michael@0: import ch.boye.httpclientandroidlib.cookie.Cookie; michael@0: import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; michael@0: import ch.boye.httpclientandroidlib.cookie.CookieOrigin; michael@0: import ch.boye.httpclientandroidlib.cookie.CookieSpec; michael@0: import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; michael@0: import ch.boye.httpclientandroidlib.cookie.SM; michael@0: import ch.boye.httpclientandroidlib.message.BufferedHeader; michael@0: import ch.boye.httpclientandroidlib.util.CharArrayBuffer; michael@0: michael@0: /** michael@0: * RFC 2965 compliant {@link CookieSpec} implementation. michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: @NotThreadSafe // superclass is @NotThreadSafe michael@0: public class RFC2965Spec extends RFC2109Spec { michael@0: michael@0: /** michael@0: * Default constructor michael@0: * michael@0: */ michael@0: public RFC2965Spec() { michael@0: this(null, false); michael@0: } michael@0: michael@0: public RFC2965Spec(final String[] datepatterns, boolean oneHeader) { michael@0: super(datepatterns, oneHeader); michael@0: registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2965DomainAttributeHandler()); michael@0: registerAttribHandler(ClientCookie.PORT_ATTR, new RFC2965PortAttributeHandler()); michael@0: registerAttribHandler(ClientCookie.COMMENTURL_ATTR, new RFC2965CommentUrlAttributeHandler()); michael@0: registerAttribHandler(ClientCookie.DISCARD_ATTR, new RFC2965DiscardAttributeHandler()); michael@0: registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2965VersionAttributeHandler()); michael@0: } michael@0: michael@0: @Override michael@0: public List parse( michael@0: final Header header, michael@0: CookieOrigin origin) throws MalformedCookieException { michael@0: if (header == null) { michael@0: throw new IllegalArgumentException("Header may not be null"); michael@0: } michael@0: if (origin == null) { michael@0: throw new IllegalArgumentException("Cookie origin may not be null"); michael@0: } michael@0: if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) { michael@0: throw new MalformedCookieException("Unrecognized cookie header '" michael@0: + header.toString() + "'"); michael@0: } michael@0: origin = adjustEffectiveHost(origin); michael@0: HeaderElement[] elems = header.getElements(); michael@0: return createCookies(elems, origin); michael@0: } michael@0: michael@0: @Override michael@0: protected List parse( michael@0: final HeaderElement[] elems, michael@0: CookieOrigin origin) throws MalformedCookieException { michael@0: origin = adjustEffectiveHost(origin); michael@0: return createCookies(elems, origin); michael@0: } michael@0: michael@0: private List createCookies( michael@0: final HeaderElement[] elems, michael@0: final CookieOrigin origin) throws MalformedCookieException { michael@0: List cookies = new ArrayList(elems.length); michael@0: for (HeaderElement headerelement : elems) { michael@0: String name = headerelement.getName(); michael@0: String value = headerelement.getValue(); michael@0: if (name == null || name.length() == 0) { michael@0: throw new MalformedCookieException("Cookie name may not be empty"); michael@0: } michael@0: michael@0: BasicClientCookie2 cookie = new BasicClientCookie2(name, value); michael@0: cookie.setPath(getDefaultPath(origin)); michael@0: cookie.setDomain(getDefaultDomain(origin)); michael@0: cookie.setPorts(new int [] { origin.getPort() }); michael@0: // cycle through the parameters michael@0: NameValuePair[] attribs = headerelement.getParameters(); michael@0: michael@0: // Eliminate duplicate attributes. The first occurrence takes precedence michael@0: // See RFC2965: 3.2 Origin Server Role michael@0: Map attribmap = michael@0: new HashMap(attribs.length); michael@0: for (int j = attribs.length - 1; j >= 0; j--) { michael@0: NameValuePair param = attribs[j]; michael@0: attribmap.put(param.getName().toLowerCase(Locale.ENGLISH), param); michael@0: } michael@0: for (Map.Entry entry : attribmap.entrySet()) { michael@0: NameValuePair attrib = entry.getValue(); michael@0: String s = attrib.getName().toLowerCase(Locale.ENGLISH); michael@0: michael@0: cookie.setAttribute(s, attrib.getValue()); michael@0: michael@0: CookieAttributeHandler handler = findAttribHandler(s); michael@0: if (handler != null) { michael@0: handler.parse(cookie, attrib.getValue()); michael@0: } michael@0: } michael@0: cookies.add(cookie); michael@0: } michael@0: return cookies; michael@0: } michael@0: michael@0: @Override michael@0: public void validate(final Cookie cookie, CookieOrigin origin) michael@0: throws MalformedCookieException { michael@0: if (cookie == null) { michael@0: throw new IllegalArgumentException("Cookie may not be null"); michael@0: } michael@0: if (origin == null) { michael@0: throw new IllegalArgumentException("Cookie origin may not be null"); michael@0: } michael@0: origin = adjustEffectiveHost(origin); michael@0: super.validate(cookie, origin); michael@0: } michael@0: michael@0: @Override michael@0: public boolean match(final Cookie cookie, CookieOrigin origin) { michael@0: if (cookie == null) { michael@0: throw new IllegalArgumentException("Cookie may not be null"); michael@0: } michael@0: if (origin == null) { michael@0: throw new IllegalArgumentException("Cookie origin may not be null"); michael@0: } michael@0: origin = adjustEffectiveHost(origin); michael@0: return super.match(cookie, origin); michael@0: } michael@0: michael@0: /** michael@0: * Adds valid Port attribute value, e.g. "8000,8001,8002" michael@0: */ michael@0: @Override michael@0: protected void formatCookieAsVer(final CharArrayBuffer buffer, michael@0: final Cookie cookie, int version) { michael@0: super.formatCookieAsVer(buffer, cookie, version); michael@0: // format port attribute michael@0: if (cookie instanceof ClientCookie) { michael@0: // Test if the port attribute as set by the origin server is not blank michael@0: String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR); michael@0: if (s != null) { michael@0: buffer.append("; $Port"); michael@0: buffer.append("=\""); michael@0: if (s.trim().length() > 0) { michael@0: int[] ports = cookie.getPorts(); michael@0: if (ports != null) { michael@0: for (int i = 0, len = ports.length; i < len; i++) { michael@0: if (i > 0) { michael@0: buffer.append(","); michael@0: } michael@0: buffer.append(Integer.toString(ports[i])); michael@0: } michael@0: } michael@0: } michael@0: buffer.append("\""); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Set 'effective host name' as defined in RFC 2965. michael@0: *

michael@0: * If a host name contains no dots, the effective host name is michael@0: * that name with the string .local appended to it. Otherwise michael@0: * the effective host name is the same as the host name. Note michael@0: * that all effective host names contain at least one dot. michael@0: * michael@0: * @param origin origin where cookie is received from or being sent to. michael@0: * @return michael@0: */ michael@0: private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) { michael@0: String host = origin.getHost(); michael@0: michael@0: // Test if the host name appears to be a fully qualified DNS name, michael@0: // IPv4 address or IPv6 address michael@0: boolean isLocalHost = true; michael@0: for (int i = 0; i < host.length(); i++) { michael@0: char ch = host.charAt(i); michael@0: if (ch == '.' || ch == ':') { michael@0: isLocalHost = false; michael@0: break; michael@0: } michael@0: } michael@0: if (isLocalHost) { michael@0: host += ".local"; michael@0: return new CookieOrigin( michael@0: host, michael@0: origin.getPort(), michael@0: origin.getPath(), michael@0: origin.isSecure()); michael@0: } else { michael@0: return origin; michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public int getVersion() { michael@0: return 1; michael@0: } michael@0: michael@0: @Override michael@0: public Header getVersionHeader() { michael@0: CharArrayBuffer buffer = new CharArrayBuffer(40); michael@0: buffer.append(SM.COOKIE2); michael@0: buffer.append(": "); michael@0: buffer.append("$Version="); michael@0: buffer.append(Integer.toString(getVersion())); michael@0: return new BufferedHeader(buffer); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return "rfc2965"; michael@0: } michael@0: michael@0: } michael@0: