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.ArrayList;
031import java.util.Collections;
032import java.util.List;
033
034import org.apache.http.Header;
035import org.apache.http.HeaderElement;
036import org.apache.http.annotation.Contract;
037import org.apache.http.annotation.Obsolete;
038import org.apache.http.annotation.ThreadingBehavior;
039import org.apache.http.client.utils.DateUtils;
040import org.apache.http.cookie.ClientCookie;
041import org.apache.http.cookie.CommonCookieAttributeHandler;
042import org.apache.http.cookie.Cookie;
043import org.apache.http.cookie.CookieOrigin;
044import org.apache.http.cookie.CookiePathComparator;
045import org.apache.http.cookie.CookieRestrictionViolationException;
046import org.apache.http.cookie.MalformedCookieException;
047import org.apache.http.cookie.SM;
048import org.apache.http.message.BufferedHeader;
049import org.apache.http.util.Args;
050import org.apache.http.util.CharArrayBuffer;
051
052/**
053 * RFC 2109 compliant {@link org.apache.http.cookie.CookieSpec} implementation.
054 * <p>
055 * Rendered obsolete by {@link org.apache.http.impl.cookie.RFC6265StrictSpec}.
056 *
057 * @since 4.0
058 * @see org.apache.http.impl.cookie.RFC6265StrictSpec
059 */
060@Obsolete
061@Contract(threading = ThreadingBehavior.SAFE)
062public class RFC2109Spec extends CookieSpecBase {
063
064    final static String[] DATE_PATTERNS = {
065        DateUtils.PATTERN_RFC1123,
066        DateUtils.PATTERN_RFC1036,
067        DateUtils.PATTERN_ASCTIME
068    };
069
070    private final boolean oneHeader;
071
072    /** Default constructor */
073    public RFC2109Spec(final String[] datepatterns, final boolean oneHeader) {
074        super(new RFC2109VersionHandler(),
075                new BasicPathHandler() {
076
077                    @Override
078                    public void validate(
079                            final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
080                        if (!match(cookie, origin)) {
081                            throw new CookieRestrictionViolationException(
082                                    "Illegal 'path' attribute \"" + cookie.getPath()
083                                            + "\". Path of origin: \"" + origin.getPath() + "\"");
084                        }
085                    }
086
087                },
088                new RFC2109DomainHandler(),
089                new BasicMaxAgeHandler(),
090                new BasicSecureHandler(),
091                new BasicCommentHandler(),
092                new BasicExpiresHandler(
093                        datepatterns != null ? datepatterns.clone() : DATE_PATTERNS));
094        this.oneHeader = oneHeader;
095    }
096
097    /** Default constructor */
098    public RFC2109Spec() {
099        this(null, false);
100    }
101
102    protected RFC2109Spec(final boolean oneHeader,
103                          final CommonCookieAttributeHandler... handlers) {
104        super(handlers);
105        this.oneHeader = oneHeader;
106    }
107
108    @Override
109    public List<Cookie> parse(final Header header, final CookieOrigin origin)
110            throws MalformedCookieException {
111        Args.notNull(header, "Header");
112        Args.notNull(origin, "Cookie origin");
113        if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) {
114            throw new MalformedCookieException("Unrecognized cookie header '"
115                    + header.toString() + "'");
116        }
117        final HeaderElement[] elems = header.getElements();
118        return parse(elems, origin);
119    }
120
121    @Override
122    public void validate(final Cookie cookie, final CookieOrigin origin)
123            throws MalformedCookieException {
124        Args.notNull(cookie, "Cookie");
125        final String name = cookie.getName();
126        if (name.indexOf(' ') != -1) {
127            throw new CookieRestrictionViolationException("Cookie name may not contain blanks");
128        }
129        if (name.startsWith("$")) {
130            throw new CookieRestrictionViolationException("Cookie name may not start with $");
131        }
132        super.validate(cookie, origin);
133    }
134
135    @Override
136    public List<Header> formatCookies(final List<Cookie> cookies) {
137        Args.notEmpty(cookies, "List of cookies");
138        List<Cookie> cookieList;
139        if (cookies.size() > 1) {
140            // Create a mutable copy and sort the copy.
141            cookieList = new ArrayList<Cookie>(cookies);
142            Collections.sort(cookieList, CookiePathComparator.INSTANCE);
143        } else {
144            cookieList = cookies;
145        }
146        if (this.oneHeader) {
147            return doFormatOneHeader(cookieList);
148        } else {
149            return doFormatManyHeaders(cookieList);
150        }
151    }
152
153    private List<Header> doFormatOneHeader(final List<Cookie> cookies) {
154        int version = Integer.MAX_VALUE;
155        // Pick the lowest common denominator
156        for (final Cookie cookie : cookies) {
157            if (cookie.getVersion() < version) {
158                version = cookie.getVersion();
159            }
160        }
161        final CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size());
162        buffer.append(SM.COOKIE);
163        buffer.append(": ");
164        buffer.append("$Version=");
165        buffer.append(Integer.toString(version));
166        for (final Cookie cooky : cookies) {
167            buffer.append("; ");
168            final Cookie cookie = cooky;
169            formatCookieAsVer(buffer, cookie, version);
170        }
171        final List<Header> headers = new ArrayList<Header>(1);
172        headers.add(new BufferedHeader(buffer));
173        return headers;
174    }
175
176    private List<Header> doFormatManyHeaders(final List<Cookie> cookies) {
177        final List<Header> headers = new ArrayList<Header>(cookies.size());
178        for (final Cookie cookie : cookies) {
179            final int version = cookie.getVersion();
180            final CharArrayBuffer buffer = new CharArrayBuffer(40);
181            buffer.append("Cookie: ");
182            buffer.append("$Version=");
183            buffer.append(Integer.toString(version));
184            buffer.append("; ");
185            formatCookieAsVer(buffer, cookie, version);
186            headers.add(new BufferedHeader(buffer));
187        }
188        return headers;
189    }
190
191    /**
192     * Return a name/value string suitable for sending in a {@code "Cookie"}
193     * header as defined in RFC 2109 for backward compatibility with cookie
194     * version 0
195     * @param buffer The char array buffer to use for output
196     * @param name The cookie name
197     * @param value The cookie value
198     * @param version The cookie version
199     */
200    protected void formatParamAsVer(final CharArrayBuffer buffer,
201            final String name, final String value, final int version) {
202        buffer.append(name);
203        buffer.append("=");
204        if (value != null) {
205            if (version > 0) {
206                buffer.append('\"');
207                buffer.append(value);
208                buffer.append('\"');
209            } else {
210                buffer.append(value);
211            }
212        }
213    }
214
215    /**
216     * Return a string suitable for sending in a {@code "Cookie"} header
217     * as defined in RFC 2109 for backward compatibility with cookie version 0
218     * @param buffer The char array buffer to use for output
219     * @param cookie The {@link Cookie} to be formatted as string
220     * @param version The version to use.
221     */
222    protected void formatCookieAsVer(final CharArrayBuffer buffer,
223            final Cookie cookie, final int version) {
224        formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version);
225        if (cookie.getPath() != null) {
226            if (cookie instanceof ClientCookie
227                    && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) {
228                buffer.append("; ");
229                formatParamAsVer(buffer, "$Path", cookie.getPath(), version);
230            }
231        }
232        if (cookie.getDomain() != null) {
233            if (cookie instanceof ClientCookie
234                    && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
235                buffer.append("; ");
236                formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version);
237            }
238        }
239    }
240
241    @Override
242    public int getVersion() {
243        return 1;
244    }
245
246    @Override
247    public Header getVersionHeader() {
248        return null;
249    }
250
251    @Override
252    public String toString() {
253        return "rfc2109";
254    }
255
256}