1 /*
2  * ====================================================================
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  * ====================================================================
20  *
21  * This software consists of voluntary contributions made by many
22  * individuals on behalf of the Apache Software Foundation.  For more
23  * information on the Apache Software Foundation, please see
24  * <http://www.apache.org/>.
25  *
26  */

27
28 package org.apache.http.impl.cookie;
29
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33
34 import org.apache.http.Header;
35 import org.apache.http.HeaderElement;
36 import org.apache.http.annotation.Contract;
37 import org.apache.http.annotation.Obsolete;
38 import org.apache.http.annotation.ThreadingBehavior;
39 import org.apache.http.client.utils.DateUtils;
40 import org.apache.http.cookie.ClientCookie;
41 import org.apache.http.cookie.CommonCookieAttributeHandler;
42 import org.apache.http.cookie.Cookie;
43 import org.apache.http.cookie.CookieOrigin;
44 import org.apache.http.cookie.CookiePathComparator;
45 import org.apache.http.cookie.CookieRestrictionViolationException;
46 import org.apache.http.cookie.MalformedCookieException;
47 import org.apache.http.cookie.SM;
48 import org.apache.http.message.BufferedHeader;
49 import org.apache.http.util.Args;
50 import org.apache.http.util.CharArrayBuffer;
51
52 /**
53  * RFC 2109 compliant {@link org.apache.http.cookie.CookieSpec} implementation.
54  * <p>
55  * Rendered obsolete by {@link org.apache.http.impl.cookie.RFC6265StrictSpec}.
56  *
57  * @since 4.0
58  * @see org.apache.http.impl.cookie.RFC6265StrictSpec
59  */

60 @Obsolete
61 @Contract(threading = ThreadingBehavior.SAFE)
62 public class RFC2109Spec extends CookieSpecBase {
63
64     final static String[] DATE_PATTERNS = {
65         DateUtils.PATTERN_RFC1123,
66         DateUtils.PATTERN_RFC1036,
67         DateUtils.PATTERN_ASCTIME
68     };
69
70     private final boolean oneHeader;
71
72     /** Default constructor */
73     public RFC2109Spec(final String[] datepatterns, final boolean oneHeader) {
74         super(new RFC2109VersionHandler(),
75                 new BasicPathHandler() {
76
77                     @Override
78                     public void validate(
79                             final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
80                         if (!match(cookie, origin)) {
81                             throw new CookieRestrictionViolationException(
82                                     "Illegal 'path' attribute \"" + cookie.getPath()
83                                             + "\". Path of origin: \"" + origin.getPath() + "\"");
84                         }
85                     }
86
87                 },
88                 new RFC2109DomainHandler(),
89                 new BasicMaxAgeHandler(),
90                 new BasicSecureHandler(),
91                 new BasicCommentHandler(),
92                 new BasicExpiresHandler(
93                         datepatterns != null ? datepatterns.clone() : DATE_PATTERNS));
94         this.oneHeader = oneHeader;
95     }
96
97     /** Default constructor */
98     public RFC2109Spec() {
99         this(nullfalse);
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         final 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         return this.oneHeader ? doFormatOneHeader(cookieList) : doFormatManyHeaders(cookieList);
147     }
148
149     private List<Header> doFormatOneHeader(final List<Cookie> cookies) {
150         int version = Integer.MAX_VALUE;
151         // Pick the lowest common denominator
152         for (final Cookie cookie : cookies) {
153             if (cookie.getVersion() < version) {
154                 version = cookie.getVersion();
155             }
156         }
157         final CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size());
158         buffer.append(SM.COOKIE);
159         buffer.append(": ");
160         buffer.append("$Version=");
161         buffer.append(Integer.toString(version));
162         for (final Cookie cooky : cookies) {
163             buffer.append("; ");
164             final Cookie cookie = cooky;
165             formatCookieAsVer(buffer, cookie, version);
166         }
167         final List<Header> headers = new ArrayList<Header>(1);
168         headers.add(new BufferedHeader(buffer));
169         return headers;
170     }
171
172     private List<Header> doFormatManyHeaders(final List<Cookie> cookies) {
173         final List<Header> headers = new ArrayList<Header>(cookies.size());
174         for (final Cookie cookie : cookies) {
175             final int version = cookie.getVersion();
176             final CharArrayBuffer buffer = new CharArrayBuffer(40);
177             buffer.append("Cookie: ");
178             buffer.append("$Version=");
179             buffer.append(Integer.toString(version));
180             buffer.append("; ");
181             formatCookieAsVer(buffer, cookie, version);
182             headers.add(new BufferedHeader(buffer));
183         }
184         return headers;
185     }
186
187     /**
188      * Return a name/value string suitable for sending in a {@code "Cookie"}
189      * header as defined in RFC 2109 for backward compatibility with cookie
190      * version 0
191      * @param buffer The char array buffer to use for output
192      * @param name The cookie name
193      * @param value The cookie value
194      * @param version The cookie version
195      */

196     protected void formatParamAsVer(final CharArrayBuffer buffer,
197             final String name, final String value, final int version) {
198         buffer.append(name);
199         buffer.append("=");
200         if (value != null) {
201             if (version > 0) {
202                 buffer.append('\"');
203                 buffer.append(value);
204                 buffer.append('\"');
205             } else {
206                 buffer.append(value);
207             }
208         }
209     }
210
211     /**
212      * Return a string suitable for sending in a {@code "Cookie"} header
213      * as defined in RFC 2109 for backward compatibility with cookie version 0
214      * @param buffer The char array buffer to use for output
215      * @param cookie The {@link Cookie} to be formatted as string
216      * @param version The version to use.
217      */

218     protected void formatCookieAsVer(final CharArrayBuffer buffer,
219             final Cookie cookie, final int version) {
220         formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version);
221         if (cookie.getPath() != null) {
222             if (cookie instanceof ClientCookie
223                     && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) {
224                 buffer.append("; ");
225                 formatParamAsVer(buffer, "$Path", cookie.getPath(), version);
226             }
227         }
228         if (cookie.getDomain() != null) {
229             if (cookie instanceof ClientCookie
230                     && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
231                 buffer.append("; ");
232                 formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version);
233             }
234         }
235     }
236
237     @Override
238     public int getVersion() {
239         return 1;
240     }
241
242     @Override
243     public Header getVersionHeader() {
244         return null;
245     }
246
247     @Override
248     public String toString() {
249         return "rfc2109";
250     }
251
252 }
253