View Javadoc

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.ThreadSafe;
37  import org.apache.http.client.utils.DateUtils;
38  import org.apache.http.cookie.ClientCookie;
39  import org.apache.http.cookie.CommonCookieAttributeHandler;
40  import org.apache.http.cookie.Cookie;
41  import org.apache.http.cookie.CookieOrigin;
42  import org.apache.http.cookie.CookiePathComparator;
43  import org.apache.http.cookie.CookieRestrictionViolationException;
44  import org.apache.http.cookie.MalformedCookieException;
45  import org.apache.http.cookie.SM;
46  import org.apache.http.message.BufferedHeader;
47  import org.apache.http.util.Args;
48  import org.apache.http.util.CharArrayBuffer;
49  
50  /**
51   * RFC 2109 compliant {@link org.apache.http.cookie.CookieSpec} implementation.
52   * This is an older version of the official HTTP state management specification
53   * superseded by RFC 2965.
54   *
55   * @see RFC2965Spec
56   *
57   * @since 4.0
58   */
59  @ThreadSafe
60  public class RFC2109Spec extends CookieSpecBase {
61  
62      private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator();
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                  new RFC2109DomainHandler(),
77                  new BasicMaxAgeHandler(),
78                  new BasicSecureHandler(),
79                  new BasicCommentHandler(),
80                  new BasicExpiresHandler(
81                          datepatterns != null ? datepatterns.clone() : DATE_PATTERNS));
82          this.oneHeader = oneHeader;
83      }
84  
85      /** Default constructor */
86      public RFC2109Spec() {
87          this(null, false);
88      }
89  
90      protected RFC2109Spec(final boolean oneHeader,
91                            final CommonCookieAttributeHandler... handlers) {
92          super(handlers);
93          this.oneHeader = oneHeader;
94      }
95  
96      @Override
97      public List<Cookie> parse(final Header header, final CookieOrigin origin)
98              throws MalformedCookieException {
99          Args.notNull(header, "Header");
100         Args.notNull(origin, "Cookie origin");
101         if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) {
102             throw new MalformedCookieException("Unrecognized cookie header '"
103                     + header.toString() + "'");
104         }
105         final HeaderElement[] elems = header.getElements();
106         return parse(elems, origin);
107     }
108 
109     @Override
110     public void validate(final Cookie cookie, final CookieOrigin origin)
111             throws MalformedCookieException {
112         Args.notNull(cookie, "Cookie");
113         final String name = cookie.getName();
114         if (name.indexOf(' ') != -1) {
115             throw new CookieRestrictionViolationException("Cookie name may not contain blanks");
116         }
117         if (name.startsWith("$")) {
118             throw new CookieRestrictionViolationException("Cookie name may not start with $");
119         }
120         super.validate(cookie, origin);
121     }
122 
123     @Override
124     public List<Header> formatCookies(final List<Cookie> cookies) {
125         Args.notEmpty(cookies, "List of cookies");
126         List<Cookie> cookieList;
127         if (cookies.size() > 1) {
128             // Create a mutable copy and sort the copy.
129             cookieList = new ArrayList<Cookie>(cookies);
130             Collections.sort(cookieList, PATH_COMPARATOR);
131         } else {
132             cookieList = cookies;
133         }
134         if (this.oneHeader) {
135             return doFormatOneHeader(cookieList);
136         } else {
137             return doFormatManyHeaders(cookieList);
138         }
139     }
140 
141     private List<Header> doFormatOneHeader(final List<Cookie> cookies) {
142         int version = Integer.MAX_VALUE;
143         // Pick the lowest common denominator
144         for (final Cookie cookie : cookies) {
145             if (cookie.getVersion() < version) {
146                 version = cookie.getVersion();
147             }
148         }
149         final CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size());
150         buffer.append(SM.COOKIE);
151         buffer.append(": ");
152         buffer.append("$Version=");
153         buffer.append(Integer.toString(version));
154         for (final Cookie cooky : cookies) {
155             buffer.append("; ");
156             final Cookie cookie = cooky;
157             formatCookieAsVer(buffer, cookie, version);
158         }
159         final List<Header> headers = new ArrayList<Header>(1);
160         headers.add(new BufferedHeader(buffer));
161         return headers;
162     }
163 
164     private List<Header> doFormatManyHeaders(final List<Cookie> cookies) {
165         final List<Header> headers = new ArrayList<Header>(cookies.size());
166         for (final Cookie cookie : cookies) {
167             final int version = cookie.getVersion();
168             final CharArrayBuffer buffer = new CharArrayBuffer(40);
169             buffer.append("Cookie: ");
170             buffer.append("$Version=");
171             buffer.append(Integer.toString(version));
172             buffer.append("; ");
173             formatCookieAsVer(buffer, cookie, version);
174             headers.add(new BufferedHeader(buffer));
175         }
176         return headers;
177     }
178 
179     /**
180      * Return a name/value string suitable for sending in a {@code "Cookie"}
181      * header as defined in RFC 2109 for backward compatibility with cookie
182      * version 0
183      * @param buffer The char array buffer to use for output
184      * @param name The cookie name
185      * @param value The cookie value
186      * @param version The cookie version
187      */
188     protected void formatParamAsVer(final CharArrayBuffer buffer,
189             final String name, final String value, final int version) {
190         buffer.append(name);
191         buffer.append("=");
192         if (value != null) {
193             if (version > 0) {
194                 buffer.append('\"');
195                 buffer.append(value);
196                 buffer.append('\"');
197             } else {
198                 buffer.append(value);
199             }
200         }
201     }
202 
203     /**
204      * Return a string suitable for sending in a {@code "Cookie"} header
205      * as defined in RFC 2109 for backward compatibility with cookie version 0
206      * @param buffer The char array buffer to use for output
207      * @param cookie The {@link Cookie} to be formatted as string
208      * @param version The version to use.
209      */
210     protected void formatCookieAsVer(final CharArrayBuffer buffer,
211             final Cookie cookie, final int version) {
212         formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version);
213         if (cookie.getPath() != null) {
214             if (cookie instanceof ClientCookie
215                     && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) {
216                 buffer.append("; ");
217                 formatParamAsVer(buffer, "$Path", cookie.getPath(), version);
218             }
219         }
220         if (cookie.getDomain() != null) {
221             if (cookie instanceof ClientCookie
222                     && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
223                 buffer.append("; ");
224                 formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version);
225             }
226         }
227     }
228 
229     @Override
230     public int getVersion() {
231         return 1;
232     }
233 
234     @Override
235     public Header getVersionHeader() {
236         return null;
237     }
238 
239     @Override
240     public String toString() {
241         return "rfc2109";
242     }
243 
244 }