Author: kpro...@google.com Date: Wed May 20 06:17:16 2009 New Revision: 5437
Modified: trunk/user/src/com/google/gwt/user/client/Cookies.java trunk/user/test/com/google/gwt/user/client/CookieTest.java Log: Addresses public issue 3566: Offer option to not decode cookies Addresses public issue 1574: RFE: Provide a Cookies.removeCookie(name, path) method Patch by: kprobst Review by: jat, jwg Modified: trunk/user/src/com/google/gwt/user/client/Cookies.java ============================================================================== --- trunk/user/src/com/google/gwt/user/client/Cookies.java (original) +++ trunk/user/src/com/google/gwt/user/client/Cookies.java Wed May 20 06:17:16 2009 @@ -40,10 +40,34 @@ static String rawCookies; /** + * Flag that indicates whether cookies should be URIencoded (when set) and + * URIdecoded (when retrieved). Defaults to URIencoding. + */ + private static boolean uriEncoding = true; + + /** + * Gets the URIencode flag + */ + public static boolean getUriEncode() { + return uriEncoding; + } + + /** + * Updates the URIencode flag and empties the cached cookies set + */ + public static void setUriEncode(boolean encode) { + if (encode != uriEncoding) { + uriEncoding = encode; + cachedCookies = null; + } + } + + /** * Gets the cookie associated with the given name. * * @param name the name of the cookie to be retrieved - * @return the cookie's value, or <code>null</code> if the cookie doesn't exist + * @return the cookie's value, or <code>null</code> if the cookie doesn't + * exist */ public static String getCookie(String name) { Map<String, String> cookiesMap = ensureCookies(); @@ -64,9 +88,40 @@ * * @param name the name of the cookie to be removed */ - public static native void removeCookie(String name) /*-{ - $doc.cookie = name + "=;expires=Fri, 02-Jan-1970 00:00:00 GMT"; - }-*/; + public static void removeCookie(String name) { + if (uriEncoding) { + uriEncode(name); + } + removeCookieNative(name); + } + + /** + * Native method to remove a cookie + */ + private static native void removeCookieNative(String name) /*-{ + $doc.cookie = name + "=;expires=Fri, 02-Jan-1970 00:00:00 GMT"; + }-*/; + + /** + * Removes the cookie associated with the given name. + * + * @param name the name of the cookie to be removed + * @param path the path to be associated with this cookie (which should match + * the path given in {...@link #setCookie}) + */ + public static void removeCookie(String name, String path) { + if (uriEncoding) { + uriEncode(name); + } + removeCookieNative(name, path); + } + + /** + * Native method to remove a cookie with a path + */ + public static native void removeCookieNative(String name, String path) /*-{ + $doc.cookie = name + "=;path=" + path + ";expires=Fri, 02-Jan-1970 00:00:00 GMT"; + }-*/; /** * Sets a cookie. The cookie will expire when the current browser session is @@ -76,7 +131,7 @@ * @param value the cookie's value */ public static void setCookie(String name, String value) { - setCookieImpl(name, value, 0, null, null, false); + setCookie(name, value, null, null, null, false); } /** @@ -91,49 +146,97 @@ } /** - * Sets a cookie. + * Sets a cookie. If uriEncoding is false, it checks the validity of name and + * value. Name: Must conform to RFC 2965. Not allowed: = , ; white space. Also + * can't begin with $. Value: No = or ; * * @param name the cookie's name * @param value the cookie's value * @param expires when the cookie expires * @param domain the domain to be associated with this cookie * @param path the path to be associated with this cookie - * @param secure <code>true</code> to make this a secure cookie (that is, - * only accessible over an SSL connection) + * @param secure <code>true</code> to make this a secure cookie (that is, only + * accessible over an SSL connection) */ public static void setCookie(String name, String value, Date expires, String domain, String path, boolean secure) { - setCookieImpl(name, value, (expires == null) ? 0 : expires.getTime(), domain, path, secure); + if (uriEncoding) { + uriEncode(name); + uriEncode(value); + } else if (!isValidCookieName(name) || !isValidCookieValue(value)) { + throw new IllegalArgumentException("Illegal cookie format."); + } + setCookieImpl(name, value, (expires == null) ? 0 : expires.getTime(), + domain, path, secure); + } + + /* + * Checks whether a cookie name is valid: can't contain =;, or whitespace. + * Can't begin with $. + * + * @param name the cookie's name + */ + private static boolean isValidCookieName(String name) { + if (uriEncoding) { + // check not necessary + return true; + } else if (name.contains("=") || name.contains(";") || name.contains(",") + || name.startsWith("$") || name.matches(".*\\s+.*")) { + return false; + } else + return true; } + /* + * Checks whether a cookie value is valid: can't contain = or ; + * + * @param value the cookie's value + */ + private static boolean isValidCookieValue(String value) { + if (uriEncoding) { + // check not necessary + return true; + } + if (value.contains("=") || value.contains(";")) { + return false; + } else + return true; + } + + private static native String uriEncode(String s) /*-{ + return encodeURIComponent(s); + }-*/; + static native void loadCookies(HashMap<String, String> m) /*-{ - var docCookie = $doc.cookie; - if (docCookie && docCookie != '') { - var crumbs = docCookie.split('; '); - for (var i = 0; i < crumbs.length; ++i) { - var name, value; - var eqIdx = crumbs[i].indexOf('='); - if (eqIdx == -1) { - name = crumbs[i]; - value = ''; - } else { - name = crumbs[i].substring(0, eqIdx); - value = crumbs[i].substring(eqIdx + 1); + var docCookie = $doc.cookie; + if (docCookie && docCookie != '') { + var crumbs = docCookie.split('; '); + for (var i = 0; i < crumbs.length; ++i) { + var name, value; + var eqIdx = crumbs[i].indexOf('='); + if (eqIdx == -1) { + name = crumbs[i]; + value = ''; + } else { + name = crumbs[i].substring(0, eqIdx); + value = crumbs[i].substring(eqIdx + 1); + } + if (@com.google.gwt.user.client.Cookies::uriEncoding) { + try { + name = decodeURIComponent(name); + } catch (e) { + // ignore error, keep undecoded name + } + try { + value = decodeURIComponent(value); + } catch (e) { + // ignore error, keep undecoded value + } + } + m...@java.util.map::put(Ljava/lang/Object;Ljava/lang/Object;)(name,value); + } } - try { - name = decodeURIComponent(name); - } catch (e) { - // ignore error, keep undecoded name - } - try { - value = decodeURIComponent(value); - } catch (e) { - // ignore error, keep undecoded value - } - m...@java.util.map::put(Ljava/lang/Object;Ljava/lang/Object;)(name,value); - } - } - }-*/; + }-*/; private static HashMap<String, String> ensureCookies() { if (cachedCookies == null || needsRefresh()) { @@ -144,31 +247,31 @@ } private static native boolean needsRefresh() /*-{ - var docCookie = $doc.cookie; - - // Check to see if cached cookies need to be invalidated. - if (docCookie != @com.google.gwt.user.client.Cookies::rawCookies) { - @com.google.gwt.user.client.Cookies::rawCookies = docCookie; - return true; - } else { - return false; - } - }-*/; + var docCookie = $doc.cookie; + + // Check to see if cached cookies need to be invalidated. + if (docCookie != @com.google.gwt.user.client.Cookies::rawCookies) { + @com.google.gwt.user.client.Cookies::rawCookies = docCookie; + return true; + } else { + return false; + } + }-*/; private static native void setCookieImpl(String name, String value, double expires, String domain, String path, boolean secure) /*-{ - var c = encodeURIComponent(name) + '=' + encodeURIComponent(value); - if ( expires ) - c += ';expires=' + (new Date(expires)).toGMTString(); - if (domain) - c += ';domain=' + domain; - if (path) - c += ';path=' + path; - if (secure) - c += ';secure'; + var c = name + '=' + value; + if ( expires ) + c += ';expires=' + (new Date(expires)).toGMTString(); + if (domain) + c += ';domain=' + domain; + if (path) + c += ';path=' + path; + if (secure) + c += ';secure'; - $doc.cookie = c; - }-*/; + $doc.cookie = c; + }-*/; private Cookies() { } Modified: trunk/user/test/com/google/gwt/user/client/CookieTest.java ============================================================================== --- trunk/user/test/com/google/gwt/user/client/CookieTest.java (original) +++ trunk/user/test/com/google/gwt/user/client/CookieTest.java Wed May 20 06:17:16 2009 @@ -79,7 +79,7 @@ assertEquals(Cookies.getCookie("shouldNotExpire"), "forever"); Cookies.removeCookie("shouldNotExpire"); assertNull(Cookies.getCookie("shouldNotExpire")); - + // Finish the test finishTest(); } @@ -87,21 +87,21 @@ timer.schedule(5010); delayTestFinish(6 * 1000); } - + /** * Test that removing cookies works correctly. */ public void testRemoveCookie() { // First clear all cookies clearCookies(); - + // Set a few cookies Cookies.setCookie("test1", "value1"); Cookies.setCookie("test2", "value2"); Cookies.setCookie("test3", "value3"); Collection<String> cookies = Cookies.getCookieNames(); assertEquals(3, cookies.size()); - + // Remove a cookie Cookies.removeCookie("test2"); assertEquals("value1", Cookies.getCookie("test1")); @@ -121,15 +121,147 @@ assertEquals(null, Cookies.getCookie("test3")); cookies = Cookies.getCookieNames(); assertEquals(0, cookies.size()); + + // Add/remove URI encoded cookies + Cookies.setCookie("test1-test1", "value1 value1"); + Cookies.removeCookie("test1-test1"); + cookies = Cookies.getCookieNames(); + assertEquals(0, cookies.size()); + + // Add/remove cookies that are not URI encoded + Cookies.setUriEncode(false); + Cookies.setCookie("test1+test1", "value1+value1"); + Cookies.removeCookie("test1+test1"); + cookies = Cookies.getCookieNames(); + assertEquals(0, cookies.size()); + + // Make sure unencoded cookies with bogus format are not added + try { + Cookies.setCookie("test1=test1", "value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + try { + Cookies.setCookie("test1;test1", "value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + try { + Cookies.setCookie("test1,test1", "value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + try { + Cookies.setCookie("test1 test1", "value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + try { + Cookies.setCookie("$test1", "value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + try { + Cookies.setCookie("test1", "value1;value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + try { + Cookies.setCookie("test1", "value1=value1"); + fail("Should have thrown an IllegalArgumentException for bad cookie format."); + } catch (IllegalArgumentException e) { + // Success. + } + } - + /** * Clear out all existing cookies. */ - private void clearCookies() { + private void clearCookies() { Collection<String> cookies = Cookies.getCookieNames(); for (String cookie : cookies) { Cookies.removeCookie(cookie); } + } + + /** + * Test that removing cookies with a path works correctly. + * + * Note that we do not verify failure to remove a cookie set with a path but + * removed without one as browser behavior differs. + */ + public void testRemoveCookiePath() { + // First clear all cookies + clearCookies(); + + // Test first without UriEncoding + Cookies.setUriEncode(false); + + // Set a few cookies + Cookies.setCookie("test1+test1", "value1", null, null, "/", false); + Cookies.setCookie("test2", "value2+value2", null, null, "/", false); + Cookies.setCookie("test3", "value3", null, null, "/", false); + Collection<String> cookies = Cookies.getCookieNames(); + assertEquals(3, cookies.size()); + + // Remove a cookie + Cookies.removeCookie("test2", "/"); + assertEquals("value1", Cookies.getCookie("test1+test1")); + assertEquals(null, Cookies.getCookie("test2")); + assertEquals("value3", Cookies.getCookie("test3")); + + // Remove another cookie + Cookies.removeCookie("test1+test1", "/"); + assertEquals(null, Cookies.getCookie("test1+test1")); + assertEquals(null, Cookies.getCookie("test2")); + assertEquals("value3", Cookies.getCookie("test3")); + + // Remove last cookie + Cookies.removeCookie("test3", "/"); + assertEquals(null, Cookies.getCookie("test1+test1")); + assertEquals(null, Cookies.getCookie("test2")); + assertEquals(null, Cookies.getCookie("test3")); + cookies = Cookies.getCookieNames(); + assertEquals(0, cookies.size()); + + // First clear all cookies + clearCookies(); + + // Test with UriEncoding + Cookies.setUriEncode(true); + + // Set a few cookies + Cookies.setCookie("test1+test1", "value1", null, null, "/", false); + Cookies.setCookie("test2", "value2+value2", null, null, "/", false); + Cookies.setCookie("test3", "value3", null, null, "/", false); + cookies = Cookies.getCookieNames(); + assertEquals(3, cookies.size()); + + // Remove a cookie + Cookies.removeCookie("test2", "/"); + assertEquals("value1", Cookies.getCookie("test1+test1")); + assertEquals(null, Cookies.getCookie("test2")); + assertEquals("value3", Cookies.getCookie("test3")); + + // Remove another cookie + Cookies.removeCookie("test1+test1", "/"); + assertEquals(null, Cookies.getCookie("test1+test1")); + assertEquals(null, Cookies.getCookie("test2")); + assertEquals("value3", Cookies.getCookie("test3")); + + // Remove last cookie + Cookies.removeCookie("test3", "/"); + assertEquals(null, Cookies.getCookie("test1+test1")); + assertEquals(null, Cookies.getCookie("test2")); + assertEquals(null, Cookies.getCookie("test3")); + cookies = Cookies.getCookieNames(); + assertEquals(0, cookies.size()); } } --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---