12
12
*/
13
13
package com .ning .http .client .cookie ;
14
14
15
- import com .ning .http .util .StringUtils ;
15
+ import org .slf4j .Logger ;
16
+ import org .slf4j .LoggerFactory ;
17
+
18
+ import java .nio .CharBuffer ;
19
+
20
+ import static com .ning .http .client .cookie .CookieUtil .*;
16
21
17
22
public class CookieDecoder {
18
23
24
+ private static final Logger LOGGER = LoggerFactory .getLogger (CookieDecoder .class );
25
+
19
26
/**
20
27
* Decodes the specified HTTP header value into {@link Cookie}.
21
28
*
22
29
* @return the decoded {@link Cookie}
23
30
*/
24
31
public static Cookie decode (String header ) {
25
32
26
- if (header .length () == 0 )
33
+ if (header == null ) {
34
+ throw new NullPointerException ("header" );
35
+ }
36
+
37
+ final int headerLen = header .length ();
38
+
39
+ if (headerLen == 0 ) {
27
40
return null ;
41
+ }
28
42
29
- KeyValuePairsParser pairsParser = new KeyValuePairsParser () ;
43
+ CookieBuilder cookieBuilder = null ;
30
44
31
- final int headerLen = header .length ();
32
45
loop : for (int i = 0 ;;) {
33
46
34
47
// Skip spaces and separators.
@@ -49,111 +62,193 @@ public static Cookie decode(String header) {
49
62
break ;
50
63
}
51
64
52
- int newNameStart = i ;
53
- int newNameEnd = i ;
54
- String value ;
55
- String rawValue ;
65
+ int nameBegin = i ;
66
+ int nameEnd = i ;
67
+ int valueBegin = - 1 ;
68
+ int valueEnd = - 1 ;
56
69
57
- if (i == headerLen ) {
58
- value = rawValue = null ;
59
- } else {
70
+ if (i != headerLen ) {
60
71
keyValLoop : for (;;) {
61
72
62
73
char curChar = header .charAt (i );
63
74
if (curChar == ';' ) {
64
75
// NAME; (no value till ';')
65
- newNameEnd = i ;
66
- value = rawValue = null ;
76
+ nameEnd = i ;
77
+ valueBegin = valueEnd = - 1 ;
67
78
break keyValLoop ;
79
+
68
80
} else if (curChar == '=' ) {
69
81
// NAME=VALUE
70
- newNameEnd = i ;
82
+ nameEnd = i ;
71
83
i ++;
72
84
if (i == headerLen ) {
73
85
// NAME= (empty value, i.e. nothing after '=')
74
- value = rawValue = "" ;
86
+ valueBegin = valueEnd = 0 ;
75
87
break keyValLoop ;
76
88
}
77
89
78
- int newValueStart = i ;
79
- char c = header .charAt (i );
80
- if (c == '"' || c == '\'' ) {
81
- // NAME="VALUE" or NAME='VALUE'
82
- StringBuilder newValueBuf = StringUtils .stringBuilder ();
83
-
84
- int rawValueStart = i ;
85
- int rawValueEnd = i ;
86
-
87
- final char q = c ;
88
- boolean hadBackslash = false ;
89
- i ++;
90
- for (;;) {
91
- if (i == headerLen ) {
92
- value = newValueBuf .toString ();
93
- // only need to compute raw value for cookie
94
- // value which is at most in 2nd position
95
- rawValue = header .substring (rawValueStart , rawValueEnd );
96
- break keyValLoop ;
97
- }
98
- if (hadBackslash ) {
99
- hadBackslash = false ;
100
- c = header .charAt (i ++);
101
- rawValueEnd = i ;
102
- switch (c ) {
103
- case '\\' :
104
- case '"' :
105
- case '\'' :
106
- // Escape last backslash.
107
- newValueBuf .setCharAt (newValueBuf .length () - 1 , c );
108
- break ;
109
- default :
110
- // Do not escape last backslash.
111
- newValueBuf .append (c );
112
- }
113
- } else {
114
- c = header .charAt (i ++);
115
- rawValueEnd = i ;
116
- if (c == q ) {
117
- value = newValueBuf .toString ();
118
- // only need to compute raw value for
119
- // cookie value which is at most in 2nd
120
- // position
121
- rawValue = header .substring (rawValueStart , rawValueEnd );
122
- break keyValLoop ;
123
- }
124
- newValueBuf .append (c );
125
- if (c == '\\' ) {
126
- hadBackslash = true ;
127
- }
128
- }
129
- }
130
- } else {
131
- // NAME=VALUE;
132
- int semiPos = header .indexOf (';' , i );
133
- if (semiPos > 0 ) {
134
- value = rawValue = header .substring (newValueStart , semiPos );
135
- i = semiPos ;
136
- } else {
137
- value = rawValue = header .substring (newValueStart );
138
- i = headerLen ;
139
- }
140
- }
90
+ valueBegin = i ;
91
+ // NAME=VALUE;
92
+ int semiPos = header .indexOf (';' , i );
93
+ valueEnd = i = semiPos > 0 ? semiPos : headerLen ;
141
94
break keyValLoop ;
142
95
} else {
143
96
i ++;
144
97
}
145
98
146
99
if (i == headerLen ) {
147
100
// NAME (no value till the end of string)
148
- newNameEnd = headerLen ;
149
- value = rawValue = null ;
101
+ nameEnd = headerLen ;
102
+ valueBegin = valueEnd = - 1 ;
150
103
break ;
151
104
}
152
105
}
153
106
}
154
107
155
- pairsParser .parseKeyValuePair (header , newNameStart , newNameEnd , value , rawValue );
108
+ if (valueEnd > 0 && header .charAt (valueEnd - 1 ) == ',' ) {
109
+ // old multiple cookies separator, skipping it
110
+ valueEnd --;
111
+ }
112
+
113
+ if (cookieBuilder == null ) {
114
+ // cookie name-value pair
115
+ if (nameBegin == -1 || nameBegin == nameEnd ) {
116
+ LOGGER .debug ("Skipping cookie with null name" );
117
+ return null ;
118
+ }
119
+
120
+ if (valueBegin == -1 ) {
121
+ LOGGER .debug ("Skipping cookie with null value" );
122
+ return null ;
123
+ }
124
+
125
+ CharSequence wrappedValue = CharBuffer .wrap (header , valueBegin , valueEnd );
126
+ CharSequence unwrappedValue = unwrapValue (wrappedValue );
127
+ if (unwrappedValue == null ) {
128
+ LOGGER .debug ("Skipping cookie because starting quotes are not properly balanced in '{}'" , unwrappedValue );
129
+ return null ;
130
+ }
131
+
132
+ final String name = header .substring (nameBegin , nameEnd );
133
+
134
+ final boolean wrap = unwrappedValue .length () != valueEnd - valueBegin ;
135
+
136
+ cookieBuilder = new CookieBuilder (name , unwrappedValue .toString (), wrap );
137
+
138
+ } else {
139
+ // cookie attribute
140
+ String attrValue = valueBegin == -1 ? null : header .substring (valueBegin , valueEnd );
141
+ cookieBuilder .appendAttribute (header , nameBegin , nameEnd , attrValue );
142
+ }
143
+ }
144
+ return cookieBuilder .cookie ();
145
+ }
146
+
147
+ private static class CookieBuilder {
148
+
149
+ private static final String PATH = "Path" ;
150
+
151
+ private static final String EXPIRES = "Expires" ;
152
+
153
+ private static final String MAX_AGE = "Max-Age" ;
154
+
155
+ private static final String DOMAIN = "Domain" ;
156
+
157
+ private static final String SECURE = "Secure" ;
158
+
159
+ private static final String HTTPONLY = "HTTPOnly" ;
160
+
161
+ private final String name ;
162
+ private final String value ;
163
+ private final boolean wrap ;
164
+ private String domain ;
165
+ private String path ;
166
+ private int maxAge = Integer .MIN_VALUE ;
167
+ private String expires ;
168
+ private boolean secure ;
169
+ private boolean httpOnly ;
170
+
171
+ public CookieBuilder (String name , String value , boolean wrap ) {
172
+ this .name = name ;
173
+ this .value = value ;
174
+ this .wrap = wrap ;
175
+ }
176
+
177
+ public Cookie cookie () {
178
+ return new Cookie (name , value , wrap , domain , path , computeExpires (expires ), maxAge , secure , httpOnly );
179
+ }
180
+
181
+ /**
182
+ * Parse and store a key-value pair. First one is considered to be the
183
+ * cookie name/value. Unknown attribute names are silently discarded.
184
+ *
185
+ * @param header
186
+ * the HTTP header
187
+ * @param keyStart
188
+ * where the key starts in the header
189
+ * @param keyEnd
190
+ * where the key ends in the header
191
+ * @param value
192
+ * the decoded value
193
+ */
194
+ public void appendAttribute (String header , int keyStart , int keyEnd , String value ) {
195
+ setCookieAttribute (header , keyStart , keyEnd , value );
196
+ }
197
+
198
+ private void setCookieAttribute (String header , int keyStart , int keyEnd , String value ) {
199
+
200
+ int length = keyEnd - keyStart ;
201
+
202
+ if (length == 4 ) {
203
+ parse4 (header , keyStart , value );
204
+ } else if (length == 6 ) {
205
+ parse6 (header , keyStart , value );
206
+ } else if (length == 7 ) {
207
+ parse7 (header , keyStart , value );
208
+ } else if (length == 8 ) {
209
+ parse8 (header , keyStart , value );
210
+ }
211
+ }
212
+
213
+ private void parse4 (String header , int nameStart , String value ) {
214
+ if (header .regionMatches (true , nameStart , PATH , 0 , 4 )) {
215
+ path = value ;
216
+ }
217
+ }
218
+
219
+ private void parse6 (String header , int nameStart , String value ) {
220
+ if (header .regionMatches (true , nameStart , DOMAIN , 0 , 5 )) {
221
+ domain = value .length () > 0 ? value .toString () : null ;
222
+ } else if (header .regionMatches (true , nameStart , SECURE , 0 , 5 )) {
223
+ secure = true ;
224
+ }
225
+ }
226
+
227
+ private void setExpire (String value ) {
228
+ expires = value ;
229
+ }
230
+
231
+ private void setMaxAge (String value ) {
232
+ try {
233
+ maxAge = Math .max (Integer .valueOf (value ), 0 );
234
+ } catch (NumberFormatException e1 ) {
235
+ // ignore failure to parse -> treat as session cookie
236
+ }
237
+ }
238
+
239
+ private void parse7 (String header , int nameStart , String value ) {
240
+ if (header .regionMatches (true , nameStart , EXPIRES , 0 , 7 )) {
241
+ setExpire (value );
242
+ } else if (header .regionMatches (true , nameStart , MAX_AGE , 0 , 7 )) {
243
+ setMaxAge (value );
244
+ }
245
+ }
246
+
247
+ private void parse8 (String header , int nameStart , String value ) {
248
+
249
+ if (header .regionMatches (true , nameStart , HTTPONLY , 0 , 8 )) {
250
+ httpOnly = true ;
251
+ }
156
252
}
157
- return pairsParser .cookie ();
158
253
}
159
254
}
0 commit comments