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 83fdd8b Implement new generic attribute methods for Cookies
83fdd8b is described below
commit 83fdd8b9662e9a58ba83dc99ce64c3f317e2643b
Author: Mark Thomas <[email protected]>
AuthorDate: Mon May 24 16:31:55 2021 +0100
Implement new generic attribute methods for Cookies
---
java/jakarta/servlet/http/Cookie.java | 140 +++++++++++++++++-----
java/jakarta/servlet/http/LocalStrings.properties | 3 +
test/jakarta/servlet/http/TestCookie.java | 46 +++++++
webapps/docs/changelog.xml | 5 +
4 files changed, 164 insertions(+), 30 deletions(-)
diff --git a/java/jakarta/servlet/http/Cookie.java
b/java/jakarta/servlet/http/Cookie.java
index 084e1e1..8422c65 100644
--- a/java/jakarta/servlet/http/Cookie.java
+++ b/java/jakarta/servlet/http/Cookie.java
@@ -21,9 +21,11 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.BitSet;
+import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
+import java.util.TreeMap;
/**
* Creates a cookie, a small amount of information sent by a servlet to a Web
@@ -56,6 +58,9 @@ import java.util.ResourceBundle;
*/
public class Cookie implements Cloneable, Serializable {
+ private static final String LSTRING_FILE =
"jakarta.servlet.http.LocalStrings";
+ private static final ResourceBundle LSTRINGS =
ResourceBundle.getBundle(LSTRING_FILE);
+
private static final CookieNameValidator validation;
static {
@@ -103,22 +108,22 @@ public class Cookie implements Cloneable, Serializable {
}
}
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 2L;
private final String name;
private String value;
private int version = 0; // ;Version=1 ... means RFC 2109 style
- //
// Attributes encoded in the header's cookie fields.
- //
- private String comment; // ;Comment=VALUE ... describes cookie's use
- private String domain; // ;Domain=VALUE ... domain that sees cookie
- private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
- private String path; // ;Path=VALUE ... URLs that see the cookie
- private boolean secure; // ;Secure ... e.g. use SSL
- private boolean httpOnly; // Not in cookie specs, but supported by browsers
+ private volatile Map<String,String> attributes;
+
+ private static final String COMMENT = "Comment";
+ private static final String DOMAIN = "Domain";
+ private static final String MAX_AGE = "Max-Age";
+ private static final String PATH = "Path";
+ private static final String SECURE = "Secure";
+ private static final String HTTP_ONLY = "HttpOnly";
/**
* Constructs a cookie with a specified name and value.
@@ -153,6 +158,7 @@ public class Cookie implements Cloneable, Serializable {
this.value = value;
}
+
/**
* Specifies a comment that describes a cookie's purpose. The comment is
* useful if the browser presents the cookie to the user. Comments are not
@@ -164,9 +170,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #getComment
*/
public void setComment(String purpose) {
- comment = purpose;
+ setAttributeInternal(COMMENT, purpose);
}
+
/**
* Returns the comment describing the purpose of this cookie, or
* <code>null</code> if the cookie has no comment.
@@ -176,9 +183,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #setComment
*/
public String getComment() {
- return comment;
+ return getAttribute(COMMENT);
}
+
/**
* Specifies the domain within which this cookie should be presented.
* <p>
@@ -194,9 +202,15 @@ public class Cookie implements Cloneable, Serializable {
* @see #getDomain
*/
public void setDomain(String pattern) {
- domain = pattern.toLowerCase(Locale.ENGLISH); // IE allegedly needs
this
+ if (pattern == null) {
+ setAttributeInternal(DOMAIN, null);
+ } else {
+ // IE requires the domain to be lower case (unconfirmed)
+ setAttributeInternal(DOMAIN, pattern.toLowerCase(Locale.ENGLISH));
+ }
}
+
/**
* Returns the domain name set for this cookie. The form of the domain name
* is set by RFC 2109.
@@ -205,9 +219,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #setDomain
*/
public String getDomain() {
- return domain;
+ return getAttribute(DOMAIN);
}
+
/**
* Sets the maximum age of the cookie in seconds.
* <p>
@@ -226,9 +241,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #getMaxAge
*/
public void setMaxAge(int expiry) {
- maxAge = expiry;
+ setAttributeInternal(MAX_AGE, Integer.toString(expiry));
}
+
/**
* Returns the maximum age of the cookie, specified in seconds, By default,
* <code>-1</code> indicating the cookie will persist until browser
@@ -239,9 +255,15 @@ public class Cookie implements Cloneable, Serializable {
* @see #setMaxAge
*/
public int getMaxAge() {
- return maxAge;
+ String maxAge = getAttribute(MAX_AGE);
+ if (maxAge == null) {
+ return -1;
+ } else {
+ return Integer.parseInt(maxAge);
+ }
}
+
/**
* Specifies a path for the cookie to which the client should return the
* cookie.
@@ -260,9 +282,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #getPath
*/
public void setPath(String uri) {
- path = uri;
+ setAttributeInternal(PATH, uri);
}
+
/**
* Returns the path on the server to which the browser returns this cookie.
* The cookie is visible to all subpaths on the server.
@@ -272,9 +295,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #setPath
*/
public String getPath() {
- return path;
+ return getAttribute(PATH);
}
+
/**
* Indicates to the browser whether the cookie should only be sent using a
* secure protocol, such as HTTPS or SSL.
@@ -288,9 +312,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #getSecure
*/
public void setSecure(boolean flag) {
- secure = flag;
+ setAttributeInternal(SECURE, Boolean.toString(flag));
}
+
/**
* Returns <code>true</code> if the browser is sending cookies only over a
* secure protocol, or <code>false</code> if the browser can send cookies
@@ -301,9 +326,10 @@ public class Cookie implements Cloneable, Serializable {
* @see #setSecure
*/
public boolean getSecure() {
- return secure;
+ return Boolean.parseBoolean(getAttribute(SECURE));
}
+
/**
* Returns the name of the cookie. The name cannot be changed after
* creation.
@@ -314,6 +340,7 @@ public class Cookie implements Cloneable, Serializable {
return name;
}
+
/**
* Assigns a new value to a cookie after the cookie is created. If you use
a
* binary value, you may want to use BASE64 encoding.
@@ -332,6 +359,7 @@ public class Cookie implements Cloneable, Serializable {
value = newValue;
}
+
/**
* Returns the value of the cookie.
*
@@ -343,6 +371,7 @@ public class Cookie implements Cloneable, Serializable {
return value;
}
+
/**
* Returns the version of the protocol this cookie complies with. Version 1
* complies with RFC 2109, and version 0 complies with the original cookie
@@ -357,6 +386,7 @@ public class Cookie implements Cloneable, Serializable {
return version;
}
+
/**
* Sets the version of the cookie protocol this cookie complies with.
* Version 0 complies with the original Netscape cookie specification.
@@ -374,6 +404,7 @@ public class Cookie implements Cloneable, Serializable {
version = v;
}
+
/**
* Overrides the standard <code>java.lang.Object.clone</code> method to
* return a copy of this cookie.
@@ -387,6 +418,7 @@ public class Cookie implements Cloneable, Serializable {
}
}
+
/**
* Sets the flag that controls if this cookie will be hidden from scripts
on
* the client side.
@@ -396,9 +428,10 @@ public class Cookie implements Cloneable, Serializable {
* @since Servlet 3.0
*/
public void setHttpOnly(boolean httpOnly) {
- this.httpOnly = httpOnly;
+ setAttributeInternal(HTTP_ONLY, Boolean.toString(httpOnly));
}
+
/**
* Gets the flag that controls if this cookie will be hidden from scripts
on
* the client side.
@@ -408,9 +441,10 @@ public class Cookie implements Cloneable, Serializable {
* @since Servlet 3.0
*/
public boolean isHttpOnly() {
- return httpOnly;
+ return Boolean.parseBoolean(getAttribute(HTTP_ONLY));
}
+
/**
* Sets the value for the given cookie attribute. When a value is set via
* this method, the value returned by the attribute specific getter (if
any)
@@ -419,9 +453,8 @@ public class Cookie implements Cloneable, Serializable {
* @param name Name of attribute to set
* @param value Value of attribute
*
- * @throws IllegalArgumentException If the attribute name is null, contains
- * any characters not permitted foe use in Cookie names or matches
a
- * name reserved by the cookie specification.
+ * @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.
@@ -429,9 +462,41 @@ public class Cookie implements Cloneable, Serializable {
* @since Servlet 5.1
*/
public void setAttribute(String name, String value) {
- // TODO - Servlet 5.1
+ if (name == null) {
+ throw new
IllegalArgumentException(LSTRINGS.getString("cookie.attribute.invalidName.null"));
+ }
+ if (!validation.isToken(name)) {
+ String msg =
LSTRINGS.getString("cookie.attribute.invalidName.notToken");
+ throw new IllegalArgumentException(MessageFormat.format(msg,
name));
+ }
+
+ if (name.equalsIgnoreCase(MAX_AGE)) {
+ if (value == null) {
+ setAttributeInternal(MAX_AGE, null);
+ } else {
+ // Integer.parseInt throws NFE if required
+ setMaxAge(Integer.parseInt(value));
+ }
+ } else {
+ setAttributeInternal(name, value);
+ }
+ }
+
+
+ private void setAttributeInternal(String name, String value) {
+ if (attributes == null) {
+ if (value == null) {
+ return;
+ } else {
+ // Case insensitive keys but retain case used
+ attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ }
+
+ attributes.put(name, value);
}
+
/**
* Obtain the value for a given attribute. Values returned from this method
* must be consistent with the values set and returned by the attribute
@@ -444,13 +509,28 @@ public class Cookie implements Cloneable, Serializable {
* @since Servlet 5.1
*/
public String getAttribute(String name) {
- // TODO - Servlet 5.1
- return null;
+ if (attributes == null) {
+ return null;
+ } else {
+ return attributes.get(name);
+ }
}
+
+ /**
+ * Obtain the Map of attributes and values (excluding version) for this
+ * cookie.
+ *
+ * @return A read-only Map of attributes to values, excluding version.
+ *
+ * @since Servlet 5.1
+ */
public Map<String,String> getAttributes() {
- // TODO - Servlet 5.1
- return null;
+ if (attributes == null) {
+ return Collections.emptyMap();
+ } else {
+ return Collections.unmodifiableMap(attributes);
+ }
}
}
@@ -480,7 +560,7 @@ class CookieNameValidator {
}
}
- private boolean isToken(String possibleToken) {
+ boolean isToken(String possibleToken) {
int len = possibleToken.length();
for (int i = 0; i < len; i++) {
diff --git a/java/jakarta/servlet/http/LocalStrings.properties
b/java/jakarta/servlet/http/LocalStrings.properties
index 3ea5922..4d0e8d8 100644
--- a/java/jakarta/servlet/http/LocalStrings.properties
+++ b/java/jakarta/servlet/http/LocalStrings.properties
@@ -13,6 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+cookie.attribute.invalidName.notToken=Cookie attribute name [{0}] is not valid
as it is not a token
+cookie.attribute.invalidName.null=Cookie attribute names may not be null
+
err.cookie_name_blank=Cookie name may not be null or zero length
err.cookie_name_is_token=Cookie name [{0}] is a reserved token
err.io.indexOutOfBounds=Invalid offset [{0}] and / or length [{1}] specified
for array of size [{2}]
diff --git a/test/jakarta/servlet/http/TestCookie.java
b/test/jakarta/servlet/http/TestCookie.java
index 353004e..7885de5 100644
--- a/test/jakarta/servlet/http/TestCookie.java
+++ b/test/jakarta/servlet/http/TestCookie.java
@@ -134,6 +134,52 @@ public class TestCookie {
Cookie cookie = new Cookie("$Foo", null);
}
+ @Test
+ public void testGetAttributes01() {
+ Cookie cookie = new Cookie("name", "value");
+ Assert.assertEquals(0, cookie.getAttributes().size());
+ }
+
+ @Test
+ public void testMaxAge01() {
+ Cookie cookie = new Cookie("name", "value");
+ Assert.assertEquals(-1, cookie.getMaxAge());
+
+ for (int value : new int[] { Integer.MIN_VALUE, -2, -1, 0, 1, 2,
Integer.MAX_VALUE}) {
+ cookie.setMaxAge(value);
+ Assert.assertEquals(value, cookie.getMaxAge());
+ }
+ }
+
+ @Test
+ public void testAttribute01() {
+ Cookie cookie = new Cookie("name", "value");
+ cookie.setAttribute("aaa", "bbb");
+ Assert.assertEquals("bbb", cookie.getAttribute("aAa"));
+ cookie.setAttribute("aaa", "");
+ Assert.assertEquals("", cookie.getAttribute("aAa"));
+ cookie.setAttribute("aaa", null);
+ Assert.assertNull(cookie.getAttribute("aAa"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAttributeInvalid01() {
+ Cookie cookie = new Cookie("name", "value");
+ cookie.setAttribute("a<aa", "bbb");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAttributeInvalid02() {
+ Cookie cookie = new Cookie("name", "value");
+ cookie.setAttribute(null, "bbb");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testAttributeInvalid03() {
+ Cookie cookie = new Cookie("name", "value");
+ cookie.setAttribute("Max-Age", "bbb");
+ }
+
public static void checkCharInName(CookieNameValidator validator, BitSet
allowed) {
for (char ch = 0; ch < allowed.size(); ch++) {
boolean expected = allowed.get(ch);
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 2b2a716..1eda6eb 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -147,6 +147,11 @@
before attempting conversion to String. Pull request provided by
tianshuang. (markt)
</fix>
+ <add>
+ Implement the new <code>Cookie</code> methods
+ <code>setAttribute()</code>, <code>getAttribute()</code> and
+ <code>getAttributes()</code> introduced in Servlet 5.1. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]