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 <ma...@apache.org>
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: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to