|
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 ch.boye.httpclientandroidlib.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 ch.boye.httpclientandroidlib.annotation.NotThreadSafe; |
|
36 |
|
37 import ch.boye.httpclientandroidlib.Header; |
|
38 import ch.boye.httpclientandroidlib.HeaderElement; |
|
39 import ch.boye.httpclientandroidlib.NameValuePair; |
|
40 import ch.boye.httpclientandroidlib.cookie.ClientCookie; |
|
41 import ch.boye.httpclientandroidlib.cookie.Cookie; |
|
42 import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; |
|
43 import ch.boye.httpclientandroidlib.cookie.CookieOrigin; |
|
44 import ch.boye.httpclientandroidlib.cookie.CookieSpec; |
|
45 import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; |
|
46 import ch.boye.httpclientandroidlib.cookie.SM; |
|
47 import ch.boye.httpclientandroidlib.message.BufferedHeader; |
|
48 import ch.boye.httpclientandroidlib.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 * @return |
|
209 */ |
|
210 private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) { |
|
211 String host = origin.getHost(); |
|
212 |
|
213 // Test if the host name appears to be a fully qualified DNS name, |
|
214 // IPv4 address or IPv6 address |
|
215 boolean isLocalHost = true; |
|
216 for (int i = 0; i < host.length(); i++) { |
|
217 char ch = host.charAt(i); |
|
218 if (ch == '.' || ch == ':') { |
|
219 isLocalHost = false; |
|
220 break; |
|
221 } |
|
222 } |
|
223 if (isLocalHost) { |
|
224 host += ".local"; |
|
225 return new CookieOrigin( |
|
226 host, |
|
227 origin.getPort(), |
|
228 origin.getPath(), |
|
229 origin.isSecure()); |
|
230 } else { |
|
231 return origin; |
|
232 } |
|
233 } |
|
234 |
|
235 @Override |
|
236 public int getVersion() { |
|
237 return 1; |
|
238 } |
|
239 |
|
240 @Override |
|
241 public Header getVersionHeader() { |
|
242 CharArrayBuffer buffer = new CharArrayBuffer(40); |
|
243 buffer.append(SM.COOKIE2); |
|
244 buffer.append(": "); |
|
245 buffer.append("$Version="); |
|
246 buffer.append(Integer.toString(getVersion())); |
|
247 return new BufferedHeader(buffer); |
|
248 } |
|
249 |
|
250 @Override |
|
251 public String toString() { |
|
252 return "rfc2965"; |
|
253 } |
|
254 |
|
255 } |
|
256 |