Skip to content

Commit

Permalink
Add getters for all fields of SetCookie (#9029)
Browse files Browse the repository at this point in the history
Fix parse method to support SameSite
Added test to validate new features
  • Loading branch information
tomas-langer authored Jul 23, 2024
1 parent 32bb209 commit c9e91c6
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 71 deletions.
214 changes: 146 additions & 68 deletions http/http/src/main/java/io/helidon/http/SetCookie.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
* Copyright (c) 2018, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,7 +21,9 @@
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

/**
* Represents {@code 'Set-Cookie'} header value specified by <a href="https://tools.ietf.org/html/rfc6265">RFC6265</a>.
Expand Down Expand Up @@ -54,15 +56,6 @@ private SetCookie(Builder builder) {
this.sameSite = builder.sameSite;
}

/**
* Gets cookie's name.
*
* @return the name.
*/
public String name() {
return name;
}

/**
* Creates a new fluent API builder.
*
Expand Down Expand Up @@ -125,6 +118,10 @@ public static SetCookie parse(String setCookie) {
hasNoValue(partName, partValue);
builder.httpOnly(true);
break;
case "samesite":
hasValue(partName, partValue);
builder.sameSite(SameSite.valueOf(partValue.toUpperCase(Locale.ROOT)));
break;
default:
throw new IllegalArgumentException("Unexpected Set-Cookie part: " + partName);
}
Expand All @@ -133,36 +130,105 @@ public static SetCookie parse(String setCookie) {
}

/**
* Text representation of this cookie.
* Creates new instance.
*
* @return cookie text
* @param name a cookie name.
* @param value a cookie value.
* @return a new instance with just the name and value configured
*/
public String text() {
return toString();
public static SetCookie create(String name, String value) {
return builder(name, value)
.build();
}

private static void hasNoValue(String partName, String partValue) {
if (partValue != null) {
throw new IllegalArgumentException("Set-Cookie parameter " + partName + " has to have no value!");
}
/**
* Name of the cookie.
*
* @return the name.
*/
public String name() {
return name;
}

private static void hasValue(String partName, String partValue) {
if (partValue == null) {
throw new IllegalArgumentException("Set-Cookie parameter " + partName + " has to have a value!");
}
/**
* Value of the cookie.
*
* @return value
*/
public String value() {
return value;
}

/**
* Creates new instance.
* Expiration of cookie.
*
* @param name a cookie name.
* @param value a cookie value.
* @return a new instance with just the name and value configured
* @return expiration if defined
*/
public static SetCookie create(String name, String value) {
return builder(name, value)
.build();
public Optional<ZonedDateTime> expires() {
return Optional.ofNullable(expires);
}

/**
* Max age of cookie.
*
* @return max age if defined
*/
public Optional<Duration> maxAge() {
return Optional.ofNullable(maxAge);
}

/**
* Domain of cookie.
*
* @return domain if defined
*/
public Optional<String> domain() {
return Optional.ofNullable(domain);
}

/**
* Path of cookie.
*
* @return path if defined
*/
public Optional<String> path() {
return Optional.ofNullable(path);
}

/**
* Secure attribute of cookie.
*
* @return whether secure was set
*/
public boolean secure() {
return secure;
}

/**
* HttpOnly attribute of cookie.
*
* @return whether {@code HttpOnly} was set
*/
public boolean httpOnly() {
return httpOnly;
}

/**
* Same site attribute of cookie.
*
* @return same site if defined
*/
public Optional<SameSite> sameSite() {
return Optional.ofNullable(sameSite);
}

/**
* Text representation of this cookie.
*
* @return cookie text
*/
public String text() {
return toString();
}

/**
Expand Down Expand Up @@ -211,6 +277,57 @@ public String toString() {
return result.toString();
}

private static void hasNoValue(String partName, String partValue) {
if (partValue != null) {
throw new IllegalArgumentException("Set-Cookie parameter " + partName + " has to have no value!");
}
}

private static void hasValue(String partName, String partValue) {
if (partValue == null) {
throw new IllegalArgumentException("Set-Cookie parameter " + partName + " has to have a value!");
}
}

/**
* The SameSite attribute of the Set-Cookie HTTP response header allows you to declare if your cookie should be restricted
* to a first-party or same-site context.
*/
public enum SameSite {
/**
* Cookies are not sent on normal cross-site subrequests (for example to load images or frames into a third party site)
* , but are sent when a user is navigating to the origin site (i.e., when following a link).
*
* This is the default cookie value if SameSite has not been explicitly specified in recent browser versions
*/
LAX("Lax"),
/**
* Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party
* websites.
*/
STRICT("Strict"),
/**
* Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-origin requests. If
* SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked).
*/
NONE("None");

private final String text;

SameSite(String text) {
this.text = text;
}

/**
* Text to write to the same site cookie param.
*
* @return text to send in cookie
*/
public String text() {
return text;
}
}

/**
* A fluent API builder for {@link SetCookie}.
*/
Expand Down Expand Up @@ -346,43 +463,4 @@ public Builder sameSite(SameSite sameSite) {
return this;
}
}

/**
* The SameSite attribute of the Set-Cookie HTTP response header allows you to declare if your cookie should be restricted
* to a first-party or same-site context.
*/
public enum SameSite {
/**
* Cookies are not sent on normal cross-site subrequests (for example to load images or frames into a third party site)
* , but are sent when a user is navigating to the origin site (i.e., when following a link).
*
* This is the default cookie value if SameSite has not been explicitly specified in recent browser versions
*/
LAX("Lax"),
/**
* Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party
* websites.
*/
STRICT("Strict"),
/**
* Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-origin requests. If
* SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked).
*/
NONE("None");

private final String text;

SameSite(String text) {
this.text = text;
}

/**
* Text to write to the same site cookie param.
*
* @return text to send in cookie
*/
public String text() {
return text;
}
}
}
21 changes: 18 additions & 3 deletions http/http/src/test/java/io/helidon/http/SetCookieTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,8 +15,11 @@
*/
package io.helidon.http;

import java.time.Duration;

import org.junit.jupiter.api.Test;

import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -34,9 +37,21 @@ public void testSetCookiesFromString() {
+ "Domain=domain.value; "
+ "Path=/; "
+ "Secure; "
+ "HttpOnly";
+ "HttpOnly; "
+ "SameSite=Lax";
SetCookie setCookie = SetCookie.parse(template);
assertThat(setCookie.toString(), is(template));

assertThat(setCookie.name(), is("some-cookie"));
assertThat(setCookie.value(), is("some-cookie-value"));
assertThat(setCookie.expires(), optionalValue(is(DateTime.parse("Thu, 22 Oct 2015 07:28:00 GMT"))));
assertThat(setCookie.maxAge(), optionalValue(is(Duration.ofSeconds(2592000))));
assertThat(setCookie.domain(), optionalValue(is("domain.value")));
assertThat(setCookie.path(), optionalValue(is("/")));
assertThat(setCookie.secure(), is(true));
assertThat(setCookie.httpOnly(), is(true));
assertThat(setCookie.sameSite(), optionalValue(is(SetCookie.SameSite.LAX)));

assertThat("Generate same cookie value", setCookie.toString(), is(template));
}

@Test
Expand Down

0 comments on commit c9e91c6

Please sign in to comment.