Index: modules/luni/src/test/java/org/apache/harmony/luni/tests/java/net/HttpCookieTest.java =================================================================== --- modules/luni/src/test/java/org/apache/harmony/luni/tests/java/net/HttpCookieTest.java (revision 0) +++ modules/luni/src/test/java/org/apache/harmony/luni/tests/java/net/HttpCookieTest.java (revision 0) @@ -0,0 +1,931 @@ +/* + * 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 org.apache.harmony.luni.tests.java.net; + +import java.net.HttpCookie; +import java.util.List; +import java.util.Locale; + +import junit.framework.TestCase; + +public class HttpCookieTest extends TestCase { + private Locale locale; + + /** + * @tests java.net.HttpCookie(String, String). + * + * @since 1.6 + */ + public void test_HttpCookie_LString_LString() { + assertNotNull(new HttpCookie("harmony_6", "test,sem")); + assertNotNull(new HttpCookie("harmony_6", null)); + assertNotNull(new HttpCookie("harmony ", null)); + assertEquals("harmony", new HttpCookie("harmony ", null).getName()); + + constructHttpCookie("", null); + + String value = "value"; + constructHttpCookie("", value); + + constructHttpCookie("harmony,", value); + constructHttpCookie("harmony;", value); + constructHttpCookie("$harmony", value); + constructHttpCookie("n\tame", value); + constructHttpCookie("n\rame", value); + constructHttpCookie("n\r\name", value); + constructHttpCookie("Comment", value); + constructHttpCookie("CommentURL", value); + constructHttpCookie("Domain", value); + constructHttpCookie("Discard", value); + constructHttpCookie("Max-Age", value); + constructHttpCookie(" Path ", value); + constructHttpCookie("Port ", value); + constructHttpCookie("SeCure", value); + constructHttpCookie("VErsion", value); + constructHttpCookie("expires", value); + constructHttpCookie("na\u0085me", value); + constructHttpCookie("\u2028me", value); + constructHttpCookie("na\u2029me", value); + + try { + new HttpCookie(null, value); + fail("Should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + + try { + new HttpCookie("\u007f", value); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + + HttpCookie cookie = new HttpCookie("harmony!", null); + assertEquals("harmony!", cookie.getName()); + + cookie = new HttpCookie("harmon$y", null); + assertEquals("harmon$y", cookie.getName()); + + } + + private static void constructHttpCookie(String name, String value) { + try { + new HttpCookie(name, value); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.net.HttpCookie#domainMatches(String, String). + * + * @since 1.6 + */ + public void test_DomainMatches() { + + /* + * Rule 1: A host isn't in a domain (RFC 2965 sec. 3.3.2) if: The value + * for the Domain attribute contains no embedded dots, and the value is + * not .local. + */ + boolean match = HttpCookie.domainMatches("hostname", "hostname"); + assertFalse(match); + + match = HttpCookie.domainMatches(".com", "test.com"); + assertFalse(match); + + match = HttpCookie.domainMatches(".com.", "test.com"); + assertFalse(match); + + // During comparison, host name is transformed to effective host name + // first. + match = HttpCookie.domainMatches(".local", "hostname"); + assertTrue(match); + + /* + * Rule 3: The request-host is a HDN (not IP address) and has the form + * HD, where D is the value of the Domain attribute, and H is a string + * that contains one or more dots. + */ + match = HttpCookie.domainMatches(".c.d", "a.b.c.d"); + assertFalse(match); + + match = HttpCookie.domainMatches(".foo.com", "y.x.foo.com"); + assertFalse(match); + + match = HttpCookie.domainMatches(".foo.com", "x.foo.com"); + assertTrue(match); + + match = HttpCookie.domainMatches(".local", "hostname.local"); + assertTrue(match); + + match = HttpCookie.domainMatches(".ajax.com", "a.ajax.com"); + assertTrue(match); + + match = HttpCookie.domainMatches(".ajax.com", "a.AJAX.com"); + assertTrue(match); + + match = HttpCookie.domainMatches("...", "test..."); + assertTrue(match); + + match = HttpCookie.domainMatches(".ajax.com", "b.a.AJAX.com"); + assertFalse(match); + + match = HttpCookie.domainMatches(".a", "b.a"); + assertFalse(match); + + // when either parameter is null + match = HttpCookie.domainMatches(".ajax.com", null); + assertFalse(match); + + match = HttpCookie.domainMatches(null, null); + assertFalse(match); + + match = HttpCookie.domainMatches(null, "b.a.AJAX.com"); + assertFalse(match); + + // TODO RI bug? The effective hostname is hostname.local which is string + // equal with hostname.local. So they should be domain match. + match = HttpCookie.domainMatches("hostname.local", "hostname"); + assertTrue(match); + } + + /** + * @tests java.net.HttpCookie#getVersion(), setVersion(int). + * + * @since 1.6 + */ + public void test_Get_SetVersion() { + HttpCookie cookie = new HttpCookie("name", "value"); + assertEquals(1, cookie.getVersion()); + cookie.setVersion(0); + assertEquals(0, cookie.getVersion()); + cookie.setVersion(1); + assertEquals(1, cookie.getVersion()); + + try { + cookie.setVersion(-1); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + cookie.setVersion(2); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.net.HttpCookie#getValue(), setValue(String) + * + * @since 1.6 + */ + public void test_Get_SetValue() { + HttpCookie cookie = new HttpCookie("name", "value"); + assertEquals("value", cookie.getValue()); + cookie.setValue("newValue"); + assertEquals("newValue", cookie.getValue()); + + cookie.setValue(null); + assertNull(cookie.getValue()); + + cookie.setValue("na\u64DEme"); + assertEquals("na\u64DEme", cookie.getValue()); + cookie.setVersion(0); + cookie.setValue("{(new value, 11)}"); + assertEquals("{(new value, 11)}", cookie.getValue()); + } + + /** + * @tests java.net.HttpCookie#getName() + * + * @since 1.6 + */ + public void test_GetName() { + HttpCookie cookie = new HttpCookie("testName", "value"); + assertEquals("testName", cookie.getName()); + } + + /** + * @tests java.net.HttpCookie#getSecure(), setSecure(boolean) + * + * @since 1.6 + */ + public void test_Get_SetSecure() { + HttpCookie cookie = new HttpCookie("testName", "value"); + assertFalse(cookie.getSecure()); + cookie.setVersion(0); + assertFalse(cookie.getSecure()); + + cookie.setSecure(true); + assertTrue(cookie.getSecure()); + cookie.setSecure(false); + cookie.setVersion(1); + assertFalse(cookie.getSecure()); + } + + /** + * @tests java.net.HttpCookie#getPath(), setPath(String) + * + * @since 1.6 + */ + public void test_Get_SetPath() { + HttpCookie cookie = new HttpCookie("name", "test new value"); + assertNull(cookie.getPath()); + + cookie.setPath("{}() test,; 43!@"); + assertEquals("{}() test,; 43!@", cookie.getPath()); + + cookie.setPath(" test"); + assertEquals(" test", cookie.getPath()); + + cookie.setPath("\u63DF\u64DE"); + cookie.setDomain("test"); + assertEquals("\u63DF\u64DE", cookie.getPath()); + } + + /** + * @tests java.net.HttpCookie#getMaxAge(), setMaxAge(long) + * + * @since 1.6 + */ + public void test_Get_SetMaxAge() { + HttpCookie cookie = new HttpCookie("name", "test new value"); + assertEquals(-1, cookie.getMaxAge()); + + cookie.setMaxAge(Long.MAX_VALUE); + assertEquals(Long.MAX_VALUE, cookie.getMaxAge()); + + cookie.setMaxAge(Long.MIN_VALUE); + cookie.setDiscard(false); + assertEquals(Long.MIN_VALUE, cookie.getMaxAge()); + } + + /** + * @tests java.net.HttpCookie#getDomain(), setDomain(String) + * + * @since 1.6 + */ + public void test_Get_SetDomain() { + HttpCookie cookie = new HttpCookie("name", "test new value"); + assertNull(cookie.getDomain()); + + cookie.setDomain("a.b.d.c.com."); + assertEquals("a.b.d.c.com.", cookie.getDomain()); + + cookie.setDomain(" a.b.d.c.com. "); + assertEquals(" a.b.d.c.com. ", cookie.getDomain()); + + cookie.setPath("temp/subTemp"); + cookie.setDomain("xy.foo.bar.de.edu"); + assertEquals("xy.foo.bar.de.edu", cookie.getDomain()); + } + + /** + * @tests java.net.HttpCookie#getPortlist(), setPortlist(String) + * + * @since 1.6 + */ + public void test_Get_SetPortlist() { + HttpCookie cookie = new HttpCookie("cookieName", "cookieName value"); + assertNull(cookie.getPortlist()); + + cookie.setPortlist("80,23,20"); + assertEquals("80,23,20", cookie.getPortlist()); + cookie.setPortlist("abcdefg1234567"); + cookie.setValue("cookie value again"); + assertEquals("abcdefg1234567", cookie.getPortlist()); + } + + /** + * @tests java.net.HttpCookie#getDiscard(), setDiscard(boolean) + * + * @since 1.6 + */ + public void test_Get_SetDiscard() { + HttpCookie cookie = new HttpCookie("cookie'sName", + "cookie's Test value"); + assertFalse(cookie.getDiscard()); + + cookie.setDiscard(true); + assertTrue(cookie.getDiscard()); + cookie.setDiscard(false); + cookie.setMaxAge(-1); + assertFalse(cookie.getDiscard()); + } + + /** + * @tests java.net.HttpCookie#getCommentURL(), setCommentURL(String) + * + * @since 1.6 + */ + public void test_Get_SetCommentURL() { + HttpCookie cookie = new HttpCookie("cookie'\"sName", + "cookie's Test value"); + assertNull(cookie.getCommentURL()); + + cookie.setCommentURL("http://www.test.com"); + assertEquals("http://www.test.com", cookie.getCommentURL()); + + cookie.setCommentURL("schema://harmony.test.org"); + cookie.setComment("just a comment"); + assertEquals("schema://harmony.test.org", cookie.getCommentURL()); + } + + /** + * @tests java.net.HttpCookie#getComment(), setComment(String) + * + * @since 1.6 + */ + public void test_Get_SetComment() { + HttpCookie cookie = new HttpCookie("cookie'\"sName?", + "cookie's Test??!@# value"); + assertNull(cookie.getComment()); + + cookie.setComment(""); + assertEquals("", cookie.getComment()); + + cookie.setComment("cookie''s @#$!&*()"); + cookie.setVersion(0); + assertEquals("cookie''s @#$!&*()", cookie.getComment()); + } + + /** + * @tests java.net.HttpCookie#hasExpired() + * + * @since 1.6 + */ + public void test_HasExpired() { + HttpCookie cookie = new HttpCookie("cookie'\"sName123456", + "cookie's Test?()!@# value"); + assertFalse(cookie.hasExpired()); + + cookie.setMaxAge(0); + assertTrue(cookie.hasExpired()); + + cookie.setMaxAge(Long.MAX_VALUE); + cookie.setVersion(0); + assertFalse(cookie.hasExpired()); + + cookie.setMaxAge(Long.MIN_VALUE); + cookie.setDiscard(false); + assertTrue(cookie.hasExpired()); + + cookie.setDiscard(true); + cookie.setMaxAge(-1); + assertFalse(cookie.hasExpired()); + } + + /** + * @tests java.net.HttpCookie#equals() + * + * @since 1.6 + */ + public void test_Equals() { + Object obj = new Object(); + HttpCookie cookie = new HttpCookie("test", "testValue"); + HttpCookie cookie2 = new HttpCookie("TesT", "TEstValue"); + + assertFalse(cookie.equals(obj)); + assertFalse(cookie.equals(null)); + assertTrue(cookie2.equals(cookie)); + assertTrue(cookie.equals(cookie2)); + assertTrue(cookie.equals(cookie)); + + cookie.setDomain(" test"); + cookie2.setDomain("test"); + assertFalse(cookie.equals(cookie2)); + cookie.setDomain("TEST"); + assertTrue(cookie.equals(cookie2)); + + cookie.setPath("temp\\e"); + assertFalse(cookie.equals(cookie2)); + cookie2.setPath("temp\\E"); + assertFalse(cookie.equals(cookie2)); + + cookie.setDiscard(true); + cookie.setMaxAge(-1234); + cookie2.setPath("temp\\e"); + assertTrue(cookie.equals(cookie2)); + } + + /** + * @tests java.net.HttpCookie#clone() + * + * @since 1.6 + */ + public void test_Clone() { + HttpCookie cookie = new HttpCookie("test", "testValue"); + cookie.setMaxAge(33l); + cookie.setComment("test comment"); + HttpCookie cloneCookie = (HttpCookie) cookie.clone(); + assertNotSame(cloneCookie, cookie); + assertEquals("test", cloneCookie.getName()); + assertEquals(33l, cloneCookie.getMaxAge()); + assertEquals("test comment", cloneCookie.getComment()); + } + + /** + * @tests java.net.HttpCookie#toString() + * + * @since 1.6 + */ + public void test_ToString() { + HttpCookie cookie = new HttpCookie("test", "testValue"); + cookie.setComment("ABCd"); + cookie.setCommentURL("\u63DF"); + cookie.setDomain(".B.com"); + cookie.setDiscard(true); + cookie.setMaxAge(Integer.MAX_VALUE); + cookie.setPath("temp/22RuTh"); + cookie.setPortlist("80.562Ab"); + cookie.setSecure(true); + cookie.setVersion(1); + + assertEquals( + "test=\"testValue\";$Path=\"temp/22RuTh\";$Domain=\".b.com\";$Port=\"80.562Ab\"", + cookie.toString()); + + cookie.setPath(null); + assertEquals( + "test=\"testValue\";$Domain=\".b.com\";$Port=\"80.562Ab\"", + cookie.toString()); + cookie.setComment(null); + assertEquals( + "test=\"testValue\";$Domain=\".b.com\";$Port=\"80.562Ab\"", + cookie.toString()); + cookie.setPortlist(null); + assertEquals("test=\"testValue\";$Domain=\".b.com\"", cookie.toString()); + cookie.setDomain(null); + assertEquals("test=\"testValue\"", cookie.toString()); + + cookie.setVersion(0); + cookie.setPortlist("80,8000"); + assertEquals("test=testValue", cookie.toString()); + } + + /** + * @tests java.net.HttpCookie#hashCode() + * + * @since 1.6 + */ + public void test_HashCode() { + HttpCookie cookie = new HttpCookie("nAmW_1", "value_1"); + assertEquals(-1052814577, cookie.hashCode()); + + cookie.setDomain("a.b.c.de"); + assertEquals(1222695220, cookie.hashCode()); + + cookie.setPath("3kmxiq;1"); + assertEquals(-675006347, cookie.hashCode()); + cookie.setPath("3KmxiQ;1"); + assertEquals(989616181, cookie.hashCode()); + + cookie.setValue("Vw0,22_789"); + assertEquals(989616181, cookie.hashCode()); + cookie.setComment("comment"); + assertEquals(989616181, cookie.hashCode()); + + cookie.setDomain(""); + assertEquals(-1285893616, cookie.hashCode()); + } + + /** + * @tests java.net.HttpCookie#parse(String) for exception cases + * + * @since 1.6 + */ + public void test_Parse_exception() { + try { + HttpCookie.parse(null); + fail("Should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + /* + * Please note that Netscape draft specification does not fully conform + * to the HTTP header format. Netscape draft does not specify whether + * multiple cookies may be sent in one header. Hence, comma character + * may be present in unquoted cookie value or unquoted parameter value. + * Refer to + * http://jakarta.apache.org/commons/httpclient/apidocs/org/apache/commons/httpclient/cookie/NetscapeDraftSpec.html#parse(java.lang.String,%20int,%20java.lang.String,%20boolean,%20java.lang.String) + * + */ + // violates the cookie specification's syntax + checkInvalidCookie("invalid cookie name"); + checkInvalidCookie("Set-Cookie2:"); + checkInvalidCookie("name"); + checkInvalidCookie("$name="); + checkInvalidCookie("Set-Cookie2:$name="); + checkInvalidCookie("Set-Cookie:$"); + checkInvalidCookie("Set-Cookie"); + checkInvalidCookie("Set-Cookie2:test==wwlala;Discard;Patth=/temp"); + checkInvalidCookie("Set-Cookie2:test==wwlala;Version=2"); + + // cookie name contains llegal characters + checkInvalidCookie("Set-Cookie:n,ame="); + checkInvalidCookie("Set-Cookie2:n\name="); + checkInvalidCookie("Set-Cookie2:n,ame="); + checkInvalidCookie("Set-Cookie2:n\tame="); + checkInvalidCookie("Set-Cookie2:n\rame="); + checkInvalidCookie("Set-Cookie2:n\r\name="); + checkInvalidCookie("Set-Cookie2:na\u0085me="); + checkInvalidCookie("Set-Cookie2:na\u2028me="); + checkInvalidCookie("Set-Cookie2:na\u2029me="); + checkInvalidCookie("Set-Cookie2:="); + checkInvalidCookie("Set-Cookie2:name=tes,t"); + + // 'CommentURL' is one of the tokens reserved, case-insensitive + checkInvalidCookie("Set-Cookie2:COmmentURL=\"lala\""); + + // check value + checkInvalidCookie("Set-Cookie2:val,ue"); + checkInvalidCookie("Set-Cookie2:name=test;comMent=sent,ence"); + checkInvalidCookie("Set-Cookie2:name=test;comMentUrL=u,rl"); + checkInvalidCookie("Set-Cookie2:name=test;Discard=fa,lse"); + checkInvalidCookie("Set-Cookie2:name=test;Disc,ard"); + checkInvalidCookie("Set-Cookie2:name=test;Domain=u,rl"); + checkInvalidCookie("Set-Cookie2:name=test;Path=pa,th"); + checkInvalidCookie("Set-Cookie2:name=test;Secure=se,cure"); + checkInvalidCookie("Set-Cookie2:name=test;se,cure"); + checkInvalidCookie("Set-Cookie2:name=test;Max-Age=se,cure"); + checkInvalidCookie("Set-Cookie2:name=test;Max-Age="); + checkInvalidCookie("Set-Cookie2:name=test;Max-Age=max-age"); + checkInvalidCookie("Set-Cookie2:name=test;Max-Age=1000.0"); + checkInvalidCookie("Set-Cookie2:name=test;Version=trivail"); + checkInvalidCookie("Set-Cookie2:name=test;vErsion=1000.0"); + checkInvalidCookie("Set-Cookie2:name=test;vErsion=1000"); + } + + /** + * @tests java.net.HttpCookie#parse(String) for locales other than + * Locale.ENGLISH. + * + * @since 1.6 + */ + public void test_Parse_locale() { + Locale.setDefault(Locale.FRENCH); + List list = HttpCookie + .parse("Set-Cookie:name=test;expires=Thu, 30-Oct-2008 19:14:07 GMT;"); + HttpCookie cookie = list.get(0); + assertEquals(0, cookie.getMaxAge()); + assertTrue(cookie.hasExpired()); + + Locale.setDefault(Locale.GERMAN); + list = HttpCookie + .parse("Set-Cookie:name=test;expires=Sun, 30-Oct-2005 19:14:07 GMT;"); + cookie = list.get(0); + assertEquals(0, cookie.getMaxAge()); + assertTrue(cookie.hasExpired()); + + Locale.setDefault(Locale.KOREA); + list = HttpCookie + .parse("Set-Cookie:name=test;max-age=1234;expires=Sun, 30-Oct-2005 19:14:07 GMT;"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertEquals(1234, cookie.getMaxAge()); + assertFalse(cookie.hasExpired()); + + Locale.setDefault(Locale.TAIWAN); + list = HttpCookie + .parse("Set-Cookie:name=test;expires=Sun, 30-Oct-2005 19:14:07 GMT;max-age=-12345;"); + cookie = list.get(0); + assertEquals(0, cookie.getMaxAge()); + assertTrue(cookie.hasExpired()); + + // Locale does not affect version 1 cookie. + Locale.setDefault(Locale.ITALIAN); + list = HttpCookie.parse("Set-Cookie2:name=test;max-age=1000"); + cookie = list.get(0); + assertEquals(1000, cookie.getMaxAge()); + assertFalse(cookie.hasExpired()); + } + + /** + * @tests java.net.HttpCookie#parse(String) for normal cases + * + * @since 1.6 + */ + public void test_Parse() { + List list = HttpCookie.parse("test=\"null\""); + HttpCookie cookie = list.get(0); + // when two '"' presents, the parser ignores it. + assertEquals("null", cookie.getValue()); + assertNull(cookie.getComment()); + assertNull(cookie.getCommentURL()); + assertFalse(cookie.getDiscard()); + assertNull(cookie.getDomain()); + assertEquals(-1, cookie.getMaxAge()); + assertNull(cookie.getPath()); + assertNull(cookie.getPortlist()); + assertFalse(cookie.getSecure()); + // default version is 0 + assertEquals(0, cookie.getVersion()); + + list = HttpCookie.parse("Set-cookie2:name=\"tes,t\""); + cookie = list.get(0); + // when two '"' presents, the parser ignores it. + assertEquals("tes,t", cookie.getValue()); + + // If cookie header = Set-Cookie2, version = 1 + list = HttpCookie + .parse("Set-cookie2:test=null\";;Port=abde,82;Path=/temp;;;Discard;commentURl=http://harmonytest.org;Max-age=-10;"); + cookie = list.get(0); + assertEquals("null\"", cookie.getValue()); + assertEquals(1, cookie.getVersion()); + assertEquals("/temp", cookie.getPath()); + assertTrue(cookie.getDiscard()); + assertEquals("http://harmonytest.org", cookie.getCommentURL()); + assertEquals(-10l, cookie.getMaxAge()); + assertTrue(cookie.hasExpired()); + assertEquals("abde,82", cookie.getPortlist()); + // Version 0 cookie + list = HttpCookie + .parse("Set-Cookie:name=tes,t;Comment=version1-cookie;Discard=false;commentURL=vers\nion1-cookie-url;Domain=x.y;"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertEquals("tes,t", cookie.getValue()); + assertEquals("name", cookie.getName()); + assertEquals("version1-cookie", cookie.getComment()); + assertEquals("vers\nion1-cookie-url", cookie.getCommentURL()); + assertEquals("x.y", cookie.getDomain()); + assertTrue(cookie.getDiscard()); + + // Check value + checkValidValue("Set-Cookie:", "val,ue"); + checkValidValue("Set-Cookie:", "val\nue"); + checkValidValue("Set-Cookie:", "value=value"); + checkValidValue("Set-Cookie2:", "val\nue"); + checkValidValue("Set-Cookie2:", "val\u2029ue"); + checkValidValue("Set-Cookie2:", "value=value"); + + // Check comment + // In RFC 2965 '=' is mandatory, but this is not the case in RI. + list = HttpCookie.parse("Set-Cookie:name=tes,t;Comment;"); + cookie = list.get(0); + assertNull(cookie.getComment()); + + list = HttpCookie + .parse("Set-Cookie:name=tes,t;Comment=sentence;Comment=anotherSentence"); + cookie = list.get(0); + assertEquals("sentence", cookie.getComment()); + + // Check CommentURL + list = HttpCookie + .parse("Set-Cookie:name=tes,t;Commenturl;commentuRL=(la,la)"); + cookie = list.get(0); + assertEquals("(la,la)", cookie.getCommentURL()); + + // Check Domain + list = HttpCookie.parse("Set-Cookie:name=test;Domain=a_domain"); + cookie = list.get(0); + assertEquals("a_domain", cookie.getDomain()); + + // Check Path + list = HttpCookie.parse("Set-Cookie:name=test;PaTh=pa$th"); + cookie = list.get(0); + assertEquals("pa$th", cookie.getPath()); + + // Check Max-Age + list = HttpCookie.parse("Set-Cookie:name=test;Max-Age=1000"); + cookie = list.get(0); + assertEquals(1000, cookie.getMaxAge()); + + list = HttpCookie.parse("Set-Cookie:name=test;Max-Age=-1000"); + cookie = list.get(0); + assertEquals(-1000, cookie.getMaxAge()); + + // Check portlist + list = HttpCookie.parse("Set-Cookie:name=tes,t;port"); + cookie = list.get(0); + assertEquals(null, cookie.getPortlist()); + + list = HttpCookie.parse("Set-Cookie:name=tes,t;port="); + cookie = list.get(0); + assertEquals("", cookie.getPortlist()); + + list = HttpCookie.parse("Set-Cookie:name=tes,t;port=123 345"); + cookie = list.get(0); + assertEquals("123 345", cookie.getPortlist()); + + list = HttpCookie.parse("Set-Cookie:name=tes,t;port=123,345"); + cookie = list.get(0); + assertEquals("123,345", cookie.getPortlist()); + + // Check Secure + list = HttpCookie.parse("Set-Cookie:name=test;secure"); + cookie = list.get(0); + assertTrue(cookie.getSecure()); + + list = HttpCookie.parse("Set-Cookie:name=test;secure=fa"); + cookie = list.get(0); + assertTrue(cookie.getSecure()); + assertFalse(cookie.hasExpired()); + + list = HttpCookie.parse("Set-Cookie2:name=test;secure=false"); + cookie = list.get(0); + assertTrue(cookie.getSecure()); + + // Check expire + list = HttpCookie.parse("Set-Cookie:name=test;expires=2006-10-23"); + cookie = list.get(0); + assertEquals(0, cookie.getMaxAge()); + assertTrue(cookie.hasExpired()); + + // Also recognize invalid date + list = HttpCookie + .parse("Set-Cookie:name=test;expires=Sun, 29-Feb-1999 19:14:07 GMT"); + cookie = list.get(0); + assertTrue(cookie.getMaxAge() < 0); + assertTrue(cookie.hasExpired()); + + // Parse multiple cookies + list = HttpCookie + .parse("Set-Cookie2:name=test;,Set-Cookie2:name2=test2;comment=c234;"); + cookie = list.get(0); + assertEquals("name", cookie.getName()); + assertEquals(1, cookie.getVersion()); + assertEquals("test", cookie.getValue()); + cookie = list.get(1); + assertEquals(1, cookie.getVersion()); + // From the second cookie, the "set-cookie2" header does not take effect + assertEquals("Set-Cookie2:name2", cookie.getName()); + assertEquals("c234", cookie.getComment()); + + list = HttpCookie.parse("Set-Cookie2:name=test,name2=test2"); + assertEquals(1, list.get(0).getVersion()); + assertEquals(1, list.get(1).getVersion()); + + // Must begin with "set-cookie2" header + list = HttpCookie.parse("name=test,Set-Cookie2:name2=test2"); + cookie = list.get(0); + assertEquals(1, list.size()); + + HttpCookie c = HttpCookie.parse( + "Set-cookie:NAME2=VALUE2;path=/t;domain=.b.c;version=1").get(0); + assertEquals(1, c.getVersion()); + + c = HttpCookie.parse( + "Set-cookie2:NAME2=VALUE2;path=/t;domain=.b.c;version=0") + .get(0); + assertEquals(1, c.getVersion()); + + list = HttpCookie.parse("Set-cookie:null=;Domain=null;Port=null"); + cookie = list.get(0); + + assertNotNull(cookie.getValue()); + assertNotNull(cookie.getName()); + assertNotNull(cookie.getDomain()); + assertNotNull(cookie.getPortlist()); + + // Check CommentURL, RI's bug: 'a name' is not valid attribute name. + list = HttpCookie + .parse("Set-Cookie:a name=tes,t;Commenturl;commentuRL=(la,la);path=hello"); + cookie = list.get(0); + assertEquals("(la,la)", cookie.getCommentURL()); + + + list = HttpCookie + .parse("Set-Cookie:name=tes,t;Commenturl;commentuRL=(la,la);commentuRL=hello"); + cookie = list.get(0); + assertEquals("(la,la)", cookie.getCommentURL()); + + list = HttpCookie + .parse("Set-Cookie:name=tes,t;Commenturl;commentuRL=(la,la); path =hello"); + cookie = list.get(0); + assertEquals("(la,la)", cookie.getCommentURL()); + assertEquals("hello", cookie.getPath()); + + list = HttpCookie + .parse("a Set-Cookie:name=tes,t;Commenturl;commentuRL=(la,la);path=hello"); + cookie = list.get(0); + assertEquals("(la,la)", cookie.getCommentURL()); + + + } + + /** + * @tests java.net.HttpCookie#parse(String) for version conflict cases + * + * @since 1.6 + */ + public void test_Parse_versionConflict() { + // If attribute expires presents, cookie will be recognized as version + // 0. No matter header is Set-cookie or Set-cookie2 + List list = HttpCookie + .parse("Set-Cookie2:name=;expires=;discard"); + HttpCookie cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertTrue(cookie.getDiscard()); + + list = HttpCookie.parse("Set-Cookie: name=value;port=80"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertEquals("80", cookie.getPortlist()); + + // In Set-Cookie header, max-age does not take effect when expires + // exists. + list = HttpCookie + .parse("Set-Cookie:name=test;expires=Tue, 27-Jan-1998 19:14:07 GMT;Max-Age=1000"); + cookie = list.get(0); + assertTrue(cookie.getMaxAge() < 0); + assertTrue(cookie.hasExpired()); + assertFalse(cookie.getDiscard()); + // Reverse sequence. max-age takes effect and decides the result of + // hasExpired() method. + list = HttpCookie + .parse("Set-Cookie:name=value;max-age=1000;expires=Tue, 17-Jan-1998 19:14:07 GMT;version=1"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertEquals(1000, cookie.getMaxAge()); + assertFalse(cookie.hasExpired()); + + // expires decides the version. Not take Set-cookie header, version into + // consideration if expires exists. + list = HttpCookie + .parse("Set-Cookie2:name=value;max-age=1000;version=1;expires=Tue, 17-Jan-1998 19:07:14 GMT;"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertEquals(1000, cookie.getMaxAge()); + assertFalse(cookie.hasExpired()); + + // expires does not cover other version 1 attributes. + list = HttpCookie + .parse("Set-Cookie2: name=value;expires=Sun, 27-Jan-2018 19:14:07 GMT;comment=mycomment;port=80,8080"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + assertEquals("80,8080", cookie.getPortlist()); + assertEquals("mycomment", cookie.getComment()); + + // When expires does not exist, version takes effect. + list = HttpCookie.parse("Set-Cookie:name=test;Version=1"); + cookie = list.get(0); + assertEquals(1, cookie.getVersion()); + assertEquals(-1, cookie.getMaxAge()); + list = HttpCookie.parse("Set-Cookie:name=test;vERsion=0;Version=1;versioN=0;vErsIon=1"); + cookie = list.get(0); + assertEquals(1, cookie.getVersion()); + + // When expires does not exist, max-age takes effect. + list = HttpCookie.parse("Set-Cookie:name=test;Max-Age=11"); + cookie = list.get(0); + assertEquals(1, cookie.getVersion()); + assertEquals(11, cookie.getMaxAge()); + // other version 1 attributes does not take effect + list = HttpCookie + .parse("Set-Cookie:name=test;comment=mycomment;commentURL=url;discard;domain=a.b.com;path=temp;port=79;secure"); + cookie = list.get(0); + assertEquals(0, cookie.getVersion()); + } + + private void checkValidValue(String header, String value) { + List list = HttpCookie + .parse(header + "name=" + value + ";"); + HttpCookie cookie = list.get(0); + assertEquals(value, cookie.getValue()); + } + + private void checkInvalidCookie(String header) { + try { + HttpCookie.parse(header); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + // version 0 cookie only takes effect on Locale.ENGLISH + locale = Locale.getDefault(); + Locale.setDefault(Locale.ENGLISH); + } + + @Override + protected void tearDown() throws Exception { + Locale.setDefault(locale); + super.tearDown(); + } + +} Index: modules/luni/src/main/java/java/net/HttpCookie.java =================================================================== --- modules/luni/src/main/java/java/net/HttpCookie.java (revision 0) +++ modules/luni/src/main/java/java/net/HttpCookie.java (revision 0) @@ -0,0 +1,818 @@ +/* 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 java.net; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.harmony.luni.util.Msg; + +/** + * This class represents a http cookie, which indicates the status information + * between the client agent side and the server side. According to RFC, there + * are 3 http cookie specifications. This class is compatible with all the three + * forms. HttpCookie class can accept all these 3 forms of syntax. + * + * @since 1.6 + */ +public final class HttpCookie implements Cloneable { + private String comment; + + private String commentURL; + + private boolean discard; + + private String domain; + + private long maxAge = -1l; + + private String name; + + private String path; + + private String portList; + + private boolean secure; + + private String value; + + private int version = 1; + + private static final String DOT_STR = "."; //$NON-NLS-1$ + + private static final String LOCAL_STR = ".local"; //$NON-NLS-1$ + + private static final String QUOTE_STR = "\""; //$NON-NLS-1$ + + private static final String COMMA_STR = ","; //$NON-NLS-1$ + + /** + * Initializes a cookie with the specified name and value. + * + * The name attribute can just contain ASCII characters, which is immutable + * after creation. Commas, white space and semicolons are not allowed. The $ + * character is also not allowed to be the beginning of the name. + * + * The value attribute depends on what the server side is interested. The + * setValue method can be used to change it. + * + * RFC 2965 is the default cookie specification of this class. If one wants + * to change the version of the cookie, the setVersion method is available. + * + * @param name - + * the specific name of the cookie + * @param value - + * the specific value of the cookie + * + * @throws IllegalArgumentException - + * if the name contains not-allowed or reserved characters + * + * @throws NullPointerException + * if the value of name is null + */ + public HttpCookie(String n, String v) { + String ntrim = n.trim(); // erase leading and trailing whitespaces + if (!isValidName(ntrim)) { + throw new IllegalArgumentException(Msg.getString("KB002")); //$NON-NLS-1$ + } + + name = ntrim; + value = v; + } + + private boolean isValidName(String n) { + // name cannot be empty or begin with '$' or equals the reserved + // attributes (case-insensitive) + boolean isValid = !(n.length() == 0 || n.startsWith("$") || attributeSet.containsKey(n.toLowerCase())); //$NON-NLS-1$ + if (isValid) { + for (int i = 0; i < n.length(); i++) { + char nameChar = n.charAt(i); + // name must be ASCII characters and cannot contain ';', ',' and + // whitespace + if (nameChar < 0 + || nameChar >= 127 + || nameChar == ';' + || nameChar == ',' + || (Character.isWhitespace(nameChar) && nameChar != ' ')) { + isValid = false; + break; + } + } + } + return isValid; + } + + /** + * Answers a copy of this object. + * + * @return a copy of this cookie + */ + @Override + public Object clone() { + try { + HttpCookie obj = (HttpCookie) super.clone(); + return obj; + } catch (CloneNotSupportedException e) { + return null; + } + } + + /** + * Answers hash code of this http cookie. The result is calculated as below: + * + * getName().toLowerCase().hashCode() + getDomain().toLowerCase().hashCode() + + * getPath().hashCode() + * + * @return the hash code of this cookie + */ + @Override + public int hashCode() { + int hashCode = name.toLowerCase().hashCode(); + hashCode += domain == null ? 0 : domain.toLowerCase().hashCode(); + hashCode += path == null ? 0 : path.hashCode(); + return hashCode; + } + + /** + * Answers whether two cookies are equal. Two cookies are equal if they have + * the same domain and name in a case-insensitive mode and path in a + * case-sensitive mode. + * + * @param obj + * the object to be compared. + * @return true if two cookies equals, false otherwise + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof HttpCookie) { + HttpCookie anotherCookie = (HttpCookie) obj; + if (name.equalsIgnoreCase(anotherCookie.getName())) { + String anotherDomain = anotherCookie.getDomain(); + boolean equals = domain == null ? anotherDomain == null + : domain.equalsIgnoreCase(anotherDomain); + if (equals) { + String anotherPath = anotherCookie.getPath(); + return path == null ? anotherPath == null : path + .equals(anotherPath); + } + } + } + return false; + } + + /** + * Returns a string to represent the cookie. The format of string follows + * the cookie specification. The leading token "Cookie" is not included + * + * @return the string format of the cookie object + */ + @Override + public String toString() { + StringBuilder cookieStr = new StringBuilder(); + cookieStr.append(name); + cookieStr.append("="); //$NON-NLS-1$ + if (version == 0) { + cookieStr.append(value); + } else if (version == 1) { + cookieStr.append(QUOTE_STR); + cookieStr.append(value); + cookieStr.append(QUOTE_STR); + + attrToString(cookieStr, "Path", path); //$NON-NLS-1$ + attrToString(cookieStr, "Domain", domain); //$NON-NLS-1$ + attrToString(cookieStr, "Port", portList);//$NON-NLS-1$ + } + return cookieStr.toString(); + } + + private void attrToString(StringBuilder builder, String attrName, + String attrValue) { + if (attrValue != null && builder != null) { + builder.append(";"); //$NON-NLS-1$ + builder.append("$");//$NON-NLS-1$ + builder.append(attrName); + builder.append("=\""); //$NON-NLS-1$ + builder.append(attrValue); + builder.append(QUOTE_STR); + } + } + + /** + * A utility method used to check whether the host name is in a domain or + * not. + * + * @param domain + * the domain to be checked against + * @param host + * the host to be checked + * @return true if the host is in the domain, false otherwise + */ + public static boolean domainMatches(String domain, String host) { + if (domain == null || host == null) { + return false; + } + String newDomain = domain.toLowerCase(); + String newHost = host.toLowerCase(); + return isValidDomain(newDomain) && effDomainMatches(newDomain, newHost) + && isValidHost(newDomain, newHost); + } + + private static boolean isValidDomain(String domain) { + // Rule 1: The value for Domain contains embedded dots, or is .local + if (domain.length() <= 2) { + return false; + } + return domain.substring(1, domain.length() - 1).indexOf(DOT_STR) != -1 + || domain.equals(LOCAL_STR); + } + + private static boolean effDomainMatches(String domain, String host) { + // calculate effective host name + String effHost = host.indexOf(DOT_STR) != -1 ? host + : (host + LOCAL_STR); + + // Rule 2: domain and host are string-compare equal, or A = NB, B = .B' + // and N is a non-empty name string + boolean inDomain = domain.equals(effHost); + inDomain = inDomain + || (effHost.endsWith(domain) + && effHost.length() > domain.length() && domain + .startsWith(DOT_STR)); + return inDomain; + } + + private static boolean isValidHost(String domain, String host) { + // Rule 3: host does not end with domain, or the remainder does not + // contain "." + boolean matches = !host.endsWith(domain); + if (!matches) { + String hostSub = host.substring(0, host.length() - domain.length()); + matches = hostSub.indexOf(DOT_STR) == -1; + } + return matches; + } + + /** + * Sets the version of the cookie. 0 indicates the original Netscape cookie + * specification, while 1 indicates RFC 2965/2109 specification. + * + * @param v + * 0 or 1 as stated above + * @throws IllegalArgumentException + * if v is neither 0 nor 1 + */ + public void setVersion(int v) { + if (v != 0 && v != 1) { + throw new IllegalArgumentException(Msg.getString("KB003")); //$NON-NLS-1$ + } + version = v; + } + + /** + * Get the version of this cookie + * + * @return 0 indicates the original Netscape cookie specification, while 1 + * indicates RFC 2965/2109 specification. + */ + public int getVersion() { + return version; + } + + /** + * Answers the value of this cookie. + * + * @return the value of this cookie + */ + public String getValue() { + return value; + } + + /** + * Sets the value for this cookie after it has been instantiated. String + * newValue can be in BASE64 form. If the version of the cookie is 0, + * special value as: white space, brackets, parentheses, equals signs, + * commas, double quotes, slashes, question marks, at signs, colons, and + * semicolons are not recommended. Empty values may lead to different + * behavior on different browsers. + * + * @param newValue + * the value for this cookie + */ + public void setValue(String newValue) { + // FIXME: According to spec, version 0 cookie value does not allow many + // symbols. But RI does not implement it. Follow RI temporarily. + value = newValue; + } + + /** + * Answers the name for this cookie. + * + * @return the name for this cookie + */ + public String getName() { + return name; + } + + /** + * Answers true if the browser only sends cookies over a secure protocol. + * False if can send cookies through any protocols. + * + * @return true if sends cookies only through secure protocol, false + * otherwise + */ + public boolean getSecure() { + return secure; + } + + /** + * Tells the browser whether the cookies should be sent to server through + * secure protocols. + * + * @param flag + * tells browser to send cookie to server only through secure + * protocol if flag is true + */ + public void setSecure(boolean flag) { + secure = flag; + } + + /** + * Answers the path part of a request URL to which this cookie is returned. + * This cookie is visible to all subpaths. + * + * @return the path used to return the cookie + */ + public String getPath() { + return path; + } + + /** + * Set the path to which this cookie is returned. This cookie is visible to + * all the pages under the path and all subpaths. + * + * @param path + * the path to which this cookie is returned + */ + public void setPath(String p) { + path = p; + } + + /** + * Returns the Max-Age value as specified in RFC 2965 of this cookie. + * + * @return the Max-Age value + */ + public long getMaxAge() { + return maxAge; + } + + /** + * Sets the Max-Age value as specified in RFC 2965 of this cookie to expire. + * + * @param expiry + * the value used to set the Max-Age value of this cookie + */ + public void setMaxAge(long expiry) { + maxAge = expiry; + } + + /** + * Answers the domain name for this cookie in the format specified in RFC + * 2965 + * + * @return the domain value of this cookie + */ + public String getDomain() { + return domain; + } + + /** + * Set the domain value for this cookie. Browsers send the cookie to the + * domain specified by this value. The form of the domain is specified in + * RFC 2965. + * + * @param pattern + * the domain pattern + */ + public void setDomain(String pattern) { + domain = pattern == null ? null : pattern.toLowerCase(); + } + + /** + * Answers the value of port attribute(specified in RFC 2965) of this + * cookie. + * + * @return port list of this cookie + */ + public String getPortlist() { + return portList; + } + + /** + * Set the value of port attribute(specified in RFC 2965) of this cookie. + * + * @param ports + * the value for port attribute + */ + public void setPortlist(String ports) { + portList = ports; + } + + /** + * Answers the value of discard attribute(specified in RFC 2965) of this + * cookie. + * + * @return discard value of this cookie + */ + public boolean getDiscard() { + return discard; + } + + /** + * Set the value of discard attribute(specified in RFC 2965) of this cookie. + * + * @param discard + * the value for discard attribute + */ + public void setDiscard(boolean dis) { + discard = dis; + } + + /** + * Answers the value of commentURL attribute(specified in RFC 2965) of this + * cookie. + * + * @return the value of commentURL attribute + */ + public String getCommentURL() { + return commentURL; + } + + /** + * Set the value of commentURL attribute(specified in RFC 2965) of this + * cookie. + * + * @param purpose + * the value of commentURL attribute to be set + */ + public void setCommentURL(String purpose) { + commentURL = purpose; + } + + /** + * Answers the value of comment attribute(specified in RFC 2965) of this + * cookie. + * + * @return the value of comment attribute + */ + public String getComment() { + return comment; + } + + /** + * Set the value of comment attribute(specified in RFC 2965) of this cookie. + * + * @param purpose + * the comment value to be set + */ + public void setComment(String purpose) { + comment = purpose; + } + + /** + * Answers whether the cookie has expired. + * + * @return true is the cookie has expired, false otherwise + */ + public boolean hasExpired() { + // -1 indicates the cookie will persist until browser shutdown + // so the cookie is not expired. + if (maxAge == -1l) { + return false; + } + + boolean expired = false; + if (maxAge <= 0l) { + expired = true; + } + return expired; + } + + /** + * Constructs a cookie from a string. The string should comply with + * set-cookie or set-cookie2 header format as specified in RFC 2965. Since + * set-cookies2 syntax allows more than one cookie definitions in one + * header, the returned object is a list. + * + * @param header + * a set-cookie or set-cookie2 header. + * @return a list of constructed cookies + * @throws IllegalArgumentException + * if the string does not comply with cookie specification, or + * the cookie name contains illegal characters, or reserved + * tokens of cookie specification appears + * @throws NullPointerException + * if header is null + */ + public static List parse(String header) { + Matcher matcher = HEAD_PATTERN.matcher(header); + // Parse cookie name & value + List list = null; + HttpCookie cookie = null; + String headerString = header; + int version = 0; + // process set-cookie | set-cookie2 head + if (matcher.find()) { + String cookieHead = matcher.group(); + if ("set-cookie2:".equalsIgnoreCase(cookieHead)) { //$NON-NLS-1$ + version = 1; + } + headerString = header.substring(cookieHead.length()); + } + + // parse cookie name/value pair + matcher = NAME_PATTERN.matcher(headerString); + if (matcher.lookingAt()) { + list = new ArrayList(); + cookie = new HttpCookie(matcher.group(1), matcher.group(2)); + cookie.setVersion(version); + + /* + * Comma is a delimiter in cookie spec 1.1. If find comma in version + * 1 cookie header, part of matched string need to be spitted out. + */ + String nameGroup = matcher.group(); + if (isCommaDelim(cookie)) { + headerString = headerString.substring(nameGroup + .indexOf(COMMA_STR)); + } else { + headerString = headerString.substring(nameGroup.length()); + } + list.add(cookie); + } else { + throw new IllegalArgumentException(); + } + + // parse cookie headerString + while (!(headerString.length() == 0)) { + matcher = cookie.getVersion() == 1 ? ATTR_PATTERN1 + .matcher(headerString) : ATTR_PATTERN0 + .matcher(headerString); + + if (matcher.lookingAt()) { + String attrName = matcher.group(1).trim(); + + // handle special situation like: <..>;;<..> + if (attrName.length() == 0) { + headerString = headerString.substring(1); + continue; + } + + // If port is the attribute, then comma will not be used as a + // delimiter + if (attrName.equalsIgnoreCase("port") //$NON-NLS-1$ + || attrName.equalsIgnoreCase("expires")) { //$NON-NLS-1$ + int start = matcher.regionStart(); + matcher = ATTR_PATTERN0.matcher(headerString); + matcher.region(start, headerString.length()); + matcher.lookingAt(); + }// If the last encountered token is comma, and the parsed + // attribute is not port, then this attribute/value pair ends. + else if (cookie.getVersion() == 1 + && attrName.startsWith(COMMA_STR)) { + headerString = headerString.substring(1); + matcher = NAME_PATTERN.matcher(headerString); + if (matcher.lookingAt()) { + cookie = new HttpCookie(matcher.group(1), matcher + .group(2)); + list.add(cookie); + headerString = headerString.substring(matcher.group() + .length()); + continue; + } + } + + Setter setter = attributeSet.get(attrName.toLowerCase()); + if (null == setter) { + throw new IllegalArgumentException(); + } + if (!setter.isSet()) { + String attrValue = matcher.group(2); + setter.validate(attrValue, cookie); + setter.setValue(matcher.group(2), cookie); + } + headerString = headerString.substring(matcher.end()); + } + } + + return list; + } + + /* + * Handle 2 special cases: 1. value is wrapped by a quotation 2. value + * contains comma + */ + + private static boolean isCommaDelim(HttpCookie cookie) { + String value = cookie.getValue(); + if (value.startsWith(QUOTE_STR) && value.endsWith(QUOTE_STR)) { + cookie.setValue(value.substring(1, value.length() - 1)); + return false; + } + if (cookie.getVersion() == 1 && value.contains(COMMA_STR)) { + cookie.setValue(value.substring(0, value.indexOf(COMMA_STR))); + return true; + } + return false; + } + + private abstract static class Setter { + boolean set; + + Setter() { + set = false; + } + + boolean isSet() { + return set; + } + + void set(boolean isSet) { + set = isSet; + } + + abstract void setValue(String value, HttpCookie cookie); + + void validate(String value, HttpCookie cookie) { + if (cookie.getVersion() == 1 && value != null + && value.contains(COMMA_STR)) { + throw new IllegalArgumentException(); + } + } + } + + private static HashMap attributeSet = new HashMap(); + { + attributeSet.put("comment", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setComment(value); + if (cookie.getComment() != null) { + set(true); + } + } + }); + attributeSet.put("commenturl", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setCommentURL(value); + if (cookie.getCommentURL() != null) { + set(true); + } + } + }); + attributeSet.put("discard", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setDiscard(true); + set(true); + } + }); + attributeSet.put("domain", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setDomain(value); + if (cookie.getDomain() != null) { + set(true); + } + } + }); + attributeSet.put("max-age", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + try { + cookie.setMaxAge(Long.parseLong(value)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(Msg.getString( + "KB001", "max-age")); //$NON-NLS-1$//$NON-NLS-2$ + } + set(true); + + if (!attributeSet.get("version").isSet()) { //$NON-NLS-1$ + cookie.setVersion(1); + } + } + }); + + attributeSet.put("path", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setPath(value); + if (cookie.getPath() != null) { + set(true); + } + } + }); + attributeSet.put("port", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setPortlist(value); + if (cookie.getPortlist() != null) { + set(true); + } + } + + @Override + void validate(String v, HttpCookie cookie) { + return; + } + }); + attributeSet.put("secure", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setSecure(true); + set(true); + } + }); + attributeSet.put("version", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + try { + int v = Integer.parseInt(value); + if (v > cookie.getVersion()) { + cookie.setVersion(v); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException(Msg.getString( + "KB001", "version"));//$NON-NLS-1$//$NON-NLS-2$ + } + if (cookie.getVersion() != 0) { + set(true); + } + } + }); + + attributeSet.put("expires", new Setter() { //$NON-NLS-1$ + @Override + void setValue(String value, HttpCookie cookie) { + cookie.setVersion(0); + attributeSet.get("version").set(true); //$NON-NLS-1$ + if (!attributeSet.get("max-age").isSet()) { //$NON-NLS-1$ + attributeSet.get("max-age").set(true); //$NON-NLS-1$ + if (!"en".equalsIgnoreCase(Locale.getDefault() //$NON-NLS-1$ + .getLanguage())) { + cookie.setMaxAge(0); + return; + } + try { + cookie.setMaxAge((Date.parse(value) - System + .currentTimeMillis()) / 1000); + } catch (IllegalArgumentException e) { + cookie.setMaxAge(0); + } + } + } + + @Override + void validate(String v, HttpCookie cookie) { + return; + } + }); + } + + private static Pattern HEAD_PATTERN = Pattern.compile("Set-Cookie2?:", //$NON-NLS-1$ + Pattern.CASE_INSENSITIVE); + + private static Pattern NAME_PATTERN = Pattern + .compile( + "([^$=,\u0085\u2028\u2029][^,\n\t\r\r\n\u0085\u2028\u2029]*?)=([^;]*)(;)?", //$NON-NLS-1$ + Pattern.DOTALL | Pattern.CASE_INSENSITIVE); + + private static Pattern ATTR_PATTERN0 = Pattern + .compile("([^;=]*)(?:=([^;]*))?"); //$NON-NLS-1$ + + private static Pattern ATTR_PATTERN1 = Pattern + .compile("(,?[^;=]*)(?:=([^;,]*))?((?=.))?"); //$NON-NLS-1$ +}