Skip to content

Commit 0f8139d

Browse files
authored
Merge pull request #20 from FusionAuth/degroff/parse-dquote-cookie-value
Fix cookie parsing to account for double-quoted values.
2 parents 3484282 + aeb05de commit 0f8139d

File tree

4 files changed

+83
-34
lines changed

4 files changed

+83
-34
lines changed

build.savant

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018-2023, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2018-2024, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ jackson5Version = "3.0.1"
1717
restifyVersion = "4.2.0"
1818
testngVersion = "7.8.0"
1919

20-
project(group: "io.fusionauth", name: "java-http", version: "0.3.0", licenses: ["ApacheV2_0"]) {
20+
project(group: "io.fusionauth", name: "java-http", version: "0.3.1", licenses: ["ApacheV2_0"]) {
2121
workflow {
2222
fetch {
2323
// Dependency resolution order:

java-http.ipr

+7-26
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
<component name="InspectionProjectProfileManager">
1313
<profile version="1.0">
1414
<option name="myName" value="Project Default" />
15+
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true">
16+
<option name="ignoredTypes">
17+
<set>
18+
<option value="java.util.List" />
19+
</set>
20+
</option>
21+
</inspection_tool>
1522
<inspection_tool class="groupsTestNG" enabled="true" level="WARNING" enabled_by_default="true">
1623
<option name="groups">
1724
<value>
@@ -1385,32 +1392,6 @@
13851392
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="Java 17" project-jdk-type="JavaSDK">
13861393
<output url="file://$PROJECT_DIR$/out" />
13871394
</component>
1388-
<component name="ProjectRunConfigurationManager">
1389-
<configuration default="true" type="TestNG">
1390-
<module name="java-http" />
1391-
<shortenClasspath name="NONE" />
1392-
<useClassPathOnly />
1393-
<option name="SUITE_NAME" value="" />
1394-
<option name="PACKAGE_NAME" value="" />
1395-
<option name="MAIN_CLASS_NAME" value="" />
1396-
<option name="GROUP_NAME" value="" />
1397-
<option name="TEST_OBJECT" value="CLASS" />
1398-
<option name="VM_PARAMETERS" value="-ea --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED" />
1399-
<option name="PARAMETERS" value="" />
1400-
<option name="OUTPUT_DIRECTORY" value="" />
1401-
<option name="TEST_SEARCH_SCOPE">
1402-
<value defaultName="moduleWithDependencies" />
1403-
</option>
1404-
<option name="PROPERTIES_FILE" value="" />
1405-
<properties />
1406-
<listeners>
1407-
<listener class="io.fusionauth.http.BaseTest$TestListener" />
1408-
</listeners>
1409-
<method v="2">
1410-
<option name="Make" enabled="true" />
1411-
</method>
1412-
</configuration>
1413-
</component>
14141395
<component name="VcsDirectoryMappings">
14151396
<mapping directory="$PROJECT_DIR$" vcs="Git" />
14161397
</component>

src/main/java/io/fusionauth/http/Cookie.java

+18-3
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ public static List<Cookie> fromRequestHeader(String header) {
130130
* @return The Cookie.
131131
*/
132132
public static Cookie fromResponseHeader(String header) {
133+
// Note we could use the JDK java.net.HttpCookie.parse(header) instead.
134+
// - However, they still don't support SameSite. Super lame.
133135
Cookie cookie = new Cookie();
134136
boolean inName = false, inValue = false, inAttributes = false;
135137
char[] chars = header.toCharArray();
@@ -145,14 +147,19 @@ public static Cookie fromResponseHeader(String header) {
145147

146148
if (c == '=' && inName) {
147149
name = header.substring(start, i);
148-
if (!inAttributes && name.trim().length() == 0) {
150+
if (!inAttributes && name.trim().isEmpty()) {
149151
return null;
150152
}
151153

152154
value = "";
153155
inValue = true;
154156
inName = false;
155-
start = i + 1;
157+
// Values may be double-quoted
158+
// https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1
159+
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
160+
start = (i < (header.length() - 1)) && chars[i + 1] == '"'
161+
? i + 2
162+
: i + 1;
156163
} else if (c == ';') {
157164
if (inName) {
158165
if (!inAttributes) {
@@ -162,7 +169,11 @@ public static Cookie fromResponseHeader(String header) {
162169
name = header.substring(start, i);
163170
value = null;
164171
} else {
165-
value = header.substring(start, i);
172+
// Values may be double-quoted
173+
int end = chars[i - 1] == '"'
174+
? i - 1
175+
: i;
176+
value = header.substring(start, end);
166177
}
167178

168179
if (inAttributes) {
@@ -209,6 +220,10 @@ public static Cookie fromResponseHeader(String header) {
209220
}
210221

211222
public void addAttribute(String name, String value) {
223+
if (name == null) {
224+
return;
225+
}
226+
212227
switch (name.toLowerCase()) {
213228
case HTTPValues.CookieAttributes.DomainLower:
214229
domain = value;

src/test/java/io/fusionauth/http/CookieTest.java

+56-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2022, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2021-2024, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -169,6 +169,58 @@ public void fromResponseHeader() {
169169
assertFalse(cookie.secure);
170170
assertEquals(cookie.value, "bar");
171171

172+
// Quoted value, no other attributes
173+
// https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1
174+
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
175+
cookie = Cookie.fromResponseHeader("foo=\"bar\";");
176+
assertNull(cookie.domain);
177+
assertNull(cookie.expires);
178+
assertFalse(cookie.httpOnly);
179+
assertNull(cookie.maxAge);
180+
assertEquals(cookie.name, "foo");
181+
assertNull(cookie.path);
182+
assertNull(cookie.sameSite);
183+
assertFalse(cookie.secure);
184+
assertEquals(cookie.value, "bar");
185+
186+
// Quoted value, additional attribute
187+
cookie = Cookie.fromResponseHeader("foo=\"bar\"; SameSite=");
188+
assertNull(cookie.domain);
189+
assertNull(cookie.expires);
190+
assertFalse(cookie.httpOnly);
191+
assertNull(cookie.maxAge);
192+
assertEquals(cookie.name, "foo");
193+
assertNull(cookie.path);
194+
assertNull(cookie.sameSite);
195+
assertFalse(cookie.secure);
196+
assertEquals(cookie.value, "bar");
197+
198+
// Missing closing quote
199+
// - This is not a valid value, but we are handling it anyway.
200+
cookie = Cookie.fromResponseHeader("foo=\"bar; SameSite=");
201+
assertNull(cookie.domain);
202+
assertNull(cookie.expires);
203+
assertFalse(cookie.httpOnly);
204+
assertNull(cookie.maxAge);
205+
assertEquals(cookie.name, "foo");
206+
assertNull(cookie.path);
207+
assertNull(cookie.sameSite);
208+
assertFalse(cookie.secure);
209+
assertEquals(cookie.value, "bar");
210+
211+
// Missing opening quote
212+
// - This is not a valid value, but we are handling it anyway.
213+
cookie = Cookie.fromResponseHeader("foo=bar\"; SameSite=");
214+
assertNull(cookie.domain);
215+
assertNull(cookie.expires);
216+
assertFalse(cookie.httpOnly);
217+
assertNull(cookie.maxAge);
218+
assertEquals(cookie.name, "foo");
219+
assertNull(cookie.path);
220+
assertNull(cookie.sameSite);
221+
assertFalse(cookie.secure);
222+
assertEquals(cookie.value, "bar");
223+
172224
// Broken attributes
173225
cookie = Cookie.fromResponseHeader("foo=bar; =fusionauth.io; =Wed, 21 Oct 2015 07:28:00 GMT; =1; =Lax");
174226
assertNull(cookie.domain);
@@ -206,7 +258,8 @@ public void fromResponseHeader() {
206258
assertEquals(cookie.value, "");
207259

208260
// Empty values
209-
cookie = Cookie.fromResponseHeader("foo=;Domain=;Expires=;Max-Age=;SameSite=");
261+
// - Max-Age and Expires should never be empty
262+
cookie = Cookie.fromResponseHeader("foo=;Domain=;SameSite=");
210263
assertEquals(cookie.domain, "");
211264
assertNull(cookie.expires);
212265
assertFalse(cookie.httpOnly);
@@ -319,4 +372,4 @@ public void roundTripSingle(String scheme) throws Exception {
319372
public void toRequestHeader() {
320373
assertEquals("foo=bar", new Cookie("foo", "bar").toRequestHeader());
321374
}
322-
}
375+
}

0 commit comments

Comments
 (0)