View Javadoc

1   /*
2    * ====================================================================
3    *
4    *  Licensed to the Apache Software Foundation (ASF) under one or more
5    *  contributor license agreements.  See the NOTICE file distributed with
6    *  this work for additional information regarding copyright ownership.
7    *  The ASF licenses this file to You under the Apache License, Version 2.0
8    *  (the "License"); you may not use this file except in compliance with
9    *  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, software
14   *  distributed under the License is distributed on an "AS IS" BASIS,
15   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *  See the License for the specific language governing permissions and
17   *  limitations under the License.
18   * ====================================================================
19   *
20   * This software consists of voluntary contributions made by many
21   * individuals on behalf of the Apache Software Foundation.  For more
22   * information on the Apache Software Foundation, please see
23   * <http://www.apache.org/>.
24   *
25   */
26  
27  package org.apache.http.impl.cookie;
28  
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Map;
34  
35  import org.apache.http.annotation.NotThreadSafe;
36  
37  import org.apache.http.Header;
38  import org.apache.http.HeaderElement;
39  import org.apache.http.NameValuePair;
40  import org.apache.http.cookie.ClientCookie;
41  import org.apache.http.cookie.Cookie;
42  import org.apache.http.cookie.CookieAttributeHandler;
43  import org.apache.http.cookie.CookieOrigin;
44  import org.apache.http.cookie.CookieSpec;
45  import org.apache.http.cookie.MalformedCookieException;
46  import org.apache.http.cookie.SM;
47  import org.apache.http.message.BufferedHeader;
48  import org.apache.http.util.CharArrayBuffer;
49  
50  /**
51   * RFC 2965 compliant {@link CookieSpec} implementation.
52   *
53   * @since 4.0
54   */
55  @NotThreadSafe // superclass is @NotThreadSafe
56  public class RFC2965Spec extends RFC2109Spec {
57  
58      /**
59       * Default constructor
60       *
61       */
62      public RFC2965Spec() {
63          this(null, false);
64      }
65  
66      public RFC2965Spec(final String[] datepatterns, boolean oneHeader) {
67          super(datepatterns, oneHeader);
68          registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2965DomainAttributeHandler());
69          registerAttribHandler(ClientCookie.PORT_ATTR, new RFC2965PortAttributeHandler());
70          registerAttribHandler(ClientCookie.COMMENTURL_ATTR, new RFC2965CommentUrlAttributeHandler());
71          registerAttribHandler(ClientCookie.DISCARD_ATTR, new RFC2965DiscardAttributeHandler());
72          registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2965VersionAttributeHandler());
73      }
74  
75      @Override
76      public List<Cookie> parse(
77              final Header header,
78              CookieOrigin origin) throws MalformedCookieException {
79          if (header == null) {
80              throw new IllegalArgumentException("Header may not be null");
81          }
82          if (origin == null) {
83              throw new IllegalArgumentException("Cookie origin may not be null");
84          }
85          if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) {
86              throw new MalformedCookieException("Unrecognized cookie header '"
87                      + header.toString() + "'");
88          }
89          origin = adjustEffectiveHost(origin);
90          HeaderElement[] elems = header.getElements();
91          return createCookies(elems, origin);
92      }
93  
94      @Override
95      protected List<Cookie> parse(
96              final HeaderElement[] elems,
97              CookieOrigin origin) throws MalformedCookieException {
98          origin = adjustEffectiveHost(origin);
99          return createCookies(elems, origin);
100     }
101 
102     private List<Cookie> createCookies(
103             final HeaderElement[] elems,
104             final CookieOrigin origin) throws MalformedCookieException {
105         List<Cookie> cookies = new ArrayList<Cookie>(elems.length);
106         for (HeaderElement headerelement : elems) {
107             String name = headerelement.getName();
108             String value = headerelement.getValue();
109             if (name == null || name.length() == 0) {
110                 throw new MalformedCookieException("Cookie name may not be empty");
111             }
112 
113             BasicClientCookie2 cookie = new BasicClientCookie2(name, value);
114             cookie.setPath(getDefaultPath(origin));
115             cookie.setDomain(getDefaultDomain(origin));
116             cookie.setPorts(new int [] { origin.getPort() });
117             // cycle through the parameters
118             NameValuePair[] attribs = headerelement.getParameters();
119 
120             // Eliminate duplicate attributes. The first occurrence takes precedence
121             // See RFC2965: 3.2  Origin Server Role
122             Map<String, NameValuePair> attribmap =
123                     new HashMap<String, NameValuePair>(attribs.length);
124             for (int j = attribs.length - 1; j >= 0; j--) {
125                 NameValuePair param = attribs[j];
126                 attribmap.put(param.getName().toLowerCase(Locale.ENGLISH), param);
127             }
128             for (Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) {
129                 NameValuePair attrib = entry.getValue();
130                 String s = attrib.getName().toLowerCase(Locale.ENGLISH);
131 
132                 cookie.setAttribute(s, attrib.getValue());
133 
134                 CookieAttributeHandler handler = findAttribHandler(s);
135                 if (handler != null) {
136                     handler.parse(cookie, attrib.getValue());
137                 }
138             }
139             cookies.add(cookie);
140         }
141         return cookies;
142     }
143 
144     @Override
145     public void validate(final Cookie cookie, CookieOrigin origin)
146             throws MalformedCookieException {
147         if (cookie == null) {
148             throw new IllegalArgumentException("Cookie may not be null");
149         }
150         if (origin == null) {
151             throw new IllegalArgumentException("Cookie origin may not be null");
152         }
153         origin = adjustEffectiveHost(origin);
154         super.validate(cookie, origin);
155     }
156 
157     @Override
158     public boolean match(final Cookie cookie, CookieOrigin origin) {
159         if (cookie == null) {
160             throw new IllegalArgumentException("Cookie may not be null");
161         }
162         if (origin == null) {
163             throw new IllegalArgumentException("Cookie origin may not be null");
164         }
165         origin = adjustEffectiveHost(origin);
166         return super.match(cookie, origin);
167     }
168 
169     /**
170      * Adds valid Port attribute value, e.g. "8000,8001,8002"
171      */
172     @Override
173     protected void formatCookieAsVer(final CharArrayBuffer buffer,
174             final Cookie cookie, int version) {
175         super.formatCookieAsVer(buffer, cookie, version);
176         // format port attribute
177         if (cookie instanceof ClientCookie) {
178             // Test if the port attribute as set by the origin server is not blank
179             String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR);
180             if (s != null) {
181                 buffer.append("; $Port");
182                 buffer.append("=\"");
183                 if (s.trim().length() > 0) {
184                     int[] ports = cookie.getPorts();
185                     if (ports != null) {
186                         for (int i = 0, len = ports.length; i < len; i++) {
187                             if (i > 0) {
188                                 buffer.append(",");
189                             }
190                             buffer.append(Integer.toString(ports[i]));
191                         }
192                     }
193                 }
194                 buffer.append("\"");
195             }
196         }
197     }
198 
199     /**
200      * Set 'effective host name' as defined in RFC 2965.
201      * <p>
202      * If a host name contains no dots, the effective host name is
203      * that name with the string .local appended to it.  Otherwise
204      * the effective host name is the same as the host name.  Note
205      * that all effective host names contain at least one dot.
206      *
207      * @param origin origin where cookie is received from or being sent to.
208      */
209     private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) {
210         String host = origin.getHost();
211 
212         // Test if the host name appears to be a fully qualified DNS name,
213         // IPv4 address or IPv6 address
214         boolean isLocalHost = true;
215         for (int i = 0; i < host.length(); i++) {
216             char ch = host.charAt(i);
217             if (ch == '.' || ch == ':') {
218                 isLocalHost = false;
219                 break;
220             }
221         }
222         if (isLocalHost) {
223             host += ".local";
224             return new CookieOrigin(
225                     host,
226                     origin.getPort(),
227                     origin.getPath(),
228                     origin.isSecure());
229         } else {
230             return origin;
231         }
232     }
233 
234     @Override
235     public int getVersion() {
236         return 1;
237     }
238 
239     @Override
240     public Header getVersionHeader() {
241         CharArrayBuffer buffer = new CharArrayBuffer(40);
242         buffer.append(SM.COOKIE2);
243         buffer.append(": ");
244         buffer.append("$Version=");
245         buffer.append(Integer.toString(getVersion()));
246         return new BufferedHeader(buffer);
247     }
248 
249     @Override
250     public String toString() {
251         return "rfc2965";
252     }
253 
254 }
255