RFC2109Spec.java revision 417f3b92ba4549b2f22340e3107d869d2b9c5bb8
1e476f8a161d445211fd6e54fe370275196e66bcbJason Evans/*
2e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java $
3e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * $Revision: 677240 $
4e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $
5e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *
6e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * ====================================================================
7e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * Licensed to the Apache Software Foundation (ASF) under one
8e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * or more contributor license agreements.  See the NOTICE file
9e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * distributed with this work for additional information
10e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * regarding copyright ownership.  The ASF licenses this file
11e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * to you under the Apache License, Version 2.0 (the
12e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * "License"); you may not use this file except in compliance
13e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * with the License.  You may obtain a copy of the License at
14e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *
15e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *   http://www.apache.org/licenses/LICENSE-2.0
16e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *
17e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * Unless required by applicable law or agreed to in writing,
18e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * software distributed under the License is distributed on an
19e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * KIND, either express or implied.  See the License for the
21e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * specific language governing permissions and limitations
22e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * under the License.
23e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * ====================================================================
24e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *
25e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * This software consists of voluntary contributions made by many
26e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * individuals on behalf of the Apache Software Foundation.  For more
27e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * information on the Apache Software Foundation, please see
28e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * <http://www.apache.org/>.
29e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *
304201af05425b69ee37ffca437aca0cdd604d1e51Jason Evans */
31609ae595f0358157b19311b0f9f9591db7cee705Jason Evans
324201af05425b69ee37ffca437aca0cdd604d1e51Jason Evanspackage org.apache.http.impl.cookie;
333c2343518c2b1fbbd66065c75a3c19f908de1d78Jason Evans
343c2343518c2b1fbbd66065c75a3c19f908de1d78Jason Evansimport java.util.ArrayList;
35e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport java.util.Collections;
36e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport java.util.List;
37e476f8a161d445211fd6e54fe370275196e66bcbJason Evans
382dbecf1f6267fae7a161b9c39cfd4d04ce168a29Jason Evansimport org.apache.http.Header;
392dbecf1f6267fae7a161b9c39cfd4d04ce168a29Jason Evansimport org.apache.http.HeaderElement;
40e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport org.apache.http.cookie.ClientCookie;
41e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport org.apache.http.cookie.Cookie;
42e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport org.apache.http.cookie.CookieOrigin;
437393f44ff025ca67716fc53b68003fd65122fd97Jason Evansimport org.apache.http.cookie.CookiePathComparator;
44e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport org.apache.http.cookie.MalformedCookieException;
45e476f8a161d445211fd6e54fe370275196e66bcbJason Evansimport org.apache.http.cookie.SM;
4612141150fdbda57651a53ae2fe0edaea4891d814Jason Evansimport org.apache.http.message.BufferedHeader;
4712141150fdbda57651a53ae2fe0edaea4891d814Jason Evansimport org.apache.http.util.CharArrayBuffer;
4812141150fdbda57651a53ae2fe0edaea4891d814Jason Evans
4912141150fdbda57651a53ae2fe0edaea4891d814Jason Evans/**
5059113bcc94b9fc7549611afb99ca99cad1a7f196aravind * RFC 2109 compliant cookie policy
5159113bcc94b9fc7549611afb99ca99cad1a7f196aravind *
52609ae595f0358157b19311b0f9f9591db7cee705Jason Evans * @author  B.C. Holmes
5312141150fdbda57651a53ae2fe0edaea4891d814Jason Evans * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
54a8f8d7540d66ddee7337db80c92890916e1063caJason Evans * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
5520f1fc95adb35ea63dc61f47f2b0ffbd37d39f32Jason Evans * @author Rod Waldhoff
5620f1fc95adb35ea63dc61f47f2b0ffbd37d39f32Jason Evans * @author dIon Gillard
5720f1fc95adb35ea63dc61f47f2b0ffbd37d39f32Jason Evans * @author Sean C. Sullivan
58e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
59e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * @author Marc A. Saegesser
60e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
61e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
62e476f8a161d445211fd6e54fe370275196e66bcbJason Evans *
63e476f8a161d445211fd6e54fe370275196e66bcbJason Evans * @since 4.0
64e476f8a161d445211fd6e54fe370275196e66bcbJason Evans */
654201af05425b69ee37ffca437aca0cdd604d1e51Jason Evans
66376b1529a383c39adf4674baf6db83a5e63f97acJason Evanspublic class RFC2109Spec extends CookieSpecBase {
67376b1529a383c39adf4674baf6db83a5e63f97acJason Evans
68    private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator();
69
70    private final static String[] DATE_PATTERNS = {
71        DateUtils.PATTERN_RFC1123,
72        DateUtils.PATTERN_RFC1036,
73        DateUtils.PATTERN_ASCTIME
74    };
75
76    private final String[] datepatterns;
77    private final boolean oneHeader;
78
79    /** Default constructor */
80    public RFC2109Spec(final String[] datepatterns, boolean oneHeader) {
81        super();
82        if (datepatterns != null) {
83            this.datepatterns = datepatterns.clone();
84        } else {
85            this.datepatterns = DATE_PATTERNS;
86        }
87        this.oneHeader = oneHeader;
88        registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2109VersionHandler());
89        registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler());
90        registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2109DomainHandler());
91        registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler());
92        registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler());
93        registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler());
94        registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler(
95                this.datepatterns));
96    }
97
98    /** Default constructor */
99    public RFC2109Spec() {
100        this(null, false);
101    }
102
103    public List<Cookie> parse(final Header header, final CookieOrigin origin)
104            throws MalformedCookieException {
105        if (header == null) {
106            throw new IllegalArgumentException("Header may not be null");
107        }
108        if (origin == null) {
109            throw new IllegalArgumentException("Cookie origin may not be null");
110        }
111        HeaderElement[] elems = header.getElements();
112        return parse(elems, origin);
113    }
114
115    @Override
116    public void validate(final Cookie cookie, final CookieOrigin origin)
117            throws MalformedCookieException {
118        if (cookie == null) {
119            throw new IllegalArgumentException("Cookie may not be null");
120        }
121        String name = cookie.getName();
122        if (name.indexOf(' ') != -1) {
123            throw new MalformedCookieException("Cookie name may not contain blanks");
124        }
125        if (name.startsWith("$")) {
126            throw new MalformedCookieException("Cookie name may not start with $");
127        }
128        super.validate(cookie, origin);
129    }
130
131    public List<Header> formatCookies(List<Cookie> cookies) {
132        if (cookies == null) {
133            throw new IllegalArgumentException("List of cookies may not be null");
134        }
135        if (cookies.isEmpty()) {
136            throw new IllegalArgumentException("List of cookies may not be empty");
137        }
138        if (cookies.size() > 1) {
139            // Create a mutable copy and sort the copy.
140            cookies = new ArrayList<Cookie>(cookies);
141            Collections.sort(cookies, PATH_COMPARATOR);
142        }
143        if (this.oneHeader) {
144            return doFormatOneHeader(cookies);
145        } else {
146            return doFormatManyHeaders(cookies);
147        }
148    }
149
150    private List<Header> doFormatOneHeader(final List<Cookie> cookies) {
151        int version = Integer.MAX_VALUE;
152        // Pick the lowest common denominator
153        for (Cookie cookie : cookies) {
154            if (cookie.getVersion() < version) {
155                version = cookie.getVersion();
156            }
157        }
158        CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size());
159        buffer.append(SM.COOKIE);
160        buffer.append(": ");
161        buffer.append("$Version=");
162        buffer.append(Integer.toString(version));
163        for (Cookie cooky : cookies) {
164            buffer.append("; ");
165            Cookie cookie = cooky;
166            formatCookieAsVer(buffer, cookie, version);
167        }
168        List<Header> headers = new ArrayList<Header>(1);
169        headers.add(new BufferedHeader(buffer));
170        return headers;
171    }
172
173    private List<Header> doFormatManyHeaders(final List<Cookie> cookies) {
174        List<Header> headers = new ArrayList<Header>(cookies.size());
175        for (Cookie cookie : cookies) {
176            int version = cookie.getVersion();
177            CharArrayBuffer buffer = new CharArrayBuffer(40);
178            buffer.append("Cookie: ");
179            buffer.append("$Version=");
180            buffer.append(Integer.toString(version));
181            buffer.append("; ");
182            formatCookieAsVer(buffer, cookie, version);
183            headers.add(new BufferedHeader(buffer));
184        }
185        return headers;
186    }
187
188    /**
189     * Return a name/value string suitable for sending in a <tt>"Cookie"</tt>
190     * header as defined in RFC 2109 for backward compatibility with cookie
191     * version 0
192     * @param buffer The char array buffer to use for output
193     * @param name The cookie name
194     * @param value The cookie value
195     * @param version The cookie version
196     */
197    protected void formatParamAsVer(final CharArrayBuffer buffer,
198            final String name, final String value, int version) {
199        buffer.append(name);
200        buffer.append("=");
201        if (value != null) {
202            if (version > 0) {
203                buffer.append('\"');
204                buffer.append(value);
205                buffer.append('\"');
206            } else {
207                buffer.append(value);
208            }
209        }
210    }
211
212    /**
213     * Return a string suitable for sending in a <tt>"Cookie"</tt> header
214     * as defined in RFC 2109 for backward compatibility with cookie version 0
215     * @param buffer The char array buffer to use for output
216     * @param cookie The {@link Cookie} to be formatted as string
217     * @param version The version to use.
218     */
219    protected void formatCookieAsVer(final CharArrayBuffer buffer,
220            final Cookie cookie, int version) {
221        formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version);
222        if (cookie.getPath() != null) {
223            if (cookie instanceof ClientCookie
224                    && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) {
225                buffer.append("; ");
226                formatParamAsVer(buffer, "$Path", cookie.getPath(), version);
227            }
228        }
229        if (cookie.getDomain() != null) {
230            if (cookie instanceof ClientCookie
231                    && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
232                buffer.append("; ");
233                formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version);
234            }
235        }
236    }
237
238    public int getVersion() {
239        return 1;
240    }
241
242    public Header getVersionHeader() {
243        return null;
244    }
245
246}
247