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