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  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.Header;
36  import org.apache.http.HeaderElement;
37  import org.apache.http.NameValuePair;
38  import org.apache.http.annotation.ThreadSafe;
39  import org.apache.http.cookie.ClientCookie;
40  import org.apache.http.cookie.CommonCookieAttributeHandler;
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.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 2965 compliant {@link org.apache.http.cookie.CookieSpec} implementation.
52   *
53   * @since 4.0
54   */
55  @ThreadSafe
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, final boolean oneHeader) {
67          super(oneHeader,
68                  new RFC2965VersionAttributeHandler(),
69                  new BasicPathHandler(),
70                  new RFC2965DomainAttributeHandler(),
71                  new RFC2965PortAttributeHandler(),
72                  new BasicMaxAgeHandler(),
73                  new BasicSecureHandler(),
74                  new BasicCommentHandler(),
75                  new BasicExpiresHandler(
76                          datepatterns != null ? datepatterns.clone() : DATE_PATTERNS),
77                  new RFC2965CommentUrlAttributeHandler(),
78                  new RFC2965DiscardAttributeHandler());
79      }
80  
81      RFC2965Spec(final boolean oneHeader,
82                  final CommonCookieAttributeHandler... handlers) {
83          super(oneHeader, handlers);
84      }
85  
86      @Override
87      public List<Cookie> parse(
88              final Header header,
89              final CookieOrigin origin) throws MalformedCookieException {
90          Args.notNull(header, "Header");
91          Args.notNull(origin, "Cookie origin");
92          if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) {
93              throw new MalformedCookieException("Unrecognized cookie header '"
94                      + header.toString() + "'");
95          }
96          final HeaderElement[] elems = header.getElements();
97          return createCookies(elems, adjustEffectiveHost(origin));
98      }
99  
100     @Override
101     protected List<Cookie> parse(
102             final HeaderElement[] elems,
103             final CookieOrigin origin) throws MalformedCookieException {
104         return createCookies(elems, adjustEffectiveHost(origin));
105     }
106 
107     private List<Cookie> createCookies(
108             final HeaderElement[] elems,
109             final CookieOrigin origin) throws MalformedCookieException {
110         final List<Cookie> cookies = new ArrayList<Cookie>(elems.length);
111         for (final HeaderElement headerelement : elems) {
112             final String name = headerelement.getName();
113             final String value = headerelement.getValue();
114             if (name == null || name.isEmpty()) {
115                 throw new MalformedCookieException("Cookie name may not be empty");
116             }
117 
118             final BasicClientCookie2 cookie = new BasicClientCookie2(name, value);
119             cookie.setPath(getDefaultPath(origin));
120             cookie.setDomain(getDefaultDomain(origin));
121             cookie.setPorts(new int [] { origin.getPort() });
122             // cycle through the parameters
123             final NameValuePair[] attribs = headerelement.getParameters();
124 
125             // Eliminate duplicate attributes. The first occurrence takes precedence
126             // See RFC2965: 3.2  Origin Server Role
127             final Map<String, NameValuePair> attribmap =
128                     new HashMap<String, NameValuePair>(attribs.length);
129             for (int j = attribs.length - 1; j >= 0; j--) {
130                 final NameValuePair param = attribs[j];
131                 attribmap.put(param.getName().toLowerCase(Locale.ROOT), param);
132             }
133             for (final Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) {
134                 final NameValuePair attrib = entry.getValue();
135                 final String s = attrib.getName().toLowerCase(Locale.ROOT);
136 
137                 cookie.setAttribute(s, attrib.getValue());
138 
139                 final CookieAttributeHandler handler = findAttribHandler(s);
140                 if (handler != null) {
141                     handler.parse(cookie, attrib.getValue());
142                 }
143             }
144             cookies.add(cookie);
145         }
146         return cookies;
147     }
148 
149     @Override
150     public void validate(
151             final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
152         Args.notNull(cookie, "Cookie");
153         Args.notNull(origin, "Cookie origin");
154         super.validate(cookie, adjustEffectiveHost(origin));
155     }
156 
157     @Override
158     public boolean match(final Cookie cookie, final CookieOrigin origin) {
159         Args.notNull(cookie, "Cookie");
160         Args.notNull(origin, "Cookie origin");
161         return super.match(cookie, adjustEffectiveHost(origin));
162     }
163 
164     /**
165      * Adds valid Port attribute value, e.g. "8000,8001,8002"
166      */
167     @Override
168     protected void formatCookieAsVer(final CharArrayBuffer buffer,
169             final Cookie cookie, final int version) {
170         super.formatCookieAsVer(buffer, cookie, version);
171         // format port attribute
172         if (cookie instanceof ClientCookie) {
173             // Test if the port attribute as set by the origin server is not blank
174             final String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR);
175             if (s != null) {
176                 buffer.append("; $Port");
177                 buffer.append("=\"");
178                 if (!s.trim().isEmpty()) {
179                     final int[] ports = cookie.getPorts();
180                     if (ports != null) {
181                         final int len = ports.length;
182                         for (int i = 0; i < len; i++) {
183                             if (i > 0) {
184                                 buffer.append(",");
185                             }
186                             buffer.append(Integer.toString(ports[i]));
187                         }
188                     }
189                 }
190                 buffer.append("\"");
191             }
192         }
193     }
194 
195     /**
196      * Set 'effective host name' as defined in RFC 2965.
197      * <p>
198      * If a host name contains no dots, the effective host name is
199      * that name with the string .local appended to it.  Otherwise
200      * the effective host name is the same as the host name.  Note
201      * that all effective host names contain at least one dot.
202      *
203      * @param origin origin where cookie is received from or being sent to.
204      */
205     private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) {
206         String host = origin.getHost();
207 
208         // Test if the host name appears to be a fully qualified DNS name,
209         // IPv4 address or IPv6 address
210         boolean isLocalHost = true;
211         for (int i = 0; i < host.length(); i++) {
212             final char ch = host.charAt(i);
213             if (ch == '.' || ch == ':') {
214                 isLocalHost = false;
215                 break;
216             }
217         }
218         if (isLocalHost) {
219             host += ".local";
220             return new CookieOrigin(
221                     host,
222                     origin.getPort(),
223                     origin.getPath(),
224                     origin.isSecure());
225         } else {
226             return origin;
227         }
228     }
229 
230     @Override
231     public int getVersion() {
232         return 1;
233     }
234 
235     @Override
236     public Header getVersionHeader() {
237         final CharArrayBuffer buffer = new CharArrayBuffer(40);
238         buffer.append(SM.COOKIE2);
239         buffer.append(": ");
240         buffer.append("$Version=");
241         buffer.append(Integer.toString(getVersion()));
242         return new BufferedHeader(buffer);
243     }
244 
245     @Override
246     public String toString() {
247         return "rfc2965";
248     }
249 
250 }
251