1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.http.impl.cookie;
29
30 import java.util.ArrayList;
31 import java.util.BitSet;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.LinkedHashMap;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.concurrent.ConcurrentHashMap;
39
40 import org.apache.http.FormattedHeader;
41 import org.apache.http.Header;
42 import org.apache.http.annotation.Contract;
43 import org.apache.http.annotation.ThreadingBehavior;
44 import org.apache.http.cookie.ClientCookie;
45 import org.apache.http.cookie.CommonCookieAttributeHandler;
46 import org.apache.http.cookie.Cookie;
47 import org.apache.http.cookie.CookieAttributeHandler;
48 import org.apache.http.cookie.CookieOrigin;
49 import org.apache.http.cookie.CookiePriorityComparator;
50 import org.apache.http.cookie.CookieSpec;
51 import org.apache.http.cookie.MalformedCookieException;
52 import org.apache.http.cookie.SM;
53 import org.apache.http.message.BufferedHeader;
54 import org.apache.http.message.ParserCursor;
55 import org.apache.http.message.TokenParser;
56 import org.apache.http.util.Args;
57 import org.apache.http.util.CharArrayBuffer;
58
59
60
61
62
63
64 @Contract(threading = ThreadingBehavior.SAFE)
65 public class RFC6265CookieSpec implements CookieSpec {
66
67 private final static char PARAM_DELIMITER = ';';
68 private final static char COMMA_CHAR = ',';
69 private final static char EQUAL_CHAR = '=';
70 private final static char DQUOTE_CHAR = '"';
71 private final static char ESCAPE_CHAR = '\\';
72
73
74
75 private static final BitSet TOKEN_DELIMS = TokenParser.INIT_BITSET(EQUAL_CHAR, PARAM_DELIMITER);
76 private static final BitSet VALUE_DELIMS = TokenParser.INIT_BITSET(PARAM_DELIMITER);
77 private static final BitSet SPECIAL_CHARS = TokenParser.INIT_BITSET(' ',
78 DQUOTE_CHAR, COMMA_CHAR, PARAM_DELIMITER, ESCAPE_CHAR);
79
80 private final CookieAttributeHandler[] attribHandlers;
81 private final Map<String, CookieAttributeHandler> attribHandlerMap;
82 private final TokenParser tokenParser;
83
84 protected RFC6265CookieSpec(final CommonCookieAttributeHandler... handlers) {
85 super();
86 this.attribHandlers = handlers.clone();
87 this.attribHandlerMap = new ConcurrentHashMap<String, CookieAttributeHandler>(handlers.length);
88 for (final CommonCookieAttributeHandler handler: handlers) {
89 this.attribHandlerMap.put(handler.getAttributeName().toLowerCase(Locale.ROOT), handler);
90 }
91 this.tokenParser = TokenParser.INSTANCE;
92 }
93
94 static String getDefaultPath(final CookieOrigin origin) {
95 String defaultPath = origin.getPath();
96 int lastSlashIndex = defaultPath.lastIndexOf('/');
97 if (lastSlashIndex >= 0) {
98 if (lastSlashIndex == 0) {
99
100 lastSlashIndex = 1;
101 }
102 defaultPath = defaultPath.substring(0, lastSlashIndex);
103 }
104 return defaultPath;
105 }
106
107 static String getDefaultDomain(final CookieOrigin origin) {
108 return origin.getHost();
109 }
110
111 @Override
112 public final List<Cookie> parse(final Header header, final CookieOrigin origin) throws MalformedCookieException {
113 Args.notNull(header, "Header");
114 Args.notNull(origin, "Cookie origin");
115 if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) {
116 throw new MalformedCookieException("Unrecognized cookie header: '" + header.toString() + "'");
117 }
118 final CharArrayBuffer buffer;
119 final ParserCursor cursor;
120 if (header instanceof FormattedHeader) {
121 buffer = ((FormattedHeader) header).getBuffer();
122 cursor = new ParserCursor(((FormattedHeader) header).getValuePos(), buffer.length());
123 } else {
124 final String s = header.getValue();
125 if (s == null) {
126 throw new MalformedCookieException("Header value is null");
127 }
128 buffer = new CharArrayBuffer(s.length());
129 buffer.append(s);
130 cursor = new ParserCursor(0, buffer.length());
131 }
132 final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
133 if (name.isEmpty()) {
134 return Collections.emptyList();
135 }
136 if (cursor.atEnd()) {
137 return Collections.emptyList();
138 }
139 final int valueDelim = buffer.charAt(cursor.getPos());
140 cursor.updatePos(cursor.getPos() + 1);
141 if (valueDelim != '=') {
142 throw new MalformedCookieException("Cookie value is invalid: '" + header.toString() + "'");
143 }
144 final String value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
145 if (!cursor.atEnd()) {
146 cursor.updatePos(cursor.getPos() + 1);
147 }
148 final BasicClientCookieentCookie.html#BasicClientCookie">BasicClientCookie cookie = new BasicClientCookie(name, value);
149 cookie.setPath(getDefaultPath(origin));
150 cookie.setDomain(getDefaultDomain(origin));
151 cookie.setCreationDate(new Date());
152
153 final Map<String, String> attribMap = new LinkedHashMap<String, String>();
154 while (!cursor.atEnd()) {
155 final String paramName = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS)
156 .toLowerCase(Locale.ROOT);
157 String paramValue = null;
158 if (!cursor.atEnd()) {
159 final int paramDelim = buffer.charAt(cursor.getPos());
160 cursor.updatePos(cursor.getPos() + 1);
161 if (paramDelim == EQUAL_CHAR) {
162 paramValue = tokenParser.parseToken(buffer, cursor, VALUE_DELIMS);
163 if (!cursor.atEnd()) {
164 cursor.updatePos(cursor.getPos() + 1);
165 }
166 }
167 }
168 cookie.setAttribute(paramName, paramValue);
169 attribMap.put(paramName, paramValue);
170 }
171
172 if (attribMap.containsKey(ClientCookie.MAX_AGE_ATTR)) {
173 attribMap.remove(ClientCookie.EXPIRES_ATTR);
174 }
175
176 for (final Map.Entry<String, String> entry: attribMap.entrySet()) {
177 final String paramName = entry.getKey();
178 final String paramValue = entry.getValue();
179 final CookieAttributeHandler handler = this.attribHandlerMap.get(paramName);
180 if (handler != null) {
181 handler.parse(cookie, paramValue);
182 }
183 }
184
185 return Collections.<Cookie>singletonList(cookie);
186 }
187
188 @Override
189 public final void validate(final Cookie cookie, final CookieOrigin origin)
190 throws MalformedCookieException {
191 Args.notNull(cookie, "Cookie");
192 Args.notNull(origin, "Cookie origin");
193 for (final CookieAttributeHandler handler: this.attribHandlers) {
194 handler.validate(cookie, origin);
195 }
196 }
197
198 @Override
199 public final boolean match(final Cookie cookie, final CookieOrigin origin) {
200 Args.notNull(cookie, "Cookie");
201 Args.notNull(origin, "Cookie origin");
202 for (final CookieAttributeHandler handler: this.attribHandlers) {
203 if (!handler.match(cookie, origin)) {
204 return false;
205 }
206 }
207 return true;
208 }
209
210 @Override
211 public List<Header> formatCookies(final List<Cookie> cookies) {
212 Args.notEmpty(cookies, "List of cookies");
213 final List<? extends Cookie> sortedCookies;
214 if (cookies.size() > 1) {
215
216 sortedCookies = new ArrayList<Cookie>(cookies);
217 Collections.sort(sortedCookies, CookiePriorityComparator.INSTANCE);
218 } else {
219 sortedCookies = cookies;
220 }
221 final CharArrayBuffer buffer = new CharArrayBuffer(20 * sortedCookies.size());
222 buffer.append(SM.COOKIE);
223 buffer.append(": ");
224 for (int n = 0; n < sortedCookies.size(); n++) {
225 final Cookie cookie = sortedCookies.get(n);
226 if (n > 0) {
227 buffer.append(PARAM_DELIMITER);
228 buffer.append(' ');
229 }
230 buffer.append(cookie.getName());
231 final String s = cookie.getValue();
232 if (s != null) {
233 buffer.append(EQUAL_CHAR);
234 if (containsSpecialChar(s)) {
235 buffer.append(DQUOTE_CHAR);
236 for (int i = 0; i < s.length(); i++) {
237 final char ch = s.charAt(i);
238 if (ch == DQUOTE_CHAR || ch == ESCAPE_CHAR) {
239 buffer.append(ESCAPE_CHAR);
240 }
241 buffer.append(ch);
242 }
243 buffer.append(DQUOTE_CHAR);
244 } else {
245 buffer.append(s);
246 }
247 }
248 }
249 final List<Header> headers = new ArrayList<Header>(1);
250 headers.add(new BufferedHeader(buffer));
251 return headers;
252 }
253
254 boolean containsSpecialChar(final CharSequence s) {
255 return containsChars(s, SPECIAL_CHARS);
256 }
257
258 boolean containsChars(final CharSequence s, final BitSet chars) {
259 for (int i = 0; i < s.length(); i++) {
260 final char ch = s.charAt(i);
261 if (chars.get(ch)) {
262 return true;
263 }
264 }
265 return false;
266 }
267
268 @Override
269 public final int getVersion() {
270 return 0;
271 }
272
273 @Override
274 public final Header getVersionHeader() {
275 return null;
276 }
277
278 }