Skip to content

Commit 6a73fa4

Browse files
committed
Add option to keep additional attributes on cookies
1 parent 4f0adc2 commit 6a73fa4

File tree

7 files changed

+66
-31
lines changed

7 files changed

+66
-31
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import java.time.ZonedDateTime;
1919
import java.util.ArrayList;
20+
import java.util.HashMap;
2021
import java.util.List;
22+
import java.util.Map;
2123
import java.util.Objects;
2224

2325
import io.fusionauth.http.HTTPValues.CookieAttributes;
@@ -39,6 +41,8 @@ public class Cookie implements Buildable<Cookie> {
3941

4042
public static final String SecurePrefix = "; " + CookieAttributes.Secure;
4143

44+
public Map<String, String> attributes = new HashMap<>(0);
45+
4246
public String domain;
4347

4448
public ZonedDateTime expires;
@@ -269,6 +273,9 @@ public void addAttribute(String name, String value) {
269273
break;
270274
case HTTPValues.CookieAttributes.SecureLower:
271275
secure = true;
276+
default:
277+
// Attributes should be not be required to have a value
278+
attributes.put(name, value == null ? "" : value);
272279
}
273280
}
274281

@@ -290,6 +297,10 @@ public boolean equals(Object o) {
290297
Objects.equals(value, cookie.value);
291298
}
292299

300+
public String getAttribute(String name) {
301+
return attributes.get(name);
302+
}
303+
293304
public String getDomain() {
294305
return domain;
295306
}
@@ -346,6 +357,10 @@ public void setValue(String value) {
346357
this.value = value;
347358
}
348359

360+
public boolean hasAttribute(String name) {
361+
return attributes.containsKey(name);
362+
}
363+
349364
@Override
350365
public int hashCode() {
351366
return Objects.hash(domain, expires, httpOnly, maxAge, name, path, secure, value);

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ private int processChunk(byte[] destination, int offset, int length) throws IOEx
103103
if (nextState == ChunkedBodyState.Complete) {
104104
state = nextState;
105105
bufferIndex++;
106-
continue;
106+
int leftOver = bufferLength - bufferIndex;
107+
if (leftOver > 0) {
108+
delegate.push(buffer, bufferIndex, leftOver);
109+
}
110+
111+
return -1;
107112
}
108113

109114
// Record the size hex digit

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,11 @@ public int read(byte[] b, int off, int len) throws IOException {
7373
this.bufferEndPosition = -1;
7474
}
7575

76-
// Ideally we would just continue to read from the delegate if we have not yet filled the buffer.
77-
// - However, in non-fixed length request such as a chunked transfer encoding, if the end of the request
78-
// is in the bytes we read from the buffer, and we call read() we will block because no bytes are available.
79-
// - So I think we have to return, and allow the caller to decide if they want to read more bytes based upon
80-
// the contents of the bytes we return.
81-
// TODO : Daniel : Review the above statement.
82-
// ...
83-
// TODO : Put back the code... we want to continue reading past the buffer here.
76+
// Note that we must return after reading from the buffer.
77+
// - We don't know if we are at the end of the InputStream. Calling read again will block causing us not to be able to
78+
// complete processing of the bytes we just read from the buffer in order to send the HTTP response.
79+
// The end result is the client will block while waiting for us to send a response until we take an exception waiting
80+
// for the read timeout.
8481
return read;
8582
}
8683

src/main/java/io/fusionauth/http/server/HTTPResponse.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -281,20 +281,6 @@ public void reset() {
281281
writer = null;
282282
}
283283

284-
/**
285-
* Reset the OutputStream.
286-
*/
287-
// TODO : Daniel : Review : This is used by prime-mvc. Is there any other way to handle this?
288-
// Discuss with Brian. I think all prime-mvc needs is for the socket to be reset?
289-
public void resetOutputStream() {
290-
if (outputStream.isCommitted()) {
291-
throw new IllegalStateException("The HTTPResponse can't be reset after it has been committed, meaning at least one byte was written back to the client.");
292-
}
293-
294-
outputStream.reset();
295-
writer = null;
296-
}
297-
298284
public void sendRedirect(String uri) {
299285
setHeader(Headers.Location, uri);
300286
status = Status.MovedTemporarily;

src/main/java/io/fusionauth/http/server/internal/HTTPServerThread.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,6 @@ public void run() {
107107
logger.trace("[{}] Accepted inbound connection. [{}] existing connections.", listenerAddress, clients.size());
108108
}
109109

110-
// TODO : Daniel : Review : Why is this number so much higher than the worker count when using persistent connections?
111-
// When using RESTIFY - we kill a lot of connections because read returns -1 while waiting for preamble.
112-
// This causes us to close a lot of workers. When using the JDK REST client this doesn't happen.
113-
// I don't know if this is just working as designed for HTTPURLConnection, or if it is related to
114-
// the read ahead we are doing.
115-
// Show Brian to see if he has any ideas.
116110
if (instrumenter != null) {
117111
instrumenter.acceptedConnection();
118112
}

src/main/java/io/fusionauth/http/util/HTTPTools.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.util.Map;
3030
import java.util.Objects;
3131

32-
import io.fusionauth.http.server.io.ConnectionClosedException;
3332
import io.fusionauth.http.HTTPMethod;
3433
import io.fusionauth.http.HTTPValues.ControlBytes;
3534
import io.fusionauth.http.HTTPValues.HeaderBytes;
@@ -41,6 +40,7 @@
4140
import io.fusionauth.http.server.HTTPRequest;
4241
import io.fusionauth.http.server.HTTPResponse;
4342
import io.fusionauth.http.server.Instrumenter;
43+
import io.fusionauth.http.server.io.ConnectionClosedException;
4444

4545
public final class HTTPTools {
4646
private static Logger logger;

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2024, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2021-2025, 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.
@@ -36,6 +36,9 @@
3636
import static org.testng.Assert.assertNull;
3737
import static org.testng.Assert.assertTrue;
3838

39+
/**
40+
* @author Brian Pontarelli
41+
*/
3942
public class CookieTest extends BaseTest {
4043
@Test
4144
public void cookieInjection() throws Exception {
@@ -305,6 +308,41 @@ public void fromResponseHeader() {
305308
// Borked cookie
306309
cookie = Cookie.fromResponseHeader("=a");
307310
assertNull(cookie);
311+
312+
// additional attributes
313+
// - name and value
314+
cookie = Cookie.fromResponseHeader("foo=;utm=123");
315+
assertEquals(cookie.name, "foo");
316+
assertEquals(cookie.getAttribute("utm"), "123");
317+
318+
// additional attributes
319+
// - name, sep but no value
320+
cookie = Cookie.fromResponseHeader("foo=;utm=");
321+
assertEquals(cookie.name, "foo");
322+
assertEquals(cookie.getAttribute("utm"), "");
323+
324+
// additional attributes
325+
// - name only
326+
cookie = Cookie.fromResponseHeader("foo=;utm");
327+
assertEquals(cookie.name, "foo");
328+
assertEquals(cookie.getAttribute("utm"), "");
329+
330+
// additional attributes
331+
// - multiple
332+
cookie = Cookie.fromResponseHeader("foo=;foo=bar;bar=baz;bing=boom");
333+
assertEquals(cookie.name, "foo");
334+
assertEquals(cookie.getAttribute("foo"), "bar");
335+
assertEquals(cookie.getAttribute("bar"), "baz");
336+
assertEquals(cookie.getAttribute("bing"), "boom");
337+
338+
// has attribute
339+
cookie = Cookie.fromResponseHeader("foo=;foo=bar;bar=baz;bing=boom");
340+
assertTrue(cookie.hasAttribute("foo"));
341+
assertTrue(cookie.hasAttribute("bar"));
342+
assertTrue(cookie.hasAttribute("bing"));
343+
assertFalse(cookie.hasAttribute("booya"));
344+
assertFalse(cookie.hasAttribute("baz"));
345+
assertFalse(cookie.hasAttribute("boom"));
308346
}
309347

310348
@Test(dataProvider = "schemes")

0 commit comments

Comments
 (0)