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$
+}