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.List;
031
032import org.apache.http.FormattedHeader;
033import org.apache.http.Header;
034import org.apache.http.HeaderElement;
035import org.apache.http.annotation.Contract;
036import org.apache.http.annotation.ThreadingBehavior;
037import org.apache.http.cookie.Cookie;
038import org.apache.http.cookie.CookieOrigin;
039import org.apache.http.cookie.CookieSpec;
040import org.apache.http.cookie.MalformedCookieException;
041import org.apache.http.cookie.SM;
042import org.apache.http.cookie.SetCookie2;
043import org.apache.http.message.ParserCursor;
044import org.apache.http.util.Args;
045import org.apache.http.util.CharArrayBuffer;
046
047/**
048 * Default cookie specification that picks up the best matching cookie policy based on
049 * the format of cookies sent with the HTTP response.
050 *
051 * @since 4.4
052 */
053@Contract(threading = ThreadingBehavior.SAFE)
054public class DefaultCookieSpec implements CookieSpec {
055
056    private final RFC2965Spec strict;
057    private final RFC2109Spec obsoleteStrict;
058    private final NetscapeDraftSpec netscapeDraft;
059
060    DefaultCookieSpec(
061            final RFC2965Spec strict,
062            final RFC2109Spec obsoleteStrict,
063            final NetscapeDraftSpec netscapeDraft) {
064        this.strict = strict;
065        this.obsoleteStrict = obsoleteStrict;
066        this.netscapeDraft = netscapeDraft;
067    }
068
069    public DefaultCookieSpec(
070            final String[] datepatterns,
071            final boolean oneHeader) {
072        this.strict = new RFC2965Spec(oneHeader,
073                new RFC2965VersionAttributeHandler(),
074                new BasicPathHandler(),
075                new RFC2965DomainAttributeHandler(),
076                new RFC2965PortAttributeHandler(),
077                new BasicMaxAgeHandler(),
078                new BasicSecureHandler(),
079                new BasicCommentHandler(),
080                new RFC2965CommentUrlAttributeHandler(),
081                new RFC2965DiscardAttributeHandler());
082        this.obsoleteStrict = new RFC2109Spec(oneHeader,
083                new RFC2109VersionHandler(),
084                new BasicPathHandler(),
085                new RFC2109DomainHandler(),
086                new BasicMaxAgeHandler(),
087                new BasicSecureHandler(),
088                new BasicCommentHandler());
089        this.netscapeDraft = new NetscapeDraftSpec(
090                new BasicDomainHandler(),
091                new BasicPathHandler(),
092                new BasicSecureHandler(),
093                new BasicCommentHandler(),
094                new BasicExpiresHandler(
095                        datepatterns != null ? datepatterns.clone() : new String[]{NetscapeDraftSpec.EXPIRES_PATTERN}));
096    }
097
098    public DefaultCookieSpec() {
099        this(null, false);
100    }
101
102    @Override
103    public List<Cookie> parse(
104            final Header header,
105            final CookieOrigin origin) throws MalformedCookieException {
106        Args.notNull(header, "Header");
107        Args.notNull(origin, "Cookie origin");
108        HeaderElement[] helems = header.getElements();
109        boolean versioned = false;
110        boolean netscape = false;
111        for (final HeaderElement helem: helems) {
112            if (helem.getParameterByName("version") != null) {
113                versioned = true;
114            }
115            if (helem.getParameterByName("expires") != null) {
116               netscape = true;
117            }
118        }
119        if (netscape || !versioned) {
120            // Need to parse the header again, because Netscape style cookies do not correctly
121            // support multiple header elements (comma cannot be treated as an element separator)
122            final NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT;
123            final CharArrayBuffer buffer;
124            final ParserCursor cursor;
125            if (header instanceof FormattedHeader) {
126                buffer = ((FormattedHeader) header).getBuffer();
127                cursor = new ParserCursor(
128                        ((FormattedHeader) header).getValuePos(),
129                        buffer.length());
130            } else {
131                final String s = header.getValue();
132                if (s == null) {
133                    throw new MalformedCookieException("Header value is null");
134                }
135                buffer = new CharArrayBuffer(s.length());
136                buffer.append(s);
137                cursor = new ParserCursor(0, buffer.length());
138            }
139            helems = new HeaderElement[] { parser.parseHeader(buffer, cursor) };
140            return netscapeDraft.parse(helems, origin);
141        } else {
142            if (SM.SET_COOKIE2.equals(header.getName())) {
143                return strict.parse(helems, origin);
144            } else {
145                return obsoleteStrict.parse(helems, origin);
146            }
147        }
148    }
149
150    @Override
151    public void validate(
152            final Cookie cookie,
153            final CookieOrigin origin) throws MalformedCookieException {
154        Args.notNull(cookie, "Cookie");
155        Args.notNull(origin, "Cookie origin");
156        if (cookie.getVersion() > 0) {
157            if (cookie instanceof SetCookie2) {
158                strict.validate(cookie, origin);
159            } else {
160                obsoleteStrict.validate(cookie, origin);
161            }
162        } else {
163            netscapeDraft.validate(cookie, origin);
164        }
165    }
166
167    @Override
168    public boolean match(final Cookie cookie, final CookieOrigin origin) {
169        Args.notNull(cookie, "Cookie");
170        Args.notNull(origin, "Cookie origin");
171        if (cookie.getVersion() > 0) {
172            if (cookie instanceof SetCookie2) {
173                return strict.match(cookie, origin);
174            } else {
175                return obsoleteStrict.match(cookie, origin);
176            }
177        } else {
178            return netscapeDraft.match(cookie, origin);
179        }
180    }
181
182    @Override
183    public List<Header> formatCookies(final List<Cookie> cookies) {
184        Args.notNull(cookies, "List of cookies");
185        int version = Integer.MAX_VALUE;
186        boolean isSetCookie2 = true;
187        for (final Cookie cookie: cookies) {
188            if (!(cookie instanceof SetCookie2)) {
189                isSetCookie2 = false;
190            }
191            if (cookie.getVersion() < version) {
192                version = cookie.getVersion();
193            }
194        }
195        if (version > 0) {
196            if (isSetCookie2) {
197                return strict.formatCookies(cookies);
198            } else {
199                return obsoleteStrict.formatCookies(cookies);
200            }
201        } else {
202            return netscapeDraft.formatCookies(cookies);
203        }
204    }
205
206    @Override
207    public int getVersion() {
208        return strict.getVersion();
209    }
210
211    @Override
212    public Header getVersionHeader() {
213        return null;
214    }
215
216    @Override
217    public String toString() {
218        return "default";
219    }
220
221}