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