Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit 330d91b

Browse files
author
Stephane Landelle
committed
Handle double quoted cookie values, master fix AsyncHttpClient#283
1 parent d4cd6f3 commit 330d91b

File tree

15 files changed

+711
-175
lines changed

15 files changed

+711
-175
lines changed

api/src/main/java/com/ning/http/client/Cookie.java

Lines changed: 163 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,83 @@
2020
import java.util.Set;
2121
import java.util.TreeSet;
2222

23-
public class Cookie {
23+
public class Cookie implements Comparable<Cookie>{
2424
private final String domain;
2525
private final String name;
2626
private final String value;
2727
private final String path;
2828
private final int maxAge;
2929
private final boolean secure;
3030
private final int version;
31+
private final boolean httpOnly;
32+
private final boolean discard;
33+
private final String comment;
34+
private final String commentUrl;
35+
3136
private Set<Integer> ports = Collections.emptySet();
3237
private Set<Integer> unmodifiablePorts = ports;
3338

34-
public Cookie(String domain, String name, String value, String path, int maxAge, boolean secure) {
35-
this.domain = domain;
36-
this.name = name;
37-
this.value = value;
38-
this.path = path;
39-
this.maxAge = maxAge;
40-
this.secure = secure;
41-
this.version = 1;
42-
}
39+
public Cookie(String domain, String name, String value, String path, int maxAge, boolean secure, int version, boolean httpOnly, boolean discard, String comment, String commentUrl, Iterable<Integer> ports) {
40+
41+
if (name == null) {
42+
throw new NullPointerException("name");
43+
}
44+
name = name.trim();
45+
if (name.length() == 0) {
46+
throw new IllegalArgumentException("empty name");
47+
}
48+
49+
for (int i = 0; i < name.length(); i++) {
50+
char c = name.charAt(i);
51+
if (c > 127) {
52+
throw new IllegalArgumentException("name contains non-ascii character: " + name);
53+
}
54+
55+
// Check prohibited characters.
56+
switch (c) {
57+
case '\t':
58+
case '\n':
59+
case 0x0b:
60+
case '\f':
61+
case '\r':
62+
case ' ':
63+
case ',':
64+
case ';':
65+
case '=':
66+
throw new IllegalArgumentException("name contains one of the following prohibited characters: " + "=,; \\t\\r\\n\\v\\f: " + name);
67+
}
68+
}
69+
70+
if (name.charAt(0) == '$') {
71+
throw new IllegalArgumentException("name starting with '$' not allowed: " + name);
72+
}
73+
74+
if (value == null) {
75+
throw new NullPointerException("value");
76+
}
4377

44-
public Cookie(String domain, String name, String value, String path, int maxAge, boolean secure, int version) {
45-
this.domain = domain;
4678
this.name = name;
4779
this.value = value;
48-
this.path = path;
80+
this.domain = validateValue("domain", domain);
81+
this.path = validateValue("path", path);
4982
this.maxAge = maxAge;
5083
this.secure = secure;
5184
this.version = version;
85+
this.httpOnly = httpOnly;
86+
87+
if (version > 0) {
88+
this.comment = validateValue("comment", comment);
89+
} else {
90+
this.comment = null;
91+
}
92+
if (version > 1) {
93+
this.discard = discard;
94+
this.commentUrl = validateValue("commentUrl", commentUrl);
95+
setPorts(ports);
96+
} else {
97+
this.discard = false;
98+
this.commentUrl = null;
99+
}
52100
}
53101

54102
public String getDomain() {
@@ -79,35 +127,30 @@ public int getVersion() {
79127
return version;
80128
}
81129

130+
public String getComment() {
131+
return this.comment;
132+
}
133+
134+
public String getCommentUrl() {
135+
return this.commentUrl;
136+
}
137+
138+
public boolean isHttpOnly() {
139+
return httpOnly;
140+
}
141+
142+
public boolean isDiscard() {
143+
return discard;
144+
}
145+
82146
public Set<Integer> getPorts() {
83147
if (unmodifiablePorts == null) {
84148
unmodifiablePorts = Collections.unmodifiableSet(ports);
85149
}
86150
return unmodifiablePorts;
87151
}
88152

89-
public void setPorts(int... ports) {
90-
if (ports == null) {
91-
throw new NullPointerException("ports");
92-
}
93-
94-
int[] portsCopy = ports.clone();
95-
if (portsCopy.length == 0) {
96-
unmodifiablePorts = this.ports = Collections.emptySet();
97-
} else {
98-
Set<Integer> newPorts = new TreeSet<Integer>();
99-
for (int p : portsCopy) {
100-
if (p <= 0 || p > 65535) {
101-
throw new IllegalArgumentException("port out of range: " + p);
102-
}
103-
newPorts.add(Integer.valueOf(p));
104-
}
105-
this.ports = newPorts;
106-
unmodifiablePorts = null;
107-
}
108-
}
109-
110-
public void setPorts(Iterable<Integer> ports) {
153+
private void setPorts(Iterable<Integer> ports) {
111154
Set<Integer> newPorts = new TreeSet<Integer>();
112155
for (int p : ports) {
113156
if (p <= 0 || p > 65535) {
@@ -125,7 +168,89 @@ public void setPorts(Iterable<Integer> ports) {
125168

126169
@Override
127170
public String toString() {
128-
return String.format("Cookie: domain=%s, name=%s, value=%s, path=%s, maxAge=%d, secure=%s",
129-
domain, name, value, path, maxAge, secure);
171+
StringBuilder buf = new StringBuilder();
172+
buf.append(getName());
173+
buf.append('=');
174+
buf.append(getValue());
175+
if (getDomain() != null) {
176+
buf.append("; domain=");
177+
buf.append(getDomain());
178+
}
179+
if (getPath() != null) {
180+
buf.append("; path=");
181+
buf.append(getPath());
182+
}
183+
if (getComment() != null) {
184+
buf.append("; comment=");
185+
buf.append(getComment());
186+
}
187+
if (getMaxAge() >= 0) {
188+
buf.append("; maxAge=");
189+
buf.append(getMaxAge());
190+
buf.append('s');
191+
}
192+
if (isSecure()) {
193+
buf.append("; secure");
194+
}
195+
if (isHttpOnly()) {
196+
buf.append("; HTTPOnly");
197+
}
198+
return buf.toString();
199+
}
200+
201+
private String validateValue(String name, String value) {
202+
if (value == null) {
203+
return null;
204+
}
205+
value = value.trim();
206+
if (value.length() == 0) {
207+
return null;
208+
}
209+
for (int i = 0; i < value.length(); i++) {
210+
char c = value.charAt(i);
211+
switch (c) {
212+
case '\r':
213+
case '\n':
214+
case '\f':
215+
case 0x0b:
216+
case ';':
217+
throw new IllegalArgumentException(name + " contains one of the following prohibited characters: " + ";\\r\\n\\f\\v (" + value + ')');
218+
}
219+
}
220+
return value;
221+
}
222+
223+
public int compareTo(Cookie c) {
224+
int v;
225+
v = getName().compareToIgnoreCase(c.getName());
226+
if (v != 0) {
227+
return v;
228+
}
229+
230+
if (getPath() == null) {
231+
if (c.getPath() != null) {
232+
return -1;
233+
}
234+
} else if (c.getPath() == null) {
235+
return 1;
236+
} else {
237+
v = getPath().compareTo(c.getPath());
238+
if (v != 0) {
239+
return v;
240+
}
241+
}
242+
243+
if (getDomain() == null) {
244+
if (c.getDomain() != null) {
245+
return -1;
246+
}
247+
} else if (c.getDomain() == null) {
248+
return 1;
249+
} else {
250+
v = getDomain().compareToIgnoreCase(c.getDomain());
251+
return v;
252+
}
253+
254+
return 0;
130255
}
131-
}
256+
}

api/src/main/java/com/ning/http/client/providers/jdk/JDKResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package com.ning.http.client.providers.jdk;
1414

15+
import com.ning.org.jboss.netty.handler.codec.http.CookieDecoder;
1516
import com.ning.http.client.Cookie;
1617
import com.ning.http.client.HttpResponseBodyPart;
1718
import com.ning.http.client.HttpResponseHeaders;
@@ -53,8 +54,7 @@ public List<Cookie> buildCookies() {
5354
// TODO: ask for parsed header
5455
List<String> v = header.getValue();
5556
for (String value : v) {
56-
Cookie cookie = AsyncHttpProviderUtils.parseCookie(value);
57-
cookies.add(cookie);
57+
cookies.addAll(CookieDecoder.decode(value));
5858
}
5959
}
6060
}

api/src/main/java/com/ning/http/util/AsyncHttpProviderUtils.java

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -508,69 +508,15 @@ public static String parseCharset(String contentType) {
508508
return null;
509509
}
510510

511-
public static Cookie parseCookie(String value) {
512-
String[] fields = value.split(";\\s*");
513-
String[] cookie = fields[0].split("=", 2);
514-
String cookieName = cookie[0];
515-
String cookieValue = (cookie.length == 1) ? null : cookie[1];
516-
517-
int maxAge = -1;
518-
String path = null;
519-
String domain = null;
520-
boolean secure = false;
521-
522-
boolean maxAgeSet = false;
523-
boolean expiresSet = false;
524-
525-
for (int j = 1; j < fields.length; j++) {
526-
if ("secure".equalsIgnoreCase(fields[j])) {
527-
secure = true;
528-
} else if (fields[j].indexOf('=') > 0) {
529-
String[] f = fields[j].split("=");
530-
if (f.length == 1) continue; // Add protection against null field values
531-
532-
// favor 'max-age' field over 'expires'
533-
if (!maxAgeSet && "max-age".equalsIgnoreCase(f[0])) {
534-
try {
535-
maxAge = Math.max(Integer.valueOf(removeQuote(f[1])), 0);
536-
} catch (NumberFormatException e1) {
537-
// ignore failure to parse -> treat as session cookie
538-
// invalidate a previously parsed expires-field
539-
maxAge = -1;
540-
}
541-
maxAgeSet = true;
542-
} else if (!maxAgeSet && !expiresSet && "expires".equalsIgnoreCase(f[0])) {
543-
try {
544-
maxAge = Math.max(convertExpireField(f[1]), 0);
545-
} catch (Exception e) {
546-
// original behavior, is this correct at all (expires field with max-age semantics)?
547-
try {
548-
maxAge = Math.max(Integer.valueOf(f[1]), 0);
549-
} catch (NumberFormatException e1) {
550-
// ignore failure to parse -> treat as session cookie
551-
}
552-
}
553-
expiresSet = true;
554-
} else if ("domain".equalsIgnoreCase(f[0])) {
555-
domain = f[1];
556-
} else if ("path".equalsIgnoreCase(f[0])) {
557-
path = f[1];
558-
}
559-
}
560-
}
561-
562-
return new Cookie(domain, cookieName, cookieValue, path, maxAge, secure);
563-
}
564-
565-
public static int convertExpireField(String timestring) throws Exception {
511+
public static int convertExpireField(String timestring) {
566512
String trimmedTimeString = removeQuote(timestring.trim());
567513

568514
for (SimpleDateFormat sdf : simpleDateFormat.get()) {
569515
Date date = sdf.parse(trimmedTimeString, new ParsePosition(0));
570516
if (date != null) {
571517
long now = System.currentTimeMillis();
572-
long expire = date.getTime();
573-
return (int) ((expire - now) / 1000);
518+
long maxAgeMillis = date.getTime() - now;
519+
return (int) (maxAgeMillis / 1000) + (maxAgeMillis % 1000 != 0 ? 1 : 0);
574520
}
575521
}
576522

0 commit comments

Comments
 (0)