|
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 ch.boye.httpclientandroidlib.impl.cookie; |
|
29 |
|
30 import java.util.ArrayList; |
|
31 import java.util.Collections; |
|
32 import java.util.List; |
|
33 |
|
34 import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; |
|
35 |
|
36 import ch.boye.httpclientandroidlib.Header; |
|
37 import ch.boye.httpclientandroidlib.HeaderElement; |
|
38 import ch.boye.httpclientandroidlib.cookie.ClientCookie; |
|
39 import ch.boye.httpclientandroidlib.cookie.Cookie; |
|
40 import ch.boye.httpclientandroidlib.cookie.CookieOrigin; |
|
41 import ch.boye.httpclientandroidlib.cookie.CookiePathComparator; |
|
42 import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; |
|
43 import ch.boye.httpclientandroidlib.cookie.CookieSpec; |
|
44 import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; |
|
45 import ch.boye.httpclientandroidlib.cookie.SM; |
|
46 import ch.boye.httpclientandroidlib.message.BufferedHeader; |
|
47 import ch.boye.httpclientandroidlib.util.CharArrayBuffer; |
|
48 |
|
49 /** |
|
50 * RFC 2109 compliant {@link CookieSpec} implementation. This is an older |
|
51 * version of the official HTTP state management specification superseded |
|
52 * by RFC 2965. |
|
53 * |
|
54 * @see RFC2965Spec |
|
55 * |
|
56 * @since 4.0 |
|
57 */ |
|
58 @NotThreadSafe // superclass is @NotThreadSafe |
|
59 public class RFC2109Spec extends CookieSpecBase { |
|
60 |
|
61 private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator(); |
|
62 |
|
63 private final static String[] DATE_PATTERNS = { |
|
64 DateUtils.PATTERN_RFC1123, |
|
65 DateUtils.PATTERN_RFC1036, |
|
66 DateUtils.PATTERN_ASCTIME |
|
67 }; |
|
68 |
|
69 private final String[] datepatterns; |
|
70 private final boolean oneHeader; |
|
71 |
|
72 /** Default constructor */ |
|
73 public RFC2109Spec(final String[] datepatterns, boolean oneHeader) { |
|
74 super(); |
|
75 if (datepatterns != null) { |
|
76 this.datepatterns = datepatterns.clone(); |
|
77 } else { |
|
78 this.datepatterns = DATE_PATTERNS; |
|
79 } |
|
80 this.oneHeader = oneHeader; |
|
81 registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2109VersionHandler()); |
|
82 registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); |
|
83 registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2109DomainHandler()); |
|
84 registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); |
|
85 registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); |
|
86 registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); |
|
87 registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( |
|
88 this.datepatterns)); |
|
89 } |
|
90 |
|
91 /** Default constructor */ |
|
92 public RFC2109Spec() { |
|
93 this(null, false); |
|
94 } |
|
95 |
|
96 public List<Cookie> parse(final Header header, final CookieOrigin origin) |
|
97 throws MalformedCookieException { |
|
98 if (header == null) { |
|
99 throw new IllegalArgumentException("Header may not be null"); |
|
100 } |
|
101 if (origin == null) { |
|
102 throw new IllegalArgumentException("Cookie origin may not be null"); |
|
103 } |
|
104 if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) { |
|
105 throw new MalformedCookieException("Unrecognized cookie header '" |
|
106 + header.toString() + "'"); |
|
107 } |
|
108 HeaderElement[] elems = header.getElements(); |
|
109 return parse(elems, origin); |
|
110 } |
|
111 |
|
112 @Override |
|
113 public void validate(final Cookie cookie, final CookieOrigin origin) |
|
114 throws MalformedCookieException { |
|
115 if (cookie == null) { |
|
116 throw new IllegalArgumentException("Cookie may not be null"); |
|
117 } |
|
118 String name = cookie.getName(); |
|
119 if (name.indexOf(' ') != -1) { |
|
120 throw new CookieRestrictionViolationException("Cookie name may not contain blanks"); |
|
121 } |
|
122 if (name.startsWith("$")) { |
|
123 throw new CookieRestrictionViolationException("Cookie name may not start with $"); |
|
124 } |
|
125 super.validate(cookie, origin); |
|
126 } |
|
127 |
|
128 public List<Header> formatCookies(List<Cookie> cookies) { |
|
129 if (cookies == null) { |
|
130 throw new IllegalArgumentException("List of cookies may not be null"); |
|
131 } |
|
132 if (cookies.isEmpty()) { |
|
133 throw new IllegalArgumentException("List of cookies may not be empty"); |
|
134 } |
|
135 if (cookies.size() > 1) { |
|
136 // Create a mutable copy and sort the copy. |
|
137 cookies = new ArrayList<Cookie>(cookies); |
|
138 Collections.sort(cookies, PATH_COMPARATOR); |
|
139 } |
|
140 if (this.oneHeader) { |
|
141 return doFormatOneHeader(cookies); |
|
142 } else { |
|
143 return doFormatManyHeaders(cookies); |
|
144 } |
|
145 } |
|
146 |
|
147 private List<Header> doFormatOneHeader(final List<Cookie> cookies) { |
|
148 int version = Integer.MAX_VALUE; |
|
149 // Pick the lowest common denominator |
|
150 for (Cookie cookie : cookies) { |
|
151 if (cookie.getVersion() < version) { |
|
152 version = cookie.getVersion(); |
|
153 } |
|
154 } |
|
155 CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size()); |
|
156 buffer.append(SM.COOKIE); |
|
157 buffer.append(": "); |
|
158 buffer.append("$Version="); |
|
159 buffer.append(Integer.toString(version)); |
|
160 for (Cookie cooky : cookies) { |
|
161 buffer.append("; "); |
|
162 Cookie cookie = cooky; |
|
163 formatCookieAsVer(buffer, cookie, version); |
|
164 } |
|
165 List<Header> headers = new ArrayList<Header>(1); |
|
166 headers.add(new BufferedHeader(buffer)); |
|
167 return headers; |
|
168 } |
|
169 |
|
170 private List<Header> doFormatManyHeaders(final List<Cookie> cookies) { |
|
171 List<Header> headers = new ArrayList<Header>(cookies.size()); |
|
172 for (Cookie cookie : cookies) { |
|
173 int version = cookie.getVersion(); |
|
174 CharArrayBuffer buffer = new CharArrayBuffer(40); |
|
175 buffer.append("Cookie: "); |
|
176 buffer.append("$Version="); |
|
177 buffer.append(Integer.toString(version)); |
|
178 buffer.append("; "); |
|
179 formatCookieAsVer(buffer, cookie, version); |
|
180 headers.add(new BufferedHeader(buffer)); |
|
181 } |
|
182 return headers; |
|
183 } |
|
184 |
|
185 /** |
|
186 * Return a name/value string suitable for sending in a <tt>"Cookie"</tt> |
|
187 * header as defined in RFC 2109 for backward compatibility with cookie |
|
188 * version 0 |
|
189 * @param buffer The char array buffer to use for output |
|
190 * @param name The cookie name |
|
191 * @param value The cookie value |
|
192 * @param version The cookie version |
|
193 */ |
|
194 protected void formatParamAsVer(final CharArrayBuffer buffer, |
|
195 final String name, final String value, int version) { |
|
196 buffer.append(name); |
|
197 buffer.append("="); |
|
198 if (value != null) { |
|
199 if (version > 0) { |
|
200 buffer.append('\"'); |
|
201 buffer.append(value); |
|
202 buffer.append('\"'); |
|
203 } else { |
|
204 buffer.append(value); |
|
205 } |
|
206 } |
|
207 } |
|
208 |
|
209 /** |
|
210 * Return a string suitable for sending in a <tt>"Cookie"</tt> header |
|
211 * as defined in RFC 2109 for backward compatibility with cookie version 0 |
|
212 * @param buffer The char array buffer to use for output |
|
213 * @param cookie The {@link Cookie} to be formatted as string |
|
214 * @param version The version to use. |
|
215 */ |
|
216 protected void formatCookieAsVer(final CharArrayBuffer buffer, |
|
217 final Cookie cookie, int version) { |
|
218 formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version); |
|
219 if (cookie.getPath() != null) { |
|
220 if (cookie instanceof ClientCookie |
|
221 && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) { |
|
222 buffer.append("; "); |
|
223 formatParamAsVer(buffer, "$Path", cookie.getPath(), version); |
|
224 } |
|
225 } |
|
226 if (cookie.getDomain() != null) { |
|
227 if (cookie instanceof ClientCookie |
|
228 && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { |
|
229 buffer.append("; "); |
|
230 formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version); |
|
231 } |
|
232 } |
|
233 } |
|
234 |
|
235 public int getVersion() { |
|
236 return 1; |
|
237 } |
|
238 |
|
239 public Header getVersionHeader() { |
|
240 return null; |
|
241 } |
|
242 |
|
243 @Override |
|
244 public String toString() { |
|
245 return "rfc2109"; |
|
246 } |
|
247 |
|
248 } |