You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2021/10/12 08:54:49 UTC
[tomcat] branch main updated: Add support for generic cookie
attributes to SessionCookieConfig
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 6b8aeaf Add support for generic cookie attributes to SessionCookieConfig
6b8aeaf is described below
commit 6b8aeaf80860dd48d5ddd44e866fea5d6072ded3
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Oct 12 09:38:53 2021 +0100
Add support for generic cookie attributes to SessionCookieConfig
---
java/jakarta/servlet/SessionCookieConfig.java | 46 +++++++
java/jakarta/servlet/resources/web-common_6_0.xsd | 57 ++++++++
.../core/ApplicationSessionCookieConfig.java | 87 +++++++++---
.../org/apache/catalina/startup/ContextConfig.java | 17 +--
.../tomcat/util/descriptor/web/Constants.java | 8 ++
.../util/descriptor/web/LocalStrings.properties | 7 +-
.../tomcat/util/descriptor/web/SessionConfig.java | 57 +++++---
.../tomcat/util/descriptor/web/WebRuleSet.java | 4 +
.../apache/tomcat/util/descriptor/web/WebXml.java | 149 +++------------------
.../tomcat/util/http/LegacyCookieProcessor.java | 38 +++++-
.../tomcat/util/http/LocalStrings.properties | 2 +
.../tomcat/util/http/Rfc6265CookieProcessor.java | 59 +++++++-
test/jakarta/servlet/TestSessionCookieConfig.java | 56 ++++++++
.../valves/TestLoadBalancerDrainingValve.java | 60 ++++++---
.../tomcat/unittest/TesterSessionCookieConfig.java | 17 +++
.../tomcat/util/descriptor/web/TestWebXml.java | 90 +++++++++++++
test/webapp/WEB-INF/web.xml | 8 ++
test/webapp/jsp/session.jsp | 18 +++
webapps/docs/changelog.xml | 5 +
19 files changed, 576 insertions(+), 209 deletions(-)
diff --git a/java/jakarta/servlet/SessionCookieConfig.java b/java/jakarta/servlet/SessionCookieConfig.java
index 936e49d..6246d02 100644
--- a/java/jakarta/servlet/SessionCookieConfig.java
+++ b/java/jakarta/servlet/SessionCookieConfig.java
@@ -16,6 +16,8 @@
*/
package jakarta.servlet;
+import java.util.Map;
+
/**
* Configures the session cookies used by the web application associated with
* the ServletContext from which this SessionCookieConfig was obtained.
@@ -142,4 +144,48 @@ public interface SessionCookieConfig {
* @return the maximum age in seconds
*/
public int getMaxAge();
+
+ /**
+ * Sets the value for the given session cookie attribute. When a value is
+ * set via this method, the value returned by the attribute specific getter
+ * (if any) must be consistent with the value set via this method.
+ *
+ * @param name Name of attribute to set
+ * @param value Value of attribute
+ *
+ * @throws IllegalStateException if the associated ServletContext has
+ * already been initialised
+ *
+ * @throws IllegalArgumentException If the attribute name is null or
+ * contains any characters not permitted for use in Cookie names.
+ *
+ * @throws NumberFormatException If the attribute is known to be numerical
+ * but the provided value cannot be parsed to a number.
+ *
+ * @since Servlet 6.0
+ */
+ public void setAttribute(String name, String value);
+
+ /**
+ * Obtain the value for a sesison cookie given attribute. Values returned
+ * from this method must be consistent with the values set and returned by
+ * the attribute specific getters and setters in this class.
+ *
+ * @param name Name of attribute to return
+ *
+ * @return Value of specified attribute
+ *
+ * @since Servlet 6.0
+ */
+ public String getAttribute(String name);
+
+ /**
+ * Obtain the Map of attributes and values (excluding version) for this
+ * session cookie.
+ *
+ * @return A read-only Map of attributes to values, excluding version.
+ *
+ * @since Servlet 6.0
+ */
+ public Map<String,String> getAttributes();
}
diff --git a/java/jakarta/servlet/resources/web-common_6_0.xsd b/java/jakarta/servlet/resources/web-common_6_0.xsd
index c0e3dd8..fae4e41 100644
--- a/java/jakarta/servlet/resources/web-common_6_0.xsd
+++ b/java/jakarta/servlet/resources/web-common_6_0.xsd
@@ -167,6 +167,50 @@
<!-- **************************************************** -->
+ <xsd:complexType name="attribute-valueType">
+ <xsd:annotation>
+ <xsd:documentation>
+
+ This type is a general type that can be used to declare
+ attribute/value lists.
+
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="description"
+ type="jakartaee:descriptionType"
+ minOccurs="0"
+ maxOccurs="unbounded"/>
+ <xsd:element name="attribute-name"
+ type="jakartaee:string">
+ <xsd:annotation>
+ <xsd:documentation>
+
+ The attribute-name element contains the name of an
+ attribute.
+
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="attribute-value"
+ type="jakartaee:xsdStringType">
+ <xsd:annotation>
+ <xsd:documentation>
+
+ The attribute-value element contains the value of a
+ attribute.
+
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="id"
+ type="xsd:ID"/>
+ </xsd:complexType>
+
+
+<!-- **************************************************** -->
+
<xsd:complexType name="auth-constraintType">
<xsd:annotation>
<xsd:documentation>
@@ -985,6 +1029,19 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name="attribute"
+ type="jakartaee:attribute-valueType"
+ minOccurs="0"
+ maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation>
+
+ The attribute-param element contains a name/value pair to
+ be added as an attribute to every session cookie.
+
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
</xsd:sequence>
<xsd:attribute name="id"
type="xsd:ID"/>
diff --git a/java/org/apache/catalina/core/ApplicationSessionCookieConfig.java b/java/org/apache/catalina/core/ApplicationSessionCookieConfig.java
index 5793d92..aaf7a13 100644
--- a/java/org/apache/catalina/core/ApplicationSessionCookieConfig.java
+++ b/java/org/apache/catalina/core/ApplicationSessionCookieConfig.java
@@ -16,12 +16,17 @@
*/
package org.apache.catalina.core;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
import jakarta.servlet.SessionCookieConfig;
import jakarta.servlet.http.Cookie;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.util.SessionConfig;
+import org.apache.tomcat.util.descriptor.web.Constants;
import org.apache.tomcat.util.res.StringManager;
public class ApplicationSessionCookieConfig implements SessionCookieConfig {
@@ -31,13 +36,13 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
*/
private static final StringManager sm = StringManager.getManager(ApplicationSessionCookieConfig.class);
- private boolean httpOnly;
- private boolean secure;
- private int maxAge = -1;
- private String comment;
- private String domain;
+ private static final int DEFAULT_MAX_AGE = -1;
+ private static final boolean DEFAULT_HTTP_ONLY = false;
+ private static final boolean DEFAULT_SECURE = false;
+
+ private final Map<String,String> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
private String name;
- private String path;
private StandardContext context;
public ApplicationSessionCookieConfig(StandardContext context) {
@@ -46,17 +51,21 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
@Override
public String getComment() {
- return comment;
+ return getAttribute(Constants.COOKIE_COMMENT_ATTR);
}
@Override
public String getDomain() {
- return domain;
+ return getAttribute(Constants.COOKIE_DOMAIN_ATTR);
}
@Override
public int getMaxAge() {
- return maxAge;
+ String maxAge = getAttribute(Constants.COOKIE_MAX_AGE_ATTR);
+ if (maxAge == null) {
+ return DEFAULT_MAX_AGE;
+ }
+ return Integer.parseInt(maxAge);
}
@Override
@@ -66,17 +75,25 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
@Override
public String getPath() {
- return path;
+ return getAttribute(Constants.COOKIE_PATH_ATTR);
}
@Override
public boolean isHttpOnly() {
- return httpOnly;
+ String httpOnly = getAttribute(Constants.COOKIE_HTTP_ONLY_ATTR);
+ if (httpOnly == null) {
+ return DEFAULT_HTTP_ONLY;
+ }
+ return Boolean.parseBoolean(httpOnly);
}
@Override
public boolean isSecure() {
- return secure;
+ String secure = getAttribute(Constants.COOKIE_SECURE_ATTR);
+ if (secure == null) {
+ return DEFAULT_SECURE;
+ }
+ return Boolean.parseBoolean(secure);
}
@Override
@@ -86,7 +103,7 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
"applicationSessionCookieConfig.ise", "comment",
context.getPath()));
}
- this.comment = comment;
+ setAttribute(Constants.COOKIE_COMMENT_ATTR, comment);
}
@Override
@@ -96,7 +113,7 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
"applicationSessionCookieConfig.ise", "domain name",
context.getPath()));
}
- this.domain = domain;
+ setAttribute(Constants.COOKIE_DOMAIN_ATTR, domain);
}
@Override
@@ -106,7 +123,7 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
"applicationSessionCookieConfig.ise", "HttpOnly",
context.getPath()));
}
- this.httpOnly = httpOnly;
+ setAttribute(Constants.COOKIE_HTTP_ONLY_ATTR, Boolean.toString(httpOnly));
}
@Override
@@ -116,7 +133,7 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
"applicationSessionCookieConfig.ise", "max age",
context.getPath()));
}
- this.maxAge = maxAge;
+ setAttribute(Constants.COOKIE_MAX_AGE_ATTR, Integer.toString(maxAge));
}
@Override
@@ -136,7 +153,7 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
"applicationSessionCookieConfig.ise", "path",
context.getPath()));
}
- this.path = path;
+ setAttribute(Constants.COOKIE_PATH_ATTR, path);
}
@Override
@@ -146,7 +163,24 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
"applicationSessionCookieConfig.ise", "secure",
context.getPath()));
}
- this.secure = secure;
+ setAttribute(Constants.COOKIE_SECURE_ATTR, Boolean.toString(secure));
+ }
+
+
+ @Override
+ public void setAttribute(String name, String value) {
+ attributes.put(name, value);
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+
+ @Override
+ public Map<String, String> getAttributes() {
+ return Collections.unmodifiableMap(attributes);
}
/**
@@ -197,6 +231,23 @@ public class ApplicationSessionCookieConfig implements SessionCookieConfig {
cookie.setPath(SessionConfig.getSessionCookiePath(context));
+ // Other attributes
+ for (Map.Entry<String,String> attribute : scc.getAttributes().entrySet()) {
+ switch (attribute.getKey()) {
+ case Constants.COOKIE_COMMENT_ATTR:
+ case Constants.COOKIE_DOMAIN_ATTR:
+ case Constants.COOKIE_MAX_AGE_ATTR:
+ case Constants.COOKIE_PATH_ATTR:
+ case Constants.COOKIE_SECURE_ATTR:
+ case Constants.COOKIE_HTTP_ONLY_ATTR:
+ // Handled above so NO-OP
+ break;
+ default: {
+ cookie.setAttribute(attribute.getKey(), attribute.getValue());
+ }
+ }
+ }
+
return cookie;
}
}
diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java
index 508457a..3e75e79 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -1567,20 +1567,11 @@ public class ContextConfig implements LifecycleListener {
context.setSessionTimeout(
sessionConfig.getSessionTimeout().intValue());
}
- SessionCookieConfig scc =
- context.getServletContext().getSessionCookieConfig();
+ SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig();
scc.setName(sessionConfig.getCookieName());
- scc.setDomain(sessionConfig.getCookieDomain());
- scc.setPath(sessionConfig.getCookiePath());
- scc.setComment(sessionConfig.getCookieComment());
- if (sessionConfig.getCookieHttpOnly() != null) {
- scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
- }
- if (sessionConfig.getCookieSecure() != null) {
- scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
- }
- if (sessionConfig.getCookieMaxAge() != null) {
- scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
+ Map<String,String> attributes = sessionConfig.getCookieAttributes();
+ for (Map.Entry<String,String> attribute : attributes.entrySet()) {
+ scc.setAttribute(attribute.getKey(), attribute.getValue());
}
if (sessionConfig.getSessionTrackingModes().size() > 0) {
context.getServletContext().setSessionTrackingModes(
diff --git a/java/org/apache/tomcat/util/descriptor/web/Constants.java b/java/org/apache/tomcat/util/descriptor/web/Constants.java
index dd3e5dd..7b6228c 100644
--- a/java/org/apache/tomcat/util/descriptor/web/Constants.java
+++ b/java/org/apache/tomcat/util/descriptor/web/Constants.java
@@ -23,4 +23,12 @@ public class Constants {
public static final String WEB_XML_LOCATION = "/WEB-INF/web.xml";
+ // -------------------------------------------------- Cookie attribute names
+ public static final String COOKIE_COMMENT_ATTR = "Comment";
+ public static final String COOKIE_DOMAIN_ATTR = "Domain";
+ public static final String COOKIE_MAX_AGE_ATTR = "Max-Age";
+ public static final String COOKIE_PATH_ATTR = "Path";
+ public static final String COOKIE_SECURE_ATTR = "Secure";
+ public static final String COOKIE_HTTP_ONLY_ATTR = "HttpOnly";
+ public static final String COOKIE_SAME_SITE_ATTR = "SameSite";
}
diff --git a/java/org/apache/tomcat/util/descriptor/web/LocalStrings.properties b/java/org/apache/tomcat/util/descriptor/web/LocalStrings.properties
index dd57b51..a48dac4 100644
--- a/java/org/apache/tomcat/util/descriptor/web/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/descriptor/web/LocalStrings.properties
@@ -45,13 +45,8 @@ webXml.mergeConflictLoginConfig=A LoginConfig was defined inconsistently in mult
webXml.mergeConflictOrder=Fragment relative ordering contains circular references. This can be resolved by using absolute ordering in web.xml.
webXml.mergeConflictResource=The Resource [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
webXml.mergeConflictServlet=The Servlet [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
-webXml.mergeConflictSessionCookieComment=The session cookie comment was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieDomain=The session cookie domain was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieHttpOnly=The session cookie http-only flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieMaxAge=The session cookie max-age was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieAttributes=The session cookie attributes were defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
webXml.mergeConflictSessionCookieName=The session cookie name was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookiePath=The session cookie path was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieSecure=The session cookie secure flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
webXml.mergeConflictSessionTimeout=The session timeout was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
webXml.mergeConflictSessionTrackingMode=The session tracking modes were defined inconsistently in multiple fragments including fragment with name [{0}] located at [{1}]
webXml.mergeConflictString=The [{0}] with name [{1}] was defined inconsistently in multiple fragments including fragment with name [{2}] located at [{3}]
diff --git a/java/org/apache/tomcat/util/descriptor/web/SessionConfig.java b/java/org/apache/tomcat/util/descriptor/web/SessionConfig.java
index 7e169ac..2daddd4 100644
--- a/java/org/apache/tomcat/util/descriptor/web/SessionConfig.java
+++ b/java/org/apache/tomcat/util/descriptor/web/SessionConfig.java
@@ -17,6 +17,8 @@
package org.apache.tomcat.util.descriptor.web;
import java.util.EnumSet;
+import java.util.Map;
+import java.util.TreeMap;
import jakarta.servlet.SessionTrackingMode;
@@ -26,14 +28,10 @@ import jakarta.servlet.SessionTrackingMode;
* deployment descriptor.
*/
public class SessionConfig {
+
private Integer sessionTimeout;
private String cookieName;
- private String cookieDomain;
- private String cookiePath;
- private String cookieComment;
- private Boolean cookieHttpOnly;
- private Boolean cookieSecure;
- private Integer cookieMaxAge;
+ private final Map<String,String> cookieAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final EnumSet<SessionTrackingMode> sessionTrackingModes =
EnumSet.noneOf(SessionTrackingMode.class);
@@ -52,45 +50,67 @@ public class SessionConfig {
}
public String getCookieDomain() {
- return cookieDomain;
+ return getCookieAttribute(Constants.COOKIE_DOMAIN_ATTR);
}
public void setCookieDomain(String cookieDomain) {
- this.cookieDomain = cookieDomain;
+ setCookieAttribute(Constants.COOKIE_DOMAIN_ATTR, cookieDomain);
}
public String getCookiePath() {
- return cookiePath;
+ return getCookieAttribute(Constants.COOKIE_PATH_ATTR);
}
public void setCookiePath(String cookiePath) {
- this.cookiePath = cookiePath;
+ setCookieAttribute(Constants.COOKIE_PATH_ATTR, cookiePath);
}
public String getCookieComment() {
- return cookieComment;
+ return getCookieAttribute(Constants.COOKIE_COMMENT_ATTR);
}
public void setCookieComment(String cookieComment) {
- this.cookieComment = cookieComment;
+ setCookieAttribute(Constants.COOKIE_COMMENT_ATTR, cookieComment);
}
public Boolean getCookieHttpOnly() {
- return cookieHttpOnly;
+ String httpOnly = getCookieAttribute(Constants.COOKIE_HTTP_ONLY_ATTR);
+ if (httpOnly == null) {
+ return null;
+ }
+ return Boolean.valueOf(httpOnly);
}
public void setCookieHttpOnly(String cookieHttpOnly) {
- this.cookieHttpOnly = Boolean.valueOf(cookieHttpOnly);
+ setCookieAttribute(Constants.COOKIE_HTTP_ONLY_ATTR, cookieHttpOnly);
}
public Boolean getCookieSecure() {
- return cookieSecure;
+ String secure = getCookieAttribute(Constants.COOKIE_SECURE_ATTR);
+ if (secure == null) {
+ return null;
+ }
+ return Boolean.valueOf(secure);
}
public void setCookieSecure(String cookieSecure) {
- this.cookieSecure = Boolean.valueOf(cookieSecure);
+ setCookieAttribute(Constants.COOKIE_SECURE_ATTR, cookieSecure);
}
public Integer getCookieMaxAge() {
- return cookieMaxAge;
+ String maxAge = getCookieAttribute(Constants.COOKIE_MAX_AGE_ATTR);
+ if (maxAge == null) {
+ return null;
+ }
+ return Integer.valueOf(maxAge);
}
public void setCookieMaxAge(String cookieMaxAge) {
- this.cookieMaxAge = Integer.valueOf(cookieMaxAge);
+ setCookieAttribute(Constants.COOKIE_MAX_AGE_ATTR, cookieMaxAge);
+ }
+
+ public Map<String,String> getCookieAttributes() {
+ return cookieAttributes;
+ }
+ public void setCookieAttribute(String name, String value) {
+ cookieAttributes.put(name, value);
+ }
+ public String getCookieAttribute(String name) {
+ return cookieAttributes.get(name);
}
public EnumSet<SessionTrackingMode> getSessionTrackingModes() {
@@ -100,5 +120,4 @@ public class SessionConfig {
sessionTrackingModes.add(
SessionTrackingMode.valueOf(sessionTrackingMode));
}
-
}
diff --git a/java/org/apache/tomcat/util/descriptor/web/WebRuleSet.java b/java/org/apache/tomcat/util/descriptor/web/WebRuleSet.java
index ba3bd53..61d3799 100644
--- a/java/org/apache/tomcat/util/descriptor/web/WebRuleSet.java
+++ b/java/org/apache/tomcat/util/descriptor/web/WebRuleSet.java
@@ -443,6 +443,10 @@ public class WebRuleSet implements RuleSet {
"setCookieSecure", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/max-age",
"setCookieMaxAge", 0);
+ digester.addCallMethod(fullPrefix + "/session-config/cookie-config/attribute",
+ "setCookieAttribute", 2);
+ digester.addCallParam(fullPrefix + "/session-config/cookie-config/attribute/attribute-name", 0);
+ digester.addCallParam(fullPrefix + "/session-config/cookie-config/attribute/attribute-value", 1);
digester.addCallMethod(fullPrefix + "/session-config/tracking-mode",
"addSessionTrackingMode", 0);
diff --git a/java/org/apache/tomcat/util/descriptor/web/WebXml.java b/java/org/apache/tomcat/util/descriptor/web/WebXml.java
index 8cba46f..3dc6a3e 100644
--- a/java/org/apache/tomcat/util/descriptor/web/WebXml.java
+++ b/java/org/apache/tomcat/util/descriptor/web/WebXml.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.TreeMap;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.ServletContext;
@@ -1779,138 +1780,32 @@ public class WebXml extends XmlEncodingBase implements DocumentProperties.Charse
sessionConfig.setCookieName(
temp.getSessionConfig().getCookieName());
}
- if (sessionConfig.getCookieDomain() == null) {
- for (WebXml fragment : fragments) {
- String value = fragment.getSessionConfig().getCookieDomain();
- if (value != null) {
- if (temp.getSessionConfig().getCookieDomain() == null) {
- temp.getSessionConfig().setCookieDomain(value);
- } else if (value.equals(
- temp.getSessionConfig().getCookieDomain())) {
- // Fragments use same value - no conflict
- } else {
- log.error(sm.getString(
- "webXml.mergeConflictSessionCookieDomain",
- fragment.getName(),
- fragment.getURL()));
- return false;
- }
- }
- }
- sessionConfig.setCookieDomain(
- temp.getSessionConfig().getCookieDomain());
- }
- if (sessionConfig.getCookiePath() == null) {
- for (WebXml fragment : fragments) {
- String value = fragment.getSessionConfig().getCookiePath();
- if (value != null) {
- if (temp.getSessionConfig().getCookiePath() == null) {
- temp.getSessionConfig().setCookiePath(value);
- } else if (value.equals(
- temp.getSessionConfig().getCookiePath())) {
- // Fragments use same value - no conflict
- } else {
- log.error(sm.getString(
- "webXml.mergeConflictSessionCookiePath",
- fragment.getName(),
- fragment.getURL()));
- return false;
- }
- }
- }
- sessionConfig.setCookiePath(
- temp.getSessionConfig().getCookiePath());
- }
- if (sessionConfig.getCookieComment() == null) {
- for (WebXml fragment : fragments) {
- String value = fragment.getSessionConfig().getCookieComment();
- if (value != null) {
- if (temp.getSessionConfig().getCookieComment() == null) {
- temp.getSessionConfig().setCookieComment(value);
- } else if (value.equals(
- temp.getSessionConfig().getCookieComment())) {
- // Fragments use same value - no conflict
- } else {
- log.error(sm.getString(
- "webXml.mergeConflictSessionCookieComment",
- fragment.getName(),
- fragment.getURL()));
- return false;
- }
- }
- }
- sessionConfig.setCookieComment(
- temp.getSessionConfig().getCookieComment());
- }
- if (sessionConfig.getCookieHttpOnly() == null) {
- for (WebXml fragment : fragments) {
- Boolean value = fragment.getSessionConfig().getCookieHttpOnly();
- if (value != null) {
- if (temp.getSessionConfig().getCookieHttpOnly() == null) {
- temp.getSessionConfig().setCookieHttpOnly(value.toString());
- } else if (value.equals(
- temp.getSessionConfig().getCookieHttpOnly())) {
- // Fragments use same value - no conflict
- } else {
- log.error(sm.getString(
- "webXml.mergeConflictSessionCookieHttpOnly",
- fragment.getName(),
- fragment.getURL()));
- return false;
- }
- }
- }
- if (temp.getSessionConfig().getCookieHttpOnly() != null) {
- sessionConfig.setCookieHttpOnly(
- temp.getSessionConfig().getCookieHttpOnly().toString());
- }
- }
- if (sessionConfig.getCookieSecure() == null) {
- for (WebXml fragment : fragments) {
- Boolean value = fragment.getSessionConfig().getCookieSecure();
- if (value != null) {
- if (temp.getSessionConfig().getCookieSecure() == null) {
- temp.getSessionConfig().setCookieSecure(value.toString());
- } else if (value.equals(
- temp.getSessionConfig().getCookieSecure())) {
- // Fragments use same value - no conflict
- } else {
- log.error(sm.getString(
- "webXml.mergeConflictSessionCookieSecure",
- fragment.getName(),
- fragment.getURL()));
- return false;
- }
- }
- }
- if (temp.getSessionConfig().getCookieSecure() != null) {
- sessionConfig.setCookieSecure(
- temp.getSessionConfig().getCookieSecure().toString());
- }
- }
- if (sessionConfig.getCookieMaxAge() == null) {
- for (WebXml fragment : fragments) {
- Integer value = fragment.getSessionConfig().getCookieMaxAge();
- if (value != null) {
- if (temp.getSessionConfig().getCookieMaxAge() == null) {
- temp.getSessionConfig().setCookieMaxAge(value.toString());
- } else if (value.equals(
- temp.getSessionConfig().getCookieMaxAge())) {
- // Fragments use same value - no conflict
+
+ Map<String,String> mainAttributes = getSessionConfig().getCookieAttributes();
+ Map<String,String> mergedFragmentAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (WebXml fragment : fragments) {
+ for (Map.Entry<String,String> attribute : fragment.getSessionConfig().getCookieAttributes().entrySet()) {
+ // Skip any attribute in a fragment that is defined in the main web.xml
+ if (!mainAttributes.containsKey(attribute.getKey())) {
+ if (mergedFragmentAttributes.containsKey(attribute.getKey())) {
+ // Attribute has already been seen.
+ // If values are the same, NO-OP. If they are different
+ // trigger a merge error
+ if (!mergedFragmentAttributes.get(attribute.getKey()).equals(attribute.getValue())) {
+ log.error(sm.getString(
+ "webXml.mergeConflictSessionCookieAttributes",
+ fragment.getName(),
+ fragment.getURL()));
+ return false;
+ }
} else {
- log.error(sm.getString(
- "webXml.mergeConflictSessionCookieMaxAge",
- fragment.getName(),
- fragment.getURL()));
- return false;
+ // First time this attribute has been seen. Add it.
+ mergedFragmentAttributes.put(attribute.getKey(), attribute.getValue());
}
}
}
- if (temp.getSessionConfig().getCookieMaxAge() != null) {
- sessionConfig.setCookieMaxAge(
- temp.getSessionConfig().getCookieMaxAge().toString());
- }
}
+ mainAttributes.putAll(mergedFragmentAttributes);
if (sessionConfig.getSessionTrackingModes().size() == 0) {
for (WebXml fragment : fragments) {
diff --git a/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java b/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java
index 9a5078e..990ad3e 100644
--- a/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java
+++ b/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java
@@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.text.FieldPosition;
import java.util.BitSet;
import java.util.Date;
+import java.util.Map;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@@ -29,6 +30,7 @@ import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.descriptor.web.Constants;
import org.apache.tomcat.util.log.UserDataHelper;
import org.apache.tomcat.util.res.StringManager;
@@ -326,11 +328,39 @@ public final class LegacyCookieProcessor extends CookieProcessorBase {
buf.append("; HttpOnly");
}
- SameSiteCookies sameSiteCookiesValue = getSameSiteCookies();
-
- if (!sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) {
+ String cookieSameSite = cookie.getAttribute(Constants.COOKIE_SAME_SITE_ATTR);
+ if (cookieSameSite == null) {
+ // Use processor config
+ SameSiteCookies sameSiteCookiesValue = getSameSiteCookies();
+ if (sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) {
+ buf.append("; SameSite=");
+ buf.append(sameSiteCookiesValue.getValue());
+ }
+ } else {
+ // Use explict config
buf.append("; SameSite=");
- buf.append(sameSiteCookiesValue.getValue());
+ buf.append(cookieSameSite);
+ }
+
+ // Add the remaining attributes
+ for (Map.Entry<String,String> entry : cookie.getAttributes().entrySet()) {
+ switch (entry.getKey()) {
+ case Constants.COOKIE_COMMENT_ATTR:
+ case Constants.COOKIE_DOMAIN_ATTR:
+ case Constants.COOKIE_MAX_AGE_ATTR:
+ case Constants.COOKIE_PATH_ATTR:
+ case Constants.COOKIE_SECURE_ATTR:
+ case Constants.COOKIE_HTTP_ONLY_ATTR:
+ case Constants.COOKIE_SAME_SITE_ATTR:
+ // Handled above so NO-OP
+ break;
+ default: {
+ buf.append("; ");
+ buf.append(entry.getKey());
+ buf.append('=');
+ maybeQuote(buf, entry.getValue(), version);
+ }
+ }
}
return buf.toString();
diff --git a/java/org/apache/tomcat/util/http/LocalStrings.properties b/java/org/apache/tomcat/util/http/LocalStrings.properties
index f9b8e0d..43307a8 100644
--- a/java/org/apache/tomcat/util/http/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/http/LocalStrings.properties
@@ -36,6 +36,8 @@ parameters.maxCountFail.fallToDebug=\n\
parameters.multipleDecodingFail=Character decoding failed. A total of [{0}] failures were detected but only the first was logged. Enable debug level logging for this logger to log all failures.
parameters.noequal=Parameter starting at position [{0}] and ending at position [{1}] with a value of [{2}] was not followed by an ''='' character
+rfc6265CookieProcessor.invalidAttributeName=An invalid attribute name [{0}] was specified for this cookie
+rfc6265CookieProcessor.invalidAttributeValue=An invalid attribute value [{1}] was specified for this cookie attribute [{0}]
rfc6265CookieProcessor.invalidCharInValue=An invalid character [{0}] was present in the Cookie value
rfc6265CookieProcessor.invalidDomain=An invalid domain [{0}] was specified for this cookie
rfc6265CookieProcessor.invalidPath=An invalid path [{0}] was specified for this cookie
diff --git a/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java b/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java
index 0864750..5a7bcde 100644
--- a/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java
+++ b/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java
@@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.text.FieldPosition;
import java.util.BitSet;
import java.util.Date;
+import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
@@ -28,7 +29,9 @@ import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.descriptor.web.Constants;
import org.apache.tomcat.util.http.parser.Cookie;
+import org.apache.tomcat.util.http.parser.HttpParser;
import org.apache.tomcat.util.res.StringManager;
public class Rfc6265CookieProcessor extends CookieProcessorBase {
@@ -164,11 +167,40 @@ public class Rfc6265CookieProcessor extends CookieProcessorBase {
header.append("; HttpOnly");
}
- SameSiteCookies sameSiteCookiesValue = getSameSiteCookies();
-
- if (!sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) {
+ String cookieSameSite = cookie.getAttribute(Constants.COOKIE_SAME_SITE_ATTR);
+ if (cookieSameSite == null) {
+ // Use processor config
+ SameSiteCookies sameSiteCookiesValue = getSameSiteCookies();
+ if (sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) {
+ header.append("; SameSite=");
+ header.append(sameSiteCookiesValue.getValue());
+ }
+ } else {
+ // Use explict config
header.append("; SameSite=");
- header.append(sameSiteCookiesValue.getValue());
+ header.append(cookieSameSite);
+ }
+
+ // Add the remaining attributes
+ for (Map.Entry<String,String> entry : cookie.getAttributes().entrySet()) {
+ switch (entry.getKey()) {
+ case Constants.COOKIE_COMMENT_ATTR:
+ case Constants.COOKIE_DOMAIN_ATTR:
+ case Constants.COOKIE_MAX_AGE_ATTR:
+ case Constants.COOKIE_PATH_ATTR:
+ case Constants.COOKIE_SECURE_ATTR:
+ case Constants.COOKIE_HTTP_ONLY_ATTR:
+ case Constants.COOKIE_SAME_SITE_ATTR:
+ // Handled above so NO-OP
+ break;
+ default: {
+ validateAttribute(entry.getKey(), entry.getValue());
+ header.append("; ");
+ header.append(entry.getKey());
+ header.append('=');
+ header.append(entry.getValue());
+ }
+ }
}
return header.toString();
@@ -237,4 +269,23 @@ public class Rfc6265CookieProcessor extends CookieProcessorBase {
}
}
}
+
+
+ private void validateAttribute(String name, String value) {
+ char[] chars = name.toCharArray();
+ for (char ch : chars) {
+ if (!HttpParser.isToken(ch)) {
+ throw new IllegalArgumentException(sm.getString(
+ "rfc6265CookieProcessor.invalidAttributeName", name));
+ }
+ }
+
+ chars = value.toCharArray();
+ for (char ch : chars) {
+ if (ch < 0x20 || ch > 0x7E || ch == ';') {
+ throw new IllegalArgumentException(sm.getString(
+ "rfc6265CookieProcessor.invalidAttributeValue", name, value));
+ }
+ }
+ }
}
diff --git a/test/jakarta/servlet/TestSessionCookieConfig.java b/test/jakarta/servlet/TestSessionCookieConfig.java
new file mode 100644
index 0000000..5faa668
--- /dev/null
+++ b/test/jakarta/servlet/TestSessionCookieConfig.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jakarta.servlet;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TestSessionCookieConfig extends TomcatBaseTest {
+
+ /*
+ * Not strictly testing the SessionCookieConfig class
+ */
+ @Test
+ public void testCustomAttribute() throws Exception {
+ getTomcatInstanceTestWebapp(false, true);
+
+ ByteChunk responseBody = new ByteChunk();
+ Map<String,List<String>> responseHeaders = new HashMap<>();
+
+ int statusCode =
+ getUrl("http://localhost:" + getPort() + "/test/bug49nnn/bug49196.jsp", responseBody, responseHeaders);
+
+ Assert.assertEquals(HttpServletResponse.SC_OK, statusCode);
+ Assert.assertTrue(responseBody.toString().contains("OK"));
+ Assert.assertTrue(responseHeaders.containsKey("Set-Cookie"));
+
+ List<String> setCookieHeaders = responseHeaders.get("Set-Cookie");
+ Assert.assertEquals(1, setCookieHeaders.size());
+
+ String setCookieHeader = setCookieHeaders.get(0);
+ Assert.assertTrue(setCookieHeader.contains("; aaa=bbb"));
+ }
+}
diff --git a/test/org/apache/catalina/valves/TestLoadBalancerDrainingValve.java b/test/org/apache/catalina/valves/TestLoadBalancerDrainingValve.java
index fc06ab7..1e13ed2 100644
--- a/test/org/apache/catalina/valves/TestLoadBalancerDrainingValve.java
+++ b/test/org/apache/catalina/valves/TestLoadBalancerDrainingValve.java
@@ -17,7 +17,10 @@ package org.apache.catalina.valves;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import jakarta.servlet.ServletContext;
import jakarta.servlet.SessionCookieConfig;
@@ -34,6 +37,7 @@ import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardPipeline;
+import org.apache.tomcat.util.descriptor.web.Constants;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
@@ -191,12 +195,8 @@ public class TestLoadBalancerDrainingValve {
private static class CookieConfig implements SessionCookieConfig {
private String name;
- private String domain;
- private String path;
- private String comment;
- private boolean httpOnly;
- private boolean secure;
- private int maxAge;
+
+ private final Map<String,String> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
@Override
public String getName() {
@@ -208,51 +208,75 @@ public class TestLoadBalancerDrainingValve {
}
@Override
public String getDomain() {
- return domain;
+ return attributes.get(Constants.COOKIE_DOMAIN_ATTR);
}
@Override
public void setDomain(String domain) {
- this.domain = domain;
+ attributes.put(Constants.COOKIE_DOMAIN_ATTR, domain);
}
@Override
public String getPath() {
- return path;
+ return attributes.get(Constants.COOKIE_PATH_ATTR);
}
@Override
public void setPath(String path) {
- this.path = path;
+ attributes.put(Constants.COOKIE_PATH_ATTR, path);
}
@Override
public String getComment() {
- return comment;
+ return attributes.get(Constants.COOKIE_COMMENT_ATTR);
}
@Override
public void setComment(String comment) {
- this.comment = comment;
+ attributes.put(Constants.COOKIE_COMMENT_ATTR, comment);
}
@Override
public boolean isHttpOnly() {
- return httpOnly;
+ String httpOnly = getAttribute(Constants.COOKIE_HTTP_ONLY_ATTR);
+ if (httpOnly == null) {
+ return false;
+ }
+ return Boolean.parseBoolean(httpOnly);
}
@Override
public void setHttpOnly(boolean httpOnly) {
- this.httpOnly = httpOnly;
+ setAttribute(Constants.COOKIE_HTTP_ONLY_ATTR, Boolean.toString(httpOnly));
}
@Override
public boolean isSecure() {
- return secure;
+ String secure = getAttribute(Constants.COOKIE_SECURE_ATTR);
+ if (secure == null) {
+ return false;
+ }
+ return Boolean.parseBoolean(secure);
}
@Override
public void setSecure(boolean secure) {
- this.secure = secure;
+ setAttribute(Constants.COOKIE_SECURE_ATTR, Boolean.toString(secure));
}
@Override
public int getMaxAge() {
- return maxAge;
+ String maxAge = getAttribute(Constants.COOKIE_MAX_AGE_ATTR);
+ if (maxAge == null) {
+ return -1;
+ }
+ return Integer.parseInt(maxAge);
}
@Override
public void setMaxAge(int maxAge) {
- this.maxAge = maxAge;
+ setAttribute(Constants.COOKIE_MAX_AGE_ATTR, Integer.toString(maxAge));
+ }
+ @Override
+ public void setAttribute(String name, String value) {
+ attributes.put(name, value);
+ }
+ @Override
+ public String getAttribute(String name) {
+ return attributes.get(name);
+ }
+ @Override
+ public Map<String, String> getAttributes() {
+ return Collections.unmodifiableMap(attributes);
}
}
diff --git a/test/org/apache/tomcat/unittest/TesterSessionCookieConfig.java b/test/org/apache/tomcat/unittest/TesterSessionCookieConfig.java
index 7994752..afae9b1 100644
--- a/test/org/apache/tomcat/unittest/TesterSessionCookieConfig.java
+++ b/test/org/apache/tomcat/unittest/TesterSessionCookieConfig.java
@@ -16,6 +16,8 @@
*/
package org.apache.tomcat.unittest;
+import java.util.Map;
+
import jakarta.servlet.SessionCookieConfig;
public class TesterSessionCookieConfig implements SessionCookieConfig {
@@ -90,4 +92,19 @@ public class TesterSessionCookieConfig implements SessionCookieConfig {
public int getMaxAge() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void setAttribute(String name, String value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java b/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
index a2c79e2..fa223aa 100644
--- a/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
+++ b/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
@@ -537,6 +537,96 @@ public class TestWebXml {
Assert.assertEquals(StandardCharsets.ISO_8859_1, securityCollection.getCharset());
}
}
+ }
+
+
+ @Test
+ public void testMergeSessionCookieConfig01() {
+ WebXml main = new WebXml();
+ WebXml fragmentA = new WebXml();
+ WebXml fragmentB = new WebXml();
+
+ fragmentA.getSessionConfig().setCookieHttpOnly("true");
+ fragmentB.getSessionConfig().setCookieSecure("true");
+
+ Set<WebXml> fragments = new HashSet<>();
+ fragments.add(fragmentA);
+ fragments.add(fragmentB);
+
+ Assert.assertTrue(main.merge(fragments));
+ Assert.assertEquals(Boolean.TRUE, main.getSessionConfig().getCookieHttpOnly());
+ Assert.assertEquals(Boolean.TRUE, main.getSessionConfig().getCookieSecure());
+ }
+
+
+ @Test
+ public void testMergeSessionCookieConfig02() {
+ WebXml main = new WebXml();
+ WebXml fragmentA = new WebXml();
+ WebXml fragmentB = new WebXml();
+
+ fragmentA.getSessionConfig().setCookieHttpOnly("true");
+ fragmentB.getSessionConfig().setCookieHttpOnly("false");
+
+ Set<WebXml> fragments = new HashSet<>();
+ fragments.add(fragmentA);
+ fragments.add(fragmentB);
+
+ Assert.assertFalse(main.merge(fragments));
+ }
+
+
+ @Test
+ public void testMergeSessionCookieConfig03() {
+ WebXml main = new WebXml();
+ WebXml fragmentA = new WebXml();
+ WebXml fragmentB = new WebXml();
+
+ main.getSessionConfig().setCookieHttpOnly("false");
+ fragmentA.getSessionConfig().setCookieHttpOnly("true");
+ fragmentB.getSessionConfig().setCookieSecure("true");
+
+ Set<WebXml> fragments = new HashSet<>();
+ fragments.add(fragmentA);
+ fragments.add(fragmentB);
+
+ Assert.assertTrue(main.merge(fragments));
+ Assert.assertEquals(Boolean.FALSE, main.getSessionConfig().getCookieHttpOnly());
+ Assert.assertEquals(Boolean.TRUE, main.getSessionConfig().getCookieSecure());
+ }
+
+
+ @Test
+ public void testMergeSessionCookieConfig04() {
+ WebXml main = new WebXml();
+ WebXml fragmentA = new WebXml();
+ WebXml fragmentB = new WebXml();
+
+ fragmentA.getSessionConfig().setCookieAttribute("aaa", "bbb");
+ fragmentB.getSessionConfig().setCookieAttribute("AAA", "bbb");
+
+ Set<WebXml> fragments = new HashSet<>();
+ fragments.add(fragmentA);
+ fragments.add(fragmentB);
+
+ Assert.assertTrue(main.merge(fragments));
+ Assert.assertEquals("bbb", main.getSessionConfig().getCookieAttribute("aAa"));
+ }
+
+
+ @Test
+ public void testMergeSessionCookieConfig05() {
+ WebXml main = new WebXml();
+ WebXml fragmentA = new WebXml();
+ WebXml fragmentB = new WebXml();
+
+ fragmentA.getSessionConfig().setCookieAttribute("aaa", "bBb");
+ fragmentB.getSessionConfig().setCookieAttribute("AAA", "bbb");
+
+ Set<WebXml> fragments = new HashSet<>();
+ fragments.add(fragmentA);
+ fragments.add(fragmentB);
+ Assert.assertFalse(main.merge(fragments));
}
}
diff --git a/test/webapp/WEB-INF/web.xml b/test/webapp/WEB-INF/web.xml
index b1f30f0..43eb0f1 100644
--- a/test/webapp/WEB-INF/web.xml
+++ b/test/webapp/WEB-INF/web.xml
@@ -327,4 +327,12 @@
<mapped-name>Bug53465MappedName</mapped-name>
</env-entry>
+ <session-config>
+ <cookie-config>
+ <attribute>
+ <attribute-name>aaa</attribute-name>
+ <attribute-value>bbb</attribute-value>
+ </attribute>
+ </cookie-config>
+ </session-config>
</web-app>
\ No newline at end of file
diff --git a/test/webapp/jsp/session.jsp b/test/webapp/jsp/session.jsp
new file mode 100644
index 0000000..87ec0c3
--- /dev/null
+++ b/test/webapp/jsp/session.jsp
@@ -0,0 +1,18 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+<%@ page session="true" %>
+<p>OK</p>
\ No newline at end of file
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 0648e73..febc140 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -117,6 +117,11 @@
recent changes updates for Servlet 6.0 in the Jakarta Servlet
specification project. (markt)
</scode>
+ <add>
+ Add support for setting generic attributes for session cookies. This
+ aligns Apache Tomcat with recent changes in the Jakarta Servlet
+ specification project. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org