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  import java.util.Locale;
34  
35  import org.apache.http.FormattedHeader;
36  import org.apache.http.Header;
37  import org.apache.http.HeaderElement;
38  import org.apache.http.NameValuePair;
39  import org.apache.http.annotation.Contract;
40  import org.apache.http.annotation.ThreadingBehavior;
41  import org.apache.http.client.utils.DateUtils;
42  import org.apache.http.cookie.Cookie;
43  import org.apache.http.cookie.CookieAttributeHandler;
44  import org.apache.http.cookie.CookieOrigin;
45  import org.apache.http.cookie.MalformedCookieException;
46  import org.apache.http.cookie.SM;
47  import org.apache.http.message.BasicHeaderElement;
48  import org.apache.http.message.BasicHeaderValueFormatter;
49  import org.apache.http.message.BufferedHeader;
50  import org.apache.http.message.ParserCursor;
51  import org.apache.http.util.Args;
52  import org.apache.http.util.CharArrayBuffer;
53  
54  
55  /**
56   * Cookie specification that strives to closely mimic (mis)behavior of
57   * common web browser applications such as Microsoft Internet Explorer
58   * and Mozilla FireFox.
59   *
60   * @deprecated (4.4) use {@link org.apache.http.impl.cookie.DefaultCookieSpec}.
61   *
62   * @since 4.0
63   */
64  @Deprecated
65  @Contract(threading = ThreadingBehavior.SAFE)
66  public class BrowserCompatSpec extends CookieSpecBase {
67  
68  
69      private static final String[] DEFAULT_DATE_PATTERNS = new String[] {
70          DateUtils.PATTERN_RFC1123,
71          DateUtils.PATTERN_RFC1036,
72          DateUtils.PATTERN_ASCTIME,
73          "EEE, dd-MMM-yyyy HH:mm:ss z",
74          "EEE, dd-MMM-yyyy HH-mm-ss z",
75          "EEE, dd MMM yy HH:mm:ss z",
76          "EEE dd-MMM-yyyy HH:mm:ss z",
77          "EEE dd MMM yyyy HH:mm:ss z",
78          "EEE dd-MMM-yyyy HH-mm-ss z",
79          "EEE dd-MMM-yy HH:mm:ss z",
80          "EEE dd MMM yy HH:mm:ss z",
81          "EEE,dd-MMM-yy HH:mm:ss z",
82          "EEE,dd-MMM-yyyy HH:mm:ss z",
83          "EEE, dd-MM-yyyy HH:mm:ss z",
84      };
85  
86      /** Default constructor */
87      public BrowserCompatSpec(final String[] datepatterns, final BrowserCompatSpecFactory.SecurityLevel securityLevel) {
88          super(new BrowserCompatVersionAttributeHandler(),
89                  new BasicDomainHandler(),
90                  securityLevel == BrowserCompatSpecFactory.SecurityLevel.SECURITYLEVEL_IE_MEDIUM ?
91                          new BasicPathHandler() {
92                              @Override
93                              public void validate(final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
94                                  // No validation
95                              }
96                          } : new BasicPathHandler(),
97                  new BasicMaxAgeHandler(),
98                  new BasicSecureHandler(),
99                  new BasicCommentHandler(),
100                 new BasicExpiresHandler(datepatterns != null ? datepatterns.clone() : DEFAULT_DATE_PATTERNS));
101     }
102 
103     /** Default constructor */
104     public BrowserCompatSpec(final String[] datepatterns) {
105         this(datepatterns, BrowserCompatSpecFactory.SecurityLevel.SECURITYLEVEL_DEFAULT);
106     }
107 
108     /** Default constructor */
109     public BrowserCompatSpec() {
110         this(null, BrowserCompatSpecFactory.SecurityLevel.SECURITYLEVEL_DEFAULT);
111     }
112 
113     @Override
114     public List<Cookie> parse(final Header header, final CookieOrigin origin)
115             throws MalformedCookieException {
116         Args.notNull(header, "Header");
117         Args.notNull(origin, "Cookie origin");
118         final String headername = header.getName();
119         if (!headername.equalsIgnoreCase(SM.SET_COOKIE)) {
120             throw new MalformedCookieException("Unrecognized cookie header '"
121                     + header.toString() + "'");
122         }
123         final HeaderElement[] helems = header.getElements();
124         boolean versioned = false;
125         boolean netscape = false;
126         for (final HeaderElement helem: helems) {
127             if (helem.getParameterByName("version") != null) {
128                 versioned = true;
129             }
130             if (helem.getParameterByName("expires") != null) {
131                netscape = true;
132             }
133         }
134         if (netscape || !versioned) {
135             // Need to parse the header again, because Netscape style cookies do not correctly
136             // support multiple header elements (comma cannot be treated as an element separator)
137             final NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT;
138             final CharArrayBuffer buffer;
139             final ParserCursor cursor;
140             if (header instanceof FormattedHeader) {
141                 buffer = ((FormattedHeader) header).getBuffer();
142                 cursor = new ParserCursor(
143                         ((FormattedHeader) header).getValuePos(),
144                         buffer.length());
145             } else {
146                 final String s = header.getValue();
147                 if (s == null) {
148                     throw new MalformedCookieException("Header value is null");
149                 }
150                 buffer = new CharArrayBuffer(s.length());
151                 buffer.append(s);
152                 cursor = new ParserCursor(0, buffer.length());
153             }
154             final HeaderElement elem = parser.parseHeader(buffer, cursor);
155             final String name = elem.getName();
156             final String value = elem.getValue();
157             if (name == null || name.isEmpty()) {
158                 throw new MalformedCookieException("Cookie name may not be empty");
159             }
160             final BasicClientCookie cookie = new BasicClientCookie(name, value);
161             cookie.setPath(getDefaultPath(origin));
162             cookie.setDomain(getDefaultDomain(origin));
163 
164             // cycle through the parameters
165             final NameValuePair[] attribs = elem.getParameters();
166             for (int j = attribs.length - 1; j >= 0; j--) {
167                 final NameValuePair attrib = attribs[j];
168                 final String s = attrib.getName().toLowerCase(Locale.ROOT);
169                 cookie.setAttribute(s, attrib.getValue());
170                 final CookieAttributeHandler handler = findAttribHandler(s);
171                 if (handler != null) {
172                     handler.parse(cookie, attrib.getValue());
173                 }
174             }
175             // Override version for Netscape style cookies
176             if (netscape) {
177                 cookie.setVersion(0);
178             }
179             return Collections.<Cookie>singletonList(cookie);
180         } else {
181             return parse(helems, origin);
182         }
183     }
184 
185     private static boolean isQuoteEnclosed(final String s) {
186         return s != null && s.startsWith("\"") && s.endsWith("\"");
187     }
188 
189     @Override
190     public List<Header> formatCookies(final List<Cookie> cookies) {
191         Args.notEmpty(cookies, "List of cookies");
192         final CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size());
193         buffer.append(SM.COOKIE);
194         buffer.append(": ");
195         for (int i = 0; i < cookies.size(); i++) {
196             final Cookie cookie = cookies.get(i);
197             if (i > 0) {
198                 buffer.append("; ");
199             }
200             final String cookieName = cookie.getName();
201             final String cookieValue = cookie.getValue();
202             if (cookie.getVersion() > 0 && !isQuoteEnclosed(cookieValue)) {
203                 BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(
204                         buffer,
205                         new BasicHeaderElement(cookieName, cookieValue),
206                         false);
207             } else {
208                 // Netscape style cookies do not support quoted values
209                 buffer.append(cookieName);
210                 buffer.append("=");
211                 if (cookieValue != null) {
212                     buffer.append(cookieValue);
213                 }
214             }
215         }
216         final List<Header> headers = new ArrayList<Header>(1);
217         headers.add(new BufferedHeader(buffer));
218         return headers;
219     }
220 
221     @Override
222     public int getVersion() {
223         return 0;
224     }
225 
226     @Override
227     public Header getVersionHeader() {
228         return null;
229     }
230 
231     @Override
232     public String toString() {
233         return "compatibility";
234     }
235 
236 }