Index: src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java =================================================================== --- src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java (revision 264062) +++ src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java (working copy) @@ -43,8 +43,10 @@ public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(TestCookie.suite()); + suite.addTest(TestCookie2.suite()); suite.addTest(TestCookieCompatibilitySpec.suite()); suite.addTest(TestCookieRFC2109Spec.suite()); + suite.addTest(TestCookieRFC2965Spec.suite()); suite.addTest(TestCookieNetscapeDraft.suite()); suite.addTest(TestCookieIgnoreSpec.suite()); suite.addTest(TestCookiePolicy.suite()); Index: src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java =================================================================== --- src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java (revision 264062) +++ src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java (working copy) @@ -1,5 +1,5 @@ /* - * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ + * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ * $Revision: 1.1 $ * $Date: 2005/08/29 05:01:58 $ * ==================================================================== @@ -34,7 +34,11 @@ import org.apache.commons.httpclient.Cookie; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.Cookie2; +import java.util.Date; +import java.util.Arrays; + /** * Test cases for RFC2965 cookie spec * @@ -42,7 +46,6 @@ */ public class TestCookieRFC2965Spec extends TestCookieBase { - // ------------------------------------------------------------ Constructor public TestCookieRFC2965Spec(String name) { @@ -55,483 +58,700 @@ return new TestSuite(TestCookieRFC2965Spec.class); } - public void testParseAttributeInvalidParams() throws Exception { + + // ------------------------------------------------------- Test Cookie Parsing + + /** + * Test parse with invalid params. + */ + public void testParseInvalidParams() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie cookie = new Cookie(); - NameValuePair attribute = new NameValuePair("name", "value"); try { - cookiespec.parseAttribute(null, cookie); + // invalid header + cookiespec.parse("www.domain.com", 80, "/", false, (Header) null /* header */); fail("IllegalArgumentException must have been thrown"); - } catch (IllegalArgumentException expected) { - } + } catch (IllegalArgumentException expected) {} + + Header header = new Header("Set-Cookie2", "name=value;Version=1"); try { - cookiespec.parseAttribute(attribute, null); + // invalid request host + cookiespec.parse(null /* host */, 80, "/", false, header); fail("IllegalArgumentException must have been thrown"); - } catch (IllegalArgumentException expected) { - } + } catch (IllegalArgumentException expected) {} + try { + // invalid request port + cookiespec.parse("www.domain.com", -32 /* port */, "/", false, header); + fail("IllegalArgumentException must have been thrown"); + } catch (IllegalArgumentException expected) {} + try { + // invalid request path + cookiespec.parse("www.domain.com", 80, null /* path */, false, header); + fail("IllegalArgumentException must have been thrown"); + } catch (IllegalArgumentException expected) {} } - public void testParseAttributePath() throws Exception { + /** + * Test parsing cookie "Path" attribute. + */ + public void testParsePath() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie cookie = new Cookie(); - try { - cookiespec.parseAttribute(new NameValuePair("path", null), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - try { - cookiespec.parseAttribute(new NameValuePair("path", " "), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - cookiespec.parseAttribute(new NameValuePair("path", "/"), cookie); - assertEquals(cookie.getPath(), "/"); - // this will be ignored since path is already specified for this cookie - cookiespec.parseAttribute(new NameValuePair("path", null), cookie); + // path cannot be null + Header header = new Header("Set-Cookie2", "name=value;Path=;Version=1"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + //path cannot be blank + header = new Header("Set-Cookie2", "name=value;Path=\" \";Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + + // valid path + header = new Header("Set-Cookie2", "name=value;Path=/;Version=1;Path="); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + // only the first occurence of path attribute is considered, others ignored + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals("/", cookie.getPath()); + assertTrue(cookie.isPathAttributeSpecified()); + + // Path is OPTIONAL, defaults to the request path + header = new Header("Set-Cookie2", "name=value;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/path" /* request path */, false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; + assertEquals("/path", cookie.getPath()); + assertFalse(cookie.isPathAttributeSpecified()); } - public void testParseAttributeDomain() throws Exception { + /** + * Test parsing cookie "Domain" attribute. + */ + public void testParseDomain() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie cookie = new Cookie(); - try { - cookiespec.parseAttribute(new NameValuePair("domain", null), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - try { - cookiespec.parseAttribute(new NameValuePair("domain", " "), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - cookiespec.parseAttribute(new NameValuePair("domain", ".domain.com"), cookie); - assertEquals(cookie.getDomain(), ".domain.com"); - // this will be ignored since domain is already specified for this cookie - cookiespec.parseAttribute(new NameValuePair("domain", null), cookie); + // domain cannot be null + Header header = new Header("Set-Cookie2", "name=value;Domain=;Version=1"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); - // puts a dot in front of domain if missing - cookie = new Cookie(); - cookiespec.parseAttribute(new NameValuePair("domain", "domain.com"), cookie); - assertEquals(cookie.getDomain(), ".domain.com"); + // domain cannot be blank + header = new Header("Set-Cookie2", "name=value;Domain=\" \";Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + + header = new Header("Set-Cookie2", "name=value;Domain=.domain.com;Version=1;Domain="); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + // only the first occurence of domain attribute is considered, others ignored + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals(".domain.com", cookie.getDomain()); + assertTrue(cookie.isDomainAttributeSpecified()); + + // should put a leading dot if there is no dot in front of domain + header = new Header("Set-Cookie2", "name=value;Domain=domain.com;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; + assertEquals(".domain.com", cookie.getDomain()); + + // Domain is OPTIONAL, defaults to the request host + header = new Header("Set-Cookie2", "name=value;Version=1"); + parsed = cookiespec.parse("www.domain.com" /* request host */, 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; + assertEquals("www.domain.com", cookie.getDomain()); + assertFalse(cookie.isDomainAttributeSpecified()); } - public void testParseAttributePort() throws Exception { + /** + * Test parsing cookie "Port" attribute. + */ + public void testParsePort() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - - Cookie cookie = new Cookie(); - cookie.setPorts(new int[] {80}); //default to request port - cookiespec.parseAttribute(new NameValuePair("port", null), cookie); // null port defaults to request port - assertTrue(equalArray(cookie.getPorts(), new int[] {80})); + Header header = new Header("Set-Cookie2", "name=value;Port=;Version=1"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80 /* request port */, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals(new int[] {80}, cookie.getPorts()); + assertTrue(cookie.isPortAttributeSpecified() && cookie.isPortAttributeBlank()); - cookie = new Cookie(); - cookie.setPorts(new int[] {80}); //default to request port - cookiespec.parseAttribute(new NameValuePair("port", " "), cookie); // blank port defaults to request port - assertTrue(equalArray(cookie.getPorts(), new int[] {80})); + header = new Header("Set-Cookie2", "name=value;Port=\" \";Version=1"); + parsed = cookiespec.parse("www.domain.com", 80 /* request port */, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; + assertEquals(new int[] {80}, cookie.getPorts()); + assertTrue(cookie.isPortAttributeSpecified() && cookie.isPortAttributeBlank()); - try { - cookie = new Cookie(); - cookiespec.parseAttribute(new NameValuePair("port", "nonsense"), cookie); - // anything else not an 'int' must fail - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } + // invalid port + header = new Header("Set-Cookie2", "name=value;Port=nonsense;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); - cookie = new Cookie(); - cookiespec.parseAttribute(new NameValuePair("port", "80,8080"), cookie); - assertTrue(equalArray(cookie.getPorts(), new int[] {80,8080})); - // this will be ignored since port already specified for this cookie - cookiespec.parseAttribute(new NameValuePair("port", "nonsense"), cookie); + // all ports must be > 0 + header = new Header("Set-Cookie2", "name=value;Port=\"80,-800,8000\";Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + + // valid ports + header = new Header("Set-Cookie2", "name=value;Port=\"80,800,8000\";Version=1;Port=nonsense"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + // only the first occurence of port attribute is considered, others ignored + cookie = (Cookie2) parsed[0]; + assertEquals(new int[] {80,800,8000}, cookie.getPorts()); + assertTrue(cookie.isPortAttributeSpecified()); + + // Port is OPTIONAL, cookie can be accepted from any port + header = new Header("Set-Cookie2", "name=value;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; + assertFalse(cookie.isPortAttributeSpecified()); } - public void testParseAttributeVersion() throws Exception { + /** + * test parsing cookie name/value. + */ + public void testParseNameValue() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie cookie = new Cookie(); - try { - cookiespec.parseAttribute(new NameValuePair("version", null), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - - try { - cookiespec.parseAttribute(new NameValuePair("version", "nonsense"), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - cookiespec.parseAttribute(new NameValuePair("version", "1"), cookie); - assertEquals(cookie.getVersion(), 1); + Header header = new Header("Set-Cookie2", "name=value;Version=1;"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals("name", cookie.getName()); + assertEquals("value", cookie.getValue()); } - public void testParseAttributeMaxage() throws Exception { + /** + * test parsing cookie "Version" attribute. + */ + public void testParseVersion() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie cookie = new Cookie(); - try { - cookiespec.parseAttribute(new NameValuePair("max-age", null), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } + // version cannot ne null + Header header = new Header("Set-Cookie2", "name=value;Version=;"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); - try { - cookiespec.parseAttribute(new NameValuePair("max-age", "nonsense"), cookie); - fail("MalformedCookieException must have been thrown"); - } catch (MalformedCookieException expected) { - } - cookiespec.parseAttribute(new NameValuePair("max-age", "3600"), cookie); - assertNotNull(cookie.getExpiryDate()); + // version must be > 0 + header = new Header("Set-Cookie2", "name=value;Version=-1;"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + + // valid version + header = new Header("Set-Cookie2", "name=value;Version=1;"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals(1, cookie.getVersion()); + assertTrue(cookie.isVersionAttributeSpecified()); } - public void testParseAttributeOthers() throws Exception { + /** + * test parsing cookie "Max-age" attribute. + */ + public void testParseMaxage() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie cookie = new Cookie(); - // test comment - String comment = "test cookie"; - cookiespec.parseAttribute(new NameValuePair("comment", comment), cookie); - assertEquals(cookie.getComment(), comment); - // test comment URL - String commentURL = "www.sourceforge.com/cookiedesc"; - cookiespec.parseAttribute(new NameValuePair("commenturl", commentURL), cookie); - assertEquals(cookie.getCommentURL(), commentURL); - // test secure - cookiespec.parseAttribute(new NameValuePair("secure", null), cookie); - assertTrue(cookie.getSecure()); - // test discard - cookiespec.parseAttribute(new NameValuePair("discard", null), cookie); + // max-age cannot be null + Header header = new Header("Set-Cookie2", "name=value;Max-age=;Version=1"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + + // max-age must be > 0 + header = new Header("Set-Cookie2", "name=value;Max-age=-3600;Version=1;"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNull(parsed); + + // valid max-age + header = new Header("Set-Cookie2", "name=value;Max-age=3600;Version=1;Max-age=nonsense"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + // only the first occurence of max-age attribute is considered, others ignored + Cookie2 cookie = (Cookie2) parsed[0]; + assertFalse(cookie.isExpired()); + + // Max-age is OPTIONAL, defaults to session cookie + header = new Header("Set-Cookie2", "name=value;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; assertFalse(cookie.isPersistent()); } /** - * Test domain equals host + * test parsing "Discard" attribute. */ - public void testParseValidateDomainEqualsHost() throws Exception { - Header header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=www.b.com; version=1"); + public void testParseDiscard() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie[] parsed = cookieParse(cookiespec, "www.b.com", 80, "/", false, header); + Header header = new Header("Set-Cookie2", "name=value;Discard;Max-age=36000;Version=1"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); assertNotNull(parsed); assertEquals(1, parsed.length); - assertEquals("www.b.com", parsed[0].getDomain()); + Cookie2 cookie = (Cookie2) parsed[0]; + // discard overrides max-age + assertFalse(cookie.isPersistent()); - // test backward compatibility with Set-Cookie header - header = new Header("Set-Cookie", - "cookie-name=cookie-value; domain=www.b.com; version=1"); - parsed = cookieParse(cookiespec, "www.b.com", 80, "/", false, header); + // Discard is OPTIONAL, default behavior is dictated by max-age + header = new Header("Set-Cookie2", "name=value;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); assertNotNull(parsed); assertEquals(1, parsed.length); - assertEquals("www.b.com", parsed[0].getDomain()); } /** - * Test Domain with no leading dot in domain name + * test parsing "Comment", "CommentURL" and + * "Secure" attributes. */ - public void testParseValidateDomainLeadingDot() throws Exception { - // set-cookie2 header works without a leading dot - Header header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=a.b.com; version=1"); + public void testParseOtherAttributes() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie[] parsed = cookieParse(cookiespec, "www.a.b.com", 80, "/", false, header); + Header header = new Header("Set-Cookie2", "name=value;Comment=\"good cookie\";" + + "CommentURL=\"www.domain.com/goodcookie/\";Secure;Version=1"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); assertNotNull(parsed); assertEquals(1, parsed.length); - assertEquals(".a.b.com", parsed[0].getDomain()); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals("good cookie", cookie.getComment()); + assertEquals("www.domain.com/goodcookie/", cookie.getCommentURL()); + assertTrue(cookie.getSecure()); - // test backward compatibility; set-cookie header must fail - // without a leading dot in domain name - header = new Header("Set-Cookie", - "cookie-name=cookie-value; domain=a.b.com; version=1"); - try { - parsed = cookieParse(cookiespec, "www.a.b.com", 80, "/", false, header); - fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + // Comment, CommentURL, Secure are OPTIONAL + header = new Header("Set-Cookie2", "name=value;Version=1"); + parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + cookie = (Cookie2) parsed[0]; + assertFalse(cookie.getSecure()); } /** - * Domain must have atleast least one embedded dot + * Test parsing header with 2 cookies (separated by comma) */ - public void testParseValidateDomainEmbeddedDot() throws Exception { - Header header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=.com; version=1"); + public void testCookiesWithComma() throws Exception { + CookieSpec cookiespec = new RFC2965Spec(); + Header header = new Header("Set-Cookie2", "a=b,c"); + Cookie[] parsed = cookiespec.parse("www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(2, parsed.length); + assertEquals("a", parsed[0].getName()); + assertEquals("b", parsed[0].getValue()); + assertEquals("c", parsed[1].getName()); + assertEquals(null, parsed[1].getValue()); + } + // ------------------------------------------------------- Test Cookie Validation + + /** + * Test Domain validation when domain is not specified + * in Set-Cookie2 header. + */ + public void testValidateNoDomain() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - try { - Cookie[] parsed = cookieParse(cookiespec, "b.com", 80, "/", false, header); - fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } - - // test backward compatibility with Set-Cookie header - header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=.com; version=1"); - try { - Cookie[] parsed = cookieParse(cookiespec, "b.com", 80, "/", false, header); - fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + Header header = new Header("Set-Cookie2", "name=value;Version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com" /* request host */, 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + // cookie domain must string match request host + assertEquals("www.domain.com", cookie.getDomain()); } /** - * test .local domain + * Test Domain validation. Cookie domain attribute must have a + * leading dot. */ - public void testParseValidateDomainLocal() throws Exception { - Header header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=.local; version=1"); + public void testValidateDomainLeadingDot() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - Cookie[] parsed = cookieParse(cookiespec, "host", 80, "/", false, header); + Header header = new Header("Set-Cookie2", "name=value;Domain=domain.com;Version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com", 80, "/", false, header); assertNotNull(parsed); assertEquals(1, parsed.length); - assertEquals(".local", parsed[0].getDomain()); - //TODO: however simple host name like 'host' should not be accepted if domain name is not .local + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals(".domain.com", cookie.getDomain()); } /** - * effective host name minus domain must not contain any dots. + * Test Domain validation. Domain must have atleast one embedded dot. */ - public void testParseValidateDomainIllegal() throws Exception { - Header header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=.c.com; version=1"); + public void testValidateDomainEmbeddedDot() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); + Header header = new Header("Set-Cookie2", "name=value; domain=.com; version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "a.b.c.com", 80, "/", false, header); + cookieParse(cookiespec, "b.com", 80, "/", false, header); fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + } catch (MalformedCookieException expected) {} - // test backward compatibility with Set-Cookie header - header = new Header("Set-Cookie", - "cookie-name=cookie-value; domain=.c.com; version=1"); + header = new Header("Set-Cookie2", "name=value;Domain=domain.com;Version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + } + + /** + * Test local Domain validation. Simple host names + * (without any dots) are valid only when cookie domain is specified + * as ".local". + */ + public void testValidateDomainLocal() throws Exception { + CookieSpec cookiespec = new RFC2965Spec(); + // when domain is specified as .local, simple host names are valid + Header header = new Header("Set-Cookie2", "name=value; domain=.local; version=1"); + Cookie[] parsed = cookieParse(cookiespec, "simplehost" /* request host */, 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals(".local", cookie.getDomain()); + + // when domain is NOT specified as .local, simple host names are invalid + header = new Header("Set-Cookie2", "name=value; domain=domain.com; version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "a.b.c.com", 80, "/", false, header); + // since domain is not .local, this must fail + parsed = cookieParse(cookiespec, "simplehost" /* request host */, 80, "/", false, header); fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + } catch (MalformedCookieException expected) {} } + /** - * test version. + * Test Domain validation. Effective host name + * must domain-match domain attribute. */ - public void testParseValidateVersion() throws Exception { - Header header = new Header("Set-Cookie2", "cookie-name=cookie-value"); + public void testValidateDomainEffectiveHost() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); + + // cookie domain does not domain-match request host + Header header = new Header("Set-Cookie2", "name=value; domain=.domain.com; version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "c.a.com", 80, "/", false, header); - // version attribute is REQUIRED + cookieParse(cookiespec, "www.domain.org" /* request host */, 80, "/", false, header); fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + } catch (MalformedCookieException expected) {} - // test backward compatibility with Set-Cookie header - header = new Header("Set-Cookie", "cookie-name=cookie-value"); + // cookie domain domain-matches request host + header = new Header("Set-Cookie2", "name=value; domain=.domain.com; version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com" /* request host */, 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + } + + /** + * Test local Domain validation. + * Effective host name minus domain must not contain any dots. + */ + public void testValidateDomainIllegal() throws Exception { + CookieSpec cookiespec = new RFC2965Spec(); + Header header = new Header("Set-Cookie2", "name=value; domain=.domain.com; version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "c.a.com", 80, "/", false, header); - // version attribute is REQUIRED + cookieParse(cookiespec, "a.b.domain.com" /* request host */, 80, "/", false, header); fail("MalformedCookieException should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + } catch (MalformedCookieException expected) {} } /** - * test path. + * Test cookie Path validation. Cookie path attribute must path-match + * request path. */ - public void testParseValidatePath() throws Exception { - Header header = new Header("Set-Cookie2", - "cookie-name=cookie-value; domain=127.0.0.1; path=/path"); + public void testValidatePath() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); + Header header = new Header("Set-Cookie2", "name=value;path=/path;version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); - fail("HttpException exception should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } - Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/path/path1", false, header); - assertNotNull(parsed); - assertEquals(1, parsed.length); - assertEquals("/path", parsed[0].getDomain()); + cookieParse(cookiespec, "www.domain.com", 80, "/" /* request path */, false, header); + fail("MalformedCookieException exception should have been thrown"); + } catch (MalformedCookieException expected) {} - // test backward compatibility with Set-Cookie header - header = new Header("Set-Cookie", - "cookie-name=cookie-value; domain=127.0.0.1; path=/path"); + // path-matching is case-sensitive + header = new Header("Set-Cookie2", "name=value;path=/Path;version=1"); try { - parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); - fail("HttpException exception should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } - parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/path/path1", false, header); + cookieParse(cookiespec, "www.domain.com", 80, "/path" /* request path */, false, header); + fail("MalformedCookieException exception should have been thrown"); + } catch (MalformedCookieException expected) {} + + header = new Header("Set-Cookie2", "name=value;path=/path;version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com", + 80, "/path/path1" /* request path */, false, header); assertNotNull(parsed); assertEquals(1, parsed.length); - assertEquals("/path", parsed[0].getDomain()); + assertEquals("/path", parsed[0].getPath()); } /** - * Tests if cookie constructor rejects cookie name containing blanks. + * Test cookie name validation. */ - public void testCookieNameWithBlanks() throws Exception { - Header header = new Header("Set-Cookie2", "invalid name="); + public void testValidateCookieName() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); + // cookie name must not contain blanks + Header header = new Header("Set-Cookie2", "invalid name=value; version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); + cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); fail("MalformedCookieException exception should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + } catch (MalformedCookieException expected) {} - // backward compatibility with set-cookie - header = new Header("Set-Cookie", "invalid name="); + // cookie name must not start with '$'. + header = new Header("Set-Cookie2", "$invalid_name=value; version=1"); try { - Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); + cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); fail("MalformedCookieException exception should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + } catch (MalformedCookieException expected) {} + + // valid name + header = new Header("Set-Cookie2", "name=value; version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals("name", cookie.getName()); + assertEquals("value", cookie.getValue()); } + /** + * Test cookie Port validation. Request port must be in the + * port attribute list. + */ + public void testValidatePort() throws Exception { + Header header = new Header("Set-Cookie2", "name=value; Port=\"80,800\"; version=1"); + CookieSpec cookiespec = new RFC2965Spec(); + try { + cookieParse(cookiespec, "www.domain.com", 8000 /* request port */, "/", false, header); + fail("MalformedCookieException should have been thrown"); + } catch (MalformedCookieException e) {} + // valid port list + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com", 80 /* request port */, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + Cookie2 cookie = (Cookie2) parsed[0]; + assertEquals(new int[] {80, 800}, cookie.getPorts()); + } + /** - * Tests if cookie constructor rejects cookie name starting with $. + * Test cookie Version validation. */ - public void testCookieNameStartingWithDollarSign() throws Exception { - Header header = new Header("Set-Cookie2", "$invalid_name="); + public void testValidateVersion() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); + // version attribute is REQUIRED + Header header = new Header("Set-Cookie2", "name=value"); try { - Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); - fail("MalformedCookieException exception should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + cookieParse(cookiespec, "www.domain.com", 8000, "/", false, header); + fail("MalformedCookieException should have been thrown"); + } catch (MalformedCookieException e) {} - // backward compatibility with set-cookie - header = new Header("Set-Cookie", "$invalid_name="); + // version attribute must be 1 with Set-Cookie2 header + header = new Header("Set-Cookie2", "name=value; version=0"); try { - Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header); - fail("MalformedCookieException exception should have been thrown"); - } catch (MalformedCookieException e) { - // expected - } + cookieParse(cookiespec, "www.domain.com", 8000, "/", false, header); + fail("MalformedCookieException should have been thrown"); + } catch (MalformedCookieException e) {} } - //TODO: test for cookie matching - //TODO: test for cookie formatting + // ------------------------------------------------------- Test Cookie Matching + /** - * Tests if cookie values with embedded comma are handled correctly. + * test cookie Path matching. Cookie path attribute must path-match + * path of the request URI. */ - public void testCookieWithComma() throws Exception { - Header header = new Header("Set-Cookie", "a=b,c"); + public void testMatchPath() throws Exception { + Cookie2 cookie = new Cookie2(".domain.com", "name", + "value", "/path" /* path */, null, false, new int[] {80}); + CookieSpec cookiespec = new RFC2965Spec(); + assertFalse(cookiespec.match("www.domain.com", 80, "/" /* request path */, false, cookie)); + assertTrue(cookiespec.match("www.domain.com", 80, "/path/path1" /* request path */, false, cookie)); + } + /** + * test cookie Domain matching. + */ + public void testMatchDomain() throws Exception { + Cookie2 cookie = new Cookie2(".domain.com" /* domain */, "name", + "value", "/", null, false, new int[] {80}); CookieSpec cookiespec = new RFC2965Spec(); - Cookie[] cookies = cookiespec.parse("localhost", 80, "/", false, header); - assertEquals("number of cookies", 2, cookies.length); - assertEquals("a", cookies[0].getName()); - assertEquals("b", cookies[0].getValue()); - assertEquals("c", cookies[1].getName()); - assertEquals(null, cookies[1].getValue()); + // effective host name minus domain must not contain any dots + assertFalse(cookiespec.match("a.b.domain.com" /* request host */, 80, "/", false, cookie)); + // The effective host name MUST domain-match the Domain + // attribute of the cookie. + assertFalse(cookiespec.match("www.domain.org" /* request host */, 80, "/", false, cookie)); + assertTrue(cookiespec.match("www.domain.com" /* request host */, 80, "/", false, cookie)); } - public void testFormatInvalidCookies() throws Exception { + /** + * test cookie local Domain matching. + */ + public void testMatchDomainLocal() throws Exception { + Cookie2 cookie = new Cookie2(".local" /* domain */, "name", + "value", "/", null, false, new int[] {80}); CookieSpec cookiespec = new RFC2965Spec(); - try { - String s = cookiespec.formatCookie(null); - fail("IllegalArgumentException nust have been thrown"); - } catch (IllegalArgumentException expected) { - } + assertTrue(cookiespec.match("host" /* request host */, 80, "/", false, cookie)); + assertFalse(cookiespec.match("host.com" /* request host */, 80, "/", false, cookie)); } /** - * Tests RFC 2965 compiant cookie formatting. + * test cookie Port matching. */ - public void testRFC2965CookieFormatting() throws Exception { + public void testMatchPort() throws Exception { + // cookie can be sent to any port if port attribute not specified + Cookie2 cookie = new Cookie2(".domain.com", "name", + "value", "/", null, false, null /* ports */); CookieSpec cookiespec = new RFC2965Spec(); - Header header = new Header("Set-Cookie", - "name=\"value\"; version=\"1\"; path=\"/\"; domain=\".mydomain.com\""); - Cookie[] cookies = cookiespec.parse("myhost.mydomain.com", 80, "/", false, header ); - cookiespec.validate("myhost.mydomain.com", 80, "/", false, cookies[0]); - String s1 = cookiespec.formatCookie(cookies[0]); - assertEquals(s1, "$Version=\"1\"; name=\"value\"; $Domain=\".mydomain.com\"; $Path=\"/\""); + cookie.setPortAttributeSpecified(false); + assertTrue(cookiespec.match("www.domain.com", 8080 /* request port */, "/", false, cookie)); + assertTrue(cookiespec.match("www.domain.com", 323 /* request port */, "/", false, cookie)); - header = new Header( "Set-Cookie", - "name=value; path=/; domain=.mydomain.com"); - cookies = cookiespec.parse("myhost.mydomain.com", 80, "/", false, header ); - cookiespec.validate("myhost.mydomain.com", 80, "/", false, cookies[0]); - String s2 = cookiespec.formatCookie(cookies[0]); - assertEquals(s2, "$Version=0; name=value; $Domain=.mydomain.com; $Path=/"); + // otherwise, request port must be in cookie's port list + cookie = new Cookie2(".domain.com", "name", + "value", "/", null, false, new int[] {80, 8080} /* ports */); + cookie.setPortAttributeSpecified(true); + assertFalse(cookiespec.match("www.domain.com", 434 /* request port */, "/", false, cookie)); + assertTrue(cookiespec.match("www.domain.com", 8080 /* request port */, "/", false, cookie)); } - public void testRFC2965CookiesFormatting() throws Exception { + /** + * test cookie expiration. + */ + public void testCookieExpiration() throws Exception { + Date afterOneHour = new Date(System.currentTimeMillis() + 3600 * 1000L); + Cookie2 cookie = new Cookie2(".domain.com", "name", + "value", "/", afterOneHour /* expiry */, false, null); CookieSpec cookiespec = new RFC2965Spec(); - Header header = new Header("Set-Cookie", - "name1=value1; path=/; domain=.mydomain.com, " + - "name2=\"value2\"; version=\"1\"; path=\"/\"; domain=\".mydomain.com\""); - Cookie[] cookies = cookieParse(cookiespec, "myhost.mydomain.com", 80, "/", false, header); - assertNotNull(cookies); - assertEquals(2, cookies.length); - String s1 = cookiespec.formatCookies(cookies); - assertEquals(s1, - "$Version=0; name1=value1; $Domain=.mydomain.com; $Path=/; " + - "name2=value2; $Domain=.mydomain.com; $Path=/"); + assertTrue(cookiespec.match("www.domain.com", 80, "/", false, cookie)); - header = new Header("Set-Cookie", - "name1=value1; version=1; path=/; domain=.mydomain.com, " + - "name2=\"value2\"; version=\"1\"; path=\"/\"; domain=\".mydomain.com\""); - cookies = cookieParse(cookiespec, "myhost.mydomain.com", 80, "/", false, header); - assertNotNull(cookies); - assertEquals(2, cookies.length); - String s2 = cookiespec.formatCookies(cookies); - assertEquals(s2, - "$Version=\"1\"; name1=\"value1\"; $Domain=\".mydomain.com\"; $Path=\"/\"; " + - "name2=\"value2\"; $Domain=\".mydomain.com\"; $Path=\"/\""); + Date beforeOneHour = new Date(System.currentTimeMillis() - 3600 * 1000L); + cookie = new Cookie2(".domain.com", "name", + "value", "/", beforeOneHour /* expiry */, false, null); + assertFalse(cookiespec.match("www.domain.com", 80, "/", false, cookie)); + + // discard attributes overrides cookie age, makes it a session cookie. + cookie.setDiscard(true); + assertFalse(cookie.isPersistent()); + assertTrue(cookiespec.match("www.domain.com", 80, "/", false, cookie)); } /** - * Tests if null cookie values are handled correctly. + * test cookie Secure attribute. */ - public void testNullCookieValueFormatting() { - Cookie cookie = new Cookie(".whatever.com", "name", null, "/", null, false); - cookie.setDomainAttributeSpecified(true); - cookie.setPathAttributeSpecified(true); + public void testCookieSecure() throws Exception { + CookieSpec cookiespec = new RFC2965Spec(); + // secure cookie can only be sent over a secure connection + Cookie2 cookie = new Cookie2(".domain.com", "name", + "value", "/", null, true /* secure */, null); + assertFalse(cookiespec.match("www.domain.com", 80, "/", false /* request secure */, cookie)); + assertTrue(cookiespec.match("www.domain.com", 80, "/", true /* request secure */, cookie)); + } + // ------------------------------------------------------- Test Cookie Formatting + + public void testFormatInvalidCookie() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - String s = cookiespec.formatCookie(cookie); - assertEquals("$Version=0; name=; $Domain=.whatever.com; $Path=/", s); + try { + cookiespec.formatCookie(null); + fail("IllegalArgumentException nust have been thrown"); + } catch (IllegalArgumentException expected) {} + } + /** + * Tests RFC 2965 compliant cookie formatting. + */ + public void testRFC2965CookieFormatting() throws Exception { + CookieSpec cookiespec = new RFC2965Spec(); + Cookie2 cookie = new Cookie2(".domain.com", "name", + "value", "/", null, false, new int[] {80,8080}); cookie.setVersion(1); - s = cookiespec.formatCookie(cookie); - assertEquals("$Version=\"1\"; name=\"\"; $Domain=\".whatever.com\"; $Path=\"/\"", s); - } + // domain, path, port specified + cookie.setDomainAttributeSpecified(true); + cookie.setPathAttributeSpecified(true); + cookie.setPortAttributeSpecified(true); + assertEquals("$Version=\"1\"; name=\"value\"; $Domain=\".domain.com\"; $Path=\"/\"; $Port=\"80,8080\"", + cookiespec.formatCookie(cookie)); - public void testCookieNullDomainNullPathFormatting() { - Cookie cookie = new Cookie(null, "name", null, "/", null, false); + // domain, path specified but port unspecified cookie.setDomainAttributeSpecified(true); cookie.setPathAttributeSpecified(true); + cookie.setPortAttributeSpecified(false); + assertEquals("$Version=\"1\"; name=\"value\"; $Domain=\".domain.com\"; $Path=\"/\"", + cookiespec.formatCookie(cookie)); + // path specified, port specified but blank, domain unspecified + cookie.setDomainAttributeSpecified(false); + cookie.setPathAttributeSpecified(true); + cookie.setPortAttributeSpecified(true); + cookie.setPortAttributeBlank(true); + assertEquals("$Version=\"1\"; name=\"value\"; $Path=\"/\"; $Port=\"\"", + cookiespec.formatCookie(cookie)); + } + + /** + * Tests RFC 2965 compliant cookies formatting. + */ + public void testRFC2965CookiesFormatting() throws Exception { CookieSpec cookiespec = new RFC2965Spec(); - String s = cookiespec.formatCookie(cookie); - assertEquals("$Version=0; name=; $Path=/", s); + Cookie2 cookie1 = new Cookie2(".domain.com", "name1", + "value1", "/", null, false, new int[] {80,8080}); + cookie1.setVersion(1); + // domain, path, port specified + cookie1.setDomainAttributeSpecified(true); + cookie1.setPathAttributeSpecified(true); + cookie1.setPortAttributeSpecified(true); + Cookie2 cookie2 = new Cookie2(".domain.com", "name2", + null, "/", null, false, null); + cookie2.setVersion(1); + // value null, domain, path, port specified + cookie2.setDomainAttributeSpecified(true); + cookie2.setPathAttributeSpecified(true); + cookie2.setPortAttributeSpecified(false); + Cookie[] cookies = new Cookie[] {cookie1, cookie2}; + assertEquals("$Version=\"1\"; name1=\"value1\"; $Domain=\".domain.com\"; $Path=\"/\"; $Port=\"80,8080\"; " + + "name2=\"\"; $Domain=\".domain.com\"; $Path=\"/\"", cookiespec.formatCookies(cookies)); + } - cookie.setDomainAttributeSpecified(false); - cookie.setPathAttributeSpecified(false); - s = cookiespec.formatCookie(cookie); - assertEquals("$Version=0; name=", s); + // ------------------------------------------------------- Backward compatibility tests + + /** + * Test backward compatibility with Set-Cookie header. + */ + public void testCompatibilityWithSetCookie() throws Exception { + CookieSpec cookiespec = new RFC2965Spec(); + Header header = new Header("Set-Cookie", "name=value; domain=.domain.com; version=1"); + Cookie[] parsed = cookieParse(cookiespec, "www.domain.com", 80, "/", false, header); + assertNotNull(parsed); + assertEquals(1, parsed.length); + assertEquals("name", parsed[0].getName()); + assertEquals("value", parsed[0].getValue()); + assertEquals(".domain.com", parsed[0].getDomain()); + assertEquals("/", parsed[0].getPath()); } - private boolean equalArray(int[] a1, int[] a2) { - if (a1 == null && a2 == null) - return true; - if (a1 == null || a2 == null) - return false; - if (a1.length != a2.length) - return false; - for (int i = 0; i < a1.length; i++) { - if (a1[i] != a2[i]) - return false; - } - return true; + // ------------------------------------------------------- Helper Methods + + /** + * Asserts that two arrays are equal (deep equality). + * @param expected expected array + * @param actual actual array + */ + public static void assertEquals(int[] expected, int[] actual) { + if (Arrays.equals(expected, actual)) + return; + failNotEquals(null, Arrays.toString(expected), Arrays.toString(actual)); } + static private void failNotEquals(String message, Object expected, Object actual) { + fail(format(message, expected, actual)); + } + + static private String format(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null) + formatted = message+" "; + return formatted + "expected:<"+expected+"> but was:<"+actual+">"; + } + } Index: src/test/org/apache/commons/httpclient/cookie/TestCookie2.java =================================================================== --- src/test/org/apache/commons/httpclient/cookie/TestCookie2.java (revision 0) +++ src/test/org/apache/commons/httpclient/cookie/TestCookie2.java (revision 0) @@ -0,0 +1,128 @@ +/* + * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ + * $Revision: 1.1 $ + * $Date: 2005/08/29 05:01:58 $ + * ==================================================================== + * + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.httpclient.cookie; + +import java.util.*; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.commons.httpclient.Cookie; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.Cookie2; + + +/** + * Test cases for {@link Cookie2}. + * + * @author Samit Jain (jain.samit@gmail.com) + */ +public class TestCookie2 extends TestCookieBase { + + + // ------------------------------------------------------------ Constructor + + public TestCookie2(String name) { + super(name); + } + + // ------------------------------------------------------- TestCase Methods + + public static Test suite() { + return new TestSuite(TestCookie2.class); + } + + /** + * Tests default constructor. + */ + public void testDefaultConstuctor() { + Cookie2 dummy = new Cookie2(); + // check cookie properties (default values) + assertNull(dummy.getPorts()); + assertFalse(dummy.getSecure()); + assertFalse(dummy.isExpired()); + assertFalse(dummy.isDomainAttributeSpecified()); + assertFalse(dummy.isPathAttributeSpecified()); + assertFalse(dummy.isPortAttributeSpecified()); + assertFalse(dummy.isVersionAttributeSpecified()); + assertFalse(dummy.isPersistent()); + + Cookie2 dummy2 = new Cookie2(); + assertEquals(dummy, dummy2); + } + + public void testComparator() throws Exception { + Header setCookie2 = null; + Cookie[] parsed = null; + List cookies = new LinkedList(); + CookieSpec cookiespec = new RFC2965Spec(); + // Cookie 0 + setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie0; Version=1"); + parsed = cookieParse(cookiespec, "domain.com", 80, + "/path/path1", true, setCookie2); + cookies.add(parsed[0]); + // Cookie 1 + setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie1; Version=1"); + parsed = cookieParse(cookiespec, "domain.com", 80, "/path", true, setCookie2); + cookies.add(parsed[0]); + // Cookie 2 + setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie2; Version=1"); + parsed = cookieParse(cookiespec, "domain.com", 80, "/", true, setCookie2); + cookies.add(parsed[0]); + // Cookie 3 + setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie3; Version=1"); + parsed = cookieParse(cookiespec, "domain.com", 80, + "/path/path1/path2", true, setCookie2); + cookies.add(parsed[0]); + // Cookie 4 + setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie4; Version=1"); + parsed = cookieParse(cookiespec, "domain.com", 80, + "/path/path1/path2/path3", true, setCookie2); + cookies.add(parsed[0]); + + // The ascending order should be: + // 2, 1, 0, 3, 4 + int[] expectedOrder = new int[] {2, 1, 0, 3, 4}; + Set sortedCookies = new TreeSet(parsed[0]); + sortedCookies.addAll(cookies); + + int pass = 0; + for (Iterator itr = sortedCookies.iterator(); itr.hasNext(); ++pass) { + Cookie2 cookie = (Cookie2) itr.next(); + assertTrue("sortedCookies[" + pass + "] should be cookies[" + expectedOrder[pass] + "]", + cookie == cookies.get(expectedOrder[pass])); + } + + try { + parsed[0].compare(parsed[0], "foo"); + fail("Should have thrown an exception trying to compare non-cookies"); + } catch (ClassCastException expected) {} + } +} + Index: src/java/org/apache/commons/httpclient/Cookie.java =================================================================== --- src/java/org/apache/commons/httpclient/Cookie.java (revision 264062) +++ src/java/org/apache/commons/httpclient/Cookie.java (working copy) @@ -63,17 +63,6 @@ */ public class Cookie extends NameValuePair implements Serializable, Comparator { - // string constants for cookie attributes - public static final String DOMAIN = "domain"; - public static final String PATH = "path"; - public static final String PORT = "port"; - public static final String VERSION = "version"; - public static final String SECURE = "secure"; - public static final String MAXAGE = "max-age"; - public static final String COMMENT = "comment"; - public static final String COMMENTURL = "commenturl"; - public static final String DISCARD = "discard"; - // ----------------------------------------------------------- Constructors /** @@ -156,32 +145,6 @@ } /** - * Creates a cookie with the given name, value, domain attribute, - * path attribute, expiration attribute, secure attribute, and ports - * attribute. - * - * @param name the cookie name - * @param value the cookie value - * @param domain the domain this cookie can be sent to - * @param path the path prefix for which this cookie can be sent - * @param expires the {@link Date} at which this cookie expires, - * or null if the cookie expires at the end - * of the session - * @param secure if true this cookie can only be sent over secure - * connections - * @param ports the ports for which this cookie can be sent - * @throws IllegalArgumentException If cookie name is null or blank, - * cookie name contains a blank, or cookie name starts with character $ - * - */ - public Cookie(String domain, String name, String value, - String path, Date expires, boolean secure, int[] ports) { - - this(domain, name, value, path, expires, secure); - this.setPorts(ports); - } - - /** * Returns the comment describing the purpose of this cookie, or * null if no such comment has been defined. * @@ -205,50 +168,6 @@ cookieComment = comment; } - /** - * If a user agent (web browser) presents this cookie to a user, the - * cookie's purpose will be described by the information at this URL. - * - * @see #setCommentURL(String) - */ - public String getCommentURL() { - return cookieCommentURL; - } - - /** - * If a user agent (web browser) presents this cookie to a user, the - * cookie's purpose will be described by the information at this URL. - * - * @param commentURL - * - * @see #getCommentURL() - */ - public void setCommentURL(String commentURL) { - this.cookieCommentURL = commentURL; - } - - /** - * Get the Port attribute. It restricts the ports to which a cookie - * may be returned in a Cookie request header. - * - * @see #setPorts(int[]) - */ - public int[] getPorts() { - return cookiePorts; - } - - /** - * Set the Port attribute. It restricts the ports to which a cookie - * may be returned in a Cookie request header. - * - * @param ports - * - * @see #getPorts() - */ - public void setPorts(int[] ports) { - this.cookiePorts = ports; - } - /** * Returns the expiration {@link Date} of the cookie, or null * if none exists. @@ -279,15 +198,6 @@ cookieExpiryDate = expiryDate; } - /** - * Set the Discard attribute. - * - * @see #isPersistent() - * - */ - public void setDiscard(boolean toDiscard) { - discard = toDiscard; - } /** * Returns false if the cookie should be discarded at the end @@ -297,7 +207,7 @@ * of the "session"; true otherwise */ public boolean isPersistent() { - return (null != cookieExpiryDate) && !discard; + return (null != cookieExpiryDate); } @@ -403,15 +313,13 @@ } /** - * Returns true if this cookie has expired. Also returns - * true for a session cookie (discard=true). + * Returns true if this cookie has expired. * * @return true if the cookie has expired. */ public boolean isExpired() { - return (cookieExpiryDate != null && - cookieExpiryDate.getTime() <= System.currentTimeMillis()) - || discard; + return (cookieExpiryDate != null + && cookieExpiryDate.getTime() <= System.currentTimeMillis()); } /** @@ -429,18 +337,11 @@ /** * Indicates whether the cookie had a path specified in a - * path attribute of the Set-Cookie header. This property - * has two uses: - * + * path attribute of the Set-Cookie header. This value + * is important for generating the Cookie header because + * some cookie specifications require that the Cookie header + * should only include a path attribute if the cookie's path + * was specified in the Set-Cookie header. * * @param value true if the cookie's path was explicitly * set, false otherwise. @@ -466,18 +367,12 @@ /** * Indicates whether the cookie had a domain specified in a - * domain attribute of the Set-Cookie header. This property - * has two uses: - * + * * @param value true if the cookie's domain was explicitly * set, false otherwise. * @@ -501,82 +396,6 @@ } /** - * Indicates whether the cookie had a ports list specified in the - * Port attribute of the Set-Cookie2 header. This ensures - * that if an attribute appears more than once in a cookie response - * header, the first appearance of the attribute is used. - * - * @param value true if the cookie's ports were explicitly - * set, false otherwise. - * - * @see #isPortAttributeSpecified - */ - public void setPortAttributeSpecified(boolean value) { - hasPortAttribute = value; - } - - /** - * Returns true if cookie's ports were set via the Port - * attribute in the Set-Cookie2 header. - * - * @return true if the cookie's ports were explicitly - * set, false otherwise. - * - * @see #setPortAttributeSpecified - */ - public boolean isPortAttributeSpecified() { - return hasPortAttribute; - } - - /** - * Indicates whether the Port attribute in Set-Cookie2 header - * is blank. - * - * @param value - * - * @see #isPortAttributeBlank - */ - public void setPortAttributeBlank(boolean value) { - isPortAttributeBlank = value; - } - - /** - * Returns true if Port attribute in Set-Cookie2 header - * did not have any value. - * - * @return true if Port attribute in Set-Cookie2 header - * did not have any value. - * - * @see #setPortAttributeBlank - */ - public boolean isPortAttributeBlank() { - return isPortAttributeBlank; - } - - /** - * Returns the response header name this cookie was obtained from. - * Currently must be either 'set-cookie' or set-cookie2'. - * - * @return response header name - * @see #setHeaderName(String) - */ - public String getHeaderName() { - return headerName; - } - - /** - * Sets the header name of this cookie. - * - * @param headerName Currently must be either 'set-cookie' or - * set-cookie2'. - * @see #getHeaderName() - */ - public void setHeaderName(String headerName) { - this.headerName = headerName; - } - - - /** * Returns a hash code in keeping with the * {@link Object#hashCode} general hashCode contract. * @return A hash code @@ -679,73 +498,38 @@ // ----------------------------------------------------- Instance Variables - /** Comment attribute. */ - private String cookieComment; + /** Comment attribute. */ + private String cookieComment; - /** - * Comment URL attribute - * Note: Required for RFC 2965 cookie specification. - */ - private String cookieCommentURL; + /** Domain attribute. */ + private String cookieDomain; - /** Domain attribute. */ - private String cookieDomain; + /** Expiration {@link Date}. */ + private Date cookieExpiryDate; - /** - * Port attribute. - * Note: Required for RFC 2965 cookie specification. - */ - private int[] cookiePorts; + /** Path attribute. */ + private String cookiePath; - /** Expiration {@link Date}. */ - private Date cookieExpiryDate; + /** My secure flag. */ + private boolean isSecure; - /** - * Discard attribute. - * Note: Required for RFC 2965 cookie specification. - */ - private boolean discard = false; + /** + * Specifies if the set-cookie header included a Path attribute for this + * cookie + */ + private boolean hasPathAttribute = false; - /** Path attribute. */ - private String cookiePath; + /** + * Specifies if the set-cookie header included a Domain attribute for this + * cookie + */ + private boolean hasDomainAttribute = false; - /** My secure flag. */ - private boolean isSecure; + /** The version of the cookie specification I was created from. */ + private int cookieVersion = 0; - /** - * the name of the response header this cookie was obtained from. - */ - private String headerName; + // -------------------------------------------------------------- Constants - /** - * Specifies if the set-cookie header included a Path attribute for this - * cookie - */ - private boolean hasPathAttribute = false; - - /** - * Specifies if the set-cookie header included a Domain attribute for this - * cookie - */ - private boolean hasDomainAttribute = false; - - /** - * Specifies if the set-cookie2 header included a Port attribute for this - * cookie - */ - private boolean hasPortAttribute = false; - - /** - * Specifies if the set-cookie2 header's Port attribute did not have - * any value. - */ - private boolean isPortAttributeBlank = false; - - /** The version of the cookie specification I was created from. */ - private int cookieVersion = -1; - - // -------------------------------------------------------------- Constants - /** * Collator for Cookie comparisons. Could be replaced with references to * specific Locales. Index: src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java (revision 264062) +++ src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java (working copy) @@ -204,8 +204,7 @@ /** * Parse the "Set-Cookie" {@link Header} into an array of {@link - * Cookie}s. Subclasses that need to support other cookie headers can - * override this method. + * Cookie}s. * *

The syntax for the Set-Cookie response header is: * @@ -245,24 +244,7 @@ if (header == null) { throw new IllegalArgumentException("Header may not be null."); } - - Cookie[] cookies; - // the default implementation only accepts Set-Cookie header - if (header.getName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) { - - cookies = parse(host, port, path, secure, header.getValue()); - // set header name of cookies so we can identify later whether cookie - // came from set-cookie header - for (int i = 0; i < cookies.length; i++) { - cookies[i].setHeaderName(Header.SET_COOKIE_KEY); - } - } - else { - throw new MalformedCookieException("Header name is not valid. " + - "Default implementation supports " + - "only \"set-cookie\" header."); - } - return cookies; + return parse(host, port, path, secure, header.getValue()); } @@ -358,15 +340,15 @@ } - public Collection getValidDateFormats() { - return this.datepatterns; - } + public Collection getValidDateFormats() { + return this.datepatterns; + } - public void setValidDateFormats(final Collection datepatterns) { - this.datepatterns = datepatterns; - } + public void setValidDateFormats(final Collection datepatterns) { + this.datepatterns = datepatterns; + } - /** + /** * Performs most common {@link Cookie} validation * * @param host the host from which the {@link Cookie} was received @@ -590,7 +572,7 @@ */ private static void addInPathOrder(List list, Cookie addCookie) { int i = 0; - // TODO (jain): better implementation of sorting cookies + for (i = 0; i < list.size(); i++) { Cookie c = (Cookie) list.get(i); if (addCookie.compare(addCookie, c) > 0) { @@ -673,9 +655,4 @@ return new Header("Cookie", formatCookie(cookie)); } - public int getCookieVersion() { - // default cookie version is 0 - return 0; - } - } Index: src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java (revision 264062) +++ src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java (working copy) @@ -53,8 +53,14 @@ public class RFC2109Spec extends CookieSpecBase { private final ParameterFormatter formatter; - - /** Default constructor */ + + /** + * Cookie Response Header name for cookies processed + * by this spec. + */ + public static String SET_COOKIE_KEY = "set-cookie"; + + /** Default constructor */ public RFC2109Spec() { super(); this.formatter = new ParameterFormatter(); Index: src/java/org/apache/commons/httpclient/cookie/CookieSpec.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/CookieSpec.java (revision 264062) +++ src/java/org/apache/commons/httpclient/cookie/CookieSpec.java (working copy) @@ -1,5 +1,5 @@ /* - * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ + * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ * $Revision: 1.1 $ * $Date: 2005/08/29 05:01:58 $ * @@ -74,7 +74,7 @@ Cookie[] parse(String host, int port, String path, boolean secure, final String header) throws MalformedCookieException, IllegalArgumentException; - + //TODO(jain): why is this method public /** * Parse the "Set-Cookie" Header into an array of Cookies. * @@ -103,7 +103,7 @@ */ void parseAttribute(NameValuePair attribute, Cookie cookie) throws MalformedCookieException, IllegalArgumentException; - + //TODO(jain): why is this method public /** * Validate the cookie according to validation rules defined by the * cookie specification. @@ -224,11 +224,4 @@ */ Header formatCookieHeader(Cookie cookie) throws IllegalArgumentException; - /** - * Gets the highest cookie version supported by this cookie specification. - * - * @return highest cookie version supported. - */ - int getCookieVersion(); - } Index: src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java (revision 264062) +++ src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java (working copy) @@ -1,5 +1,5 @@ /* - * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ + * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ * $Revision: 1.1 $ * $Date: 2005/08/29 05:01:58 $ * @@ -320,4 +320,11 @@ public static CookieSpec getCompatibilitySpec() { return getSpecByPolicy(COMPATIBILITY); } + + public static int getCookieVersionBySpec(CookieSpec spec) { + if ((spec instanceof RFC2109Spec) || + (spec instanceof RFC2965Spec)) + return 1; + return 0; + } } Index: src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java (revision 264062) +++ src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java (working copy) @@ -1,5 +1,5 @@ /* - * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ + * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ * $Revision: 1.1 $ * $Date: 2005/08/29 05:01:58 $ * @@ -149,8 +149,4 @@ return false; } - public int getCookieVersion() { - return -1; - } - } Index: src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java (revision 264062) +++ src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java (working copy) @@ -1,76 +1,93 @@ /* - * $HeadURL$ - * $Revision: 1.1 $ - * $Date: 2005/08/29 05:01:58 $ - * - * ==================================================================== - * - * Copyright 2002-2004 The Apache Software Foundation - * - * Licensed 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ +* RFC2965Spec.java +* ==================================================================== +* +* Copyright 2002-2004 The Apache Software Foundation +* +* Licensed 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. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the Apache Software Foundation. For more +* information on the Apache Software Foundation, please see +* . +* +*/ package org.apache.commons.httpclient.cookie; -import org.apache.commons.httpclient.NameValuePair; -import org.apache.commons.httpclient.Cookie; -import org.apache.commons.httpclient.HeaderElement; -import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.util.ParameterFormatter; import java.util.*; -//TODO: make sure that the syntaz confirms to java 1.2/1.3 spec and does not include 1.4/1.4 features. -//TODO: refacortinf and naming for easy readabale code -//TODO: exception handling control flow problem in many parts of code. At many places we dont want to stop processing -// when an exception occirs. we want to catch the exception and continie processing /** - *

RFC 2965 specific cookie management functions + *

RFC 2965 specific cookie management functions. * - * @author Samit Jain - * + * @author jain.samit@gmail.com (Samit Jain) * @since 3.0 */ +public class RFC2965Spec extends CookieSpecBase { -// TODO: revise all comments and documentation + /** + * used for formatting RFC 2956 style cookies + */ + private final ParameterFormatter formatter; -// TODO: refactoring + /** + * Stores attribute name -> attribute handler mappings + */ + private static Map attributeHandlerMap = null; -public class RFC2965Spec extends CookieSpecBase { + /** + * Cookie Response Header name for cookies processed + * by this spec. + */ + public static String SET_COOKIE2_KEY = "set-cookie2"; - private final ParameterFormatter formatter; - - /** Default constructor */ + /** + * Default constructor + */ public RFC2965Spec() { super(); this.formatter = new ParameterFormatter(); this.formatter.setAlwaysUseQuotes(true); + initializeAttributeHandlerMap(); } /** + * initializes attribute name -> attribute handler mappings. + * Called from constructor. + */ + private void initializeAttributeHandlerMap() { + if (attributeHandlerMap == null) { + attributeHandlerMap = new HashMap(); + attributeHandlerMap.put(Cookie2.COOKIE_NAME_KEY, new Cookie2NameAttributeHandler()); + attributeHandlerMap.put(Cookie2.PATH, new Cookie2PathAttributeHandler()); + attributeHandlerMap.put(Cookie2.DOMAIN, new Cookie2DomainAttributeHandler()); + attributeHandlerMap.put(Cookie2.PORT, new Cookie2PortAttributeHandler()); + attributeHandlerMap.put(Cookie2.VERSION, new Cookie2VersionAttributeHandler()); + attributeHandlerMap.put(Cookie2.MAXAGE, new Cookie2MaxageAttributeHandler()); + } + } + + /** * Parses the Set-Cookie2 value into an array of Cookies. * *

The syntax for the Set-Cookie2 response header is: * *

-     * set-cookie      =    "Set-Cookie:" cookies
+     * set-cookie      =    "Set-Cookie2:" cookies
      * cookies         =    1#cookie
      * cookie          =    NAME "=" VALUE * (";" cookie-av)
      * NAME            =    attr
@@ -103,85 +120,41 @@
     public Cookie[] parse(
             String host, int port, String path, boolean secure, final Header header)
             throws MalformedCookieException {
-            //TODO (jain): should not throw MalformedCookieException since that is for a cookie
-            // should throw a more general exception -- like MalformedHeaderException or something ...
         LOG.trace("enter RFC2965.parse("
-                  + "String, port, path, boolean, String)");
+                  + "String, int, String, boolean, Header)");
 
         if (header == null) {
             throw new IllegalArgumentException("Header may not be null.");
         }
-        Cookie[] cookies = null;
-        String headerName;
+        if (header.getName() == null) {
+            throw new IllegalArgumentException("Header name may not be null.");
+        }
 
-        if (header.getName().equalsIgnoreCase(Header.SET_COOKIE2_KEY)) {
-            cookies = parse(host, port, path, secure, header.getValue());
-            headerName = Header.SET_COOKIE2_KEY;
-        }
-        else if (header.getName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+        if (header.getName().equalsIgnoreCase(SET_COOKIE2_KEY)) {
+            // parse cookie2 cookies
+            return parse(host, port, path, secure, header.getValue());
+        } else if (header.getName().equalsIgnoreCase(RFC2109Spec.SET_COOKIE_KEY)) {
+            // delegate parsing of old-style cookies to rfc2109Spec
             CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
-            cookies = rfc2109Spec.parse(host, port, path, secure, header.getValue());
-            headerName = Header.SET_COOKIE_KEY;
-        }
-        else {
+            return rfc2109Spec.parse(host, port, path, secure, header.getValue());
+        } else {
             throw new MalformedCookieException("Header name is not valid. " +
                                                "RFC 2965 supports \"set-cookie\" " +
                                                "and \"set-cookie2\" headers.");
         }
-
-        // set header name of cookies so we can identify later whether cookie
-        // came from set-cookie header or set-cookie2 header or ...
-        for (int i = 0; i < cookies.length; i++) {
-            cookies[i].setHeaderName(headerName);
-        }
-        return cookies;
     }
 
     /**
-     * Parses the Set-Cookie2 value into an array of Cookies.
-     *
-     * 

The syntax for the Set-Cookie2 response header is: - * - *

-     * set-cookie      =    "Set-Cookie:" cookies
-     * cookies         =    1#cookie
-     * cookie          =    NAME "=" VALUE * (";" cookie-av)
-     * NAME            =    attr
-     * VALUE           =    value
-     * cookie-av       =    "Comment" "=" value
-     *                 |    "CommentURL" "=" <"> http_URL <">
-     *                 |    "Discard"
-     *                 |    "Domain" "=" value
-     *                 |    "Max-Age" "=" value
-     *                 |    "Path" "=" value
-     *                 |    "Port" [ "=" <"> portlist <"> ]
-     *                 |    "Secure"
-     *                 |    "Version" "=" 1*DIGIT
-     * portlist        =       1#portnum
-     * portnum         =       1*DIGIT
-     * 
- * - * @param host the host from which the Set-Cookie2 value was - * received - * @param port the port from which the Set-Cookie2 value was - * received - * @param path the path from which the Set-Cookie2 value was - * received - * @param secure true when the Set-Cookie2 value was - * received over secure conection - * @param header the Set-Cookie2 header string received from the server - * @return an array of Cookies parsed from the Set-Cookie2 value - * @throws MalformedCookieException if an exception occurs during parsing + * @see #parse(String, int, String, boolean, org.apache.commons.httpclient.Header) */ public Cookie[] parse(String host, int port, String path, boolean secure, final String header) throws MalformedCookieException { - LOG.trace("enter RFC2965Spec.parse(" - + "String, port, path, boolean, Header)"); + + "String, int, String, boolean, String)"); - checkCommonArguments(host, port, path); - + // before we do anything, lets check validity of arguments + validateArgs(host, port, path); if (header == null) { throw new IllegalArgumentException("Header may not be null."); } @@ -194,13 +167,12 @@ HeaderElement[] headerElements = HeaderElement.parseElements(header.toCharArray()); - List cookies = new ArrayList(); - + List cookies = new LinkedList(); for (int i = 0; i < headerElements.length; i++) { HeaderElement headerelement = headerElements[i]; - Cookie cookie = null; + Cookie2 cookie = null; try { - cookie = new Cookie(host, + cookie = new Cookie2(host, headerelement.getName(), headerelement.getValue(), path, @@ -217,186 +189,303 @@ } } cookies.add(cookie); - } - catch (Exception e) { - // TODO (jain): Not sure what to do here? Oleg suggested stop processing - // when a cookie is malformed. However continue processing when cookie is well formed but there - // was a problem in parsing an attribute. How to implement this? - - // log the error and continue processing other cookies in header + } catch (Exception e) { + //TODO(jain): when do we consider the header malformed and stop processing it? + // throw this cookie, continue processing other cookies in header if (LOG.isDebugEnabled()) LOG.debug("Error occured while parsing cookie: \"" + headerelement + "\". " + e); } } - return (Cookie[]) cookies.toArray(new Cookie[0]); + if (cookies.isEmpty()) + return null; + return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]); } - private void checkCommonArguments(String host, int port, String path) { - if (host == null) { - throw new IllegalArgumentException( - "Host of origin may not be null"); - } - if (host.trim().equals("")) { - throw new IllegalArgumentException( - "Host of origin may not be blank"); - } - if (port < 0) { - throw new IllegalArgumentException("Invalid port: " + port); - } - if (path == null) { - throw new IllegalArgumentException( - "Path of origin may not be null."); - } - } - /** * Parse RFC 2965 specific cookie attribute and update the corresponsing * {@link org.apache.commons.httpclient.Cookie} properties. * * @param attribute {@link org.apache.commons.httpclient.NameValuePair} cookie attribute from the - * Set-Cookie2 header or Set-Cookie header (in case no Set-Cookie2 header was - * received). - * @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated + * Set-Cookie2 header. + * @param cookieParam {@link org.apache.commons.httpclient.Cookie} to be updated * @throws MalformedCookieException if an exception occurs during parsing */ public void parseAttribute( - final NameValuePair attribute, final Cookie cookie) + final NameValuePair attribute, final Cookie cookieParam) throws MalformedCookieException { - if (attribute == null) { throw new IllegalArgumentException("Attribute may not be null."); } - if (cookie == null) { + if (attribute.getName() == null) { + throw new IllegalArgumentException("Attribute Name may not be null."); + } + if (cookieParam == null) { throw new IllegalArgumentException("Cookie may not be null."); } + if (!(cookieParam instanceof Cookie2)) { + throw new IllegalArgumentException("Expected Cookie2 cookies."); + } + Cookie2 cookie = (Cookie2) cookieParam; final String paramName = attribute.getName().toLowerCase(); final String paramValue = attribute.getValue(); - if (paramName.equals("path")) { - //TODO: if Path attribute is specified in header without any value, what to do? - if (!cookie.isPathAttributeSpecified()) { - if (paramValue == null) { - throw new MalformedCookieException( - "Missing value for path attribute"); - } - if (paramValue.trim().equals("")) { - throw new MalformedCookieException( - "Blank value for path attribute"); - } - cookie.setPath(paramValue); - cookie.setPathAttributeSpecified(true); - } + try { + CookieAttributeHandler handler = getAttributeHandler(paramName); + handler.parse(cookie, paramValue); + } catch (IllegalStateException canIgnore) { + // handler not registered for this paramName } - else if (paramName.equals("domain")) { - //TODO: if Domain attribute is specified in header without any value, what to do? - if (!cookie.isDomainAttributeSpecified()) { - if (paramValue == null) { - throw new MalformedCookieException( - "Missing value for domain attribute"); - } - if (paramValue.trim().equals("")) { - throw new MalformedCookieException( - "Blank value for domain attribute"); - } - // domain is lowercased before storing in cookie since - // domain matching is case-insensitive - String domain = paramValue.toLowerCase(); - if (!domain.startsWith(".")) - domain = "." + domain; - cookie.setDomain(domain); - cookie.setDomainAttributeSpecified(true); - } + // handle other cookie attributes + if (paramName.equals(Cookie2.COMMENT)) { + if (cookie.getComment() == null) + cookie.setComment(paramValue); + } else if (paramName.equals(Cookie2.SECURE)) { + cookie.setSecure(true); + } else if (paramName.equals(Cookie2.COMMENTURL)) { + if (cookie.getCommentURL() == null) + cookie.setCommentURL(paramValue); + } else if (paramName.equals(Cookie2.DISCARD)) { + cookie.setDiscard(true); + } else { + // ignore unknown attribute-value pairs + if (LOG.isDebugEnabled()) + LOG.debug("Unrecognized cookie attribute: " + + attribute.toString()); } - else if (paramName.equals("max-age")) { + } - if (cookie.getExpiryDate() == null) { - if (paramValue == null) { - throw new MalformedCookieException( - "Missing value for max-age attribute"); - } - int age; - try { - age = Integer.parseInt(paramValue); - } catch (NumberFormatException e) { - throw new MalformedCookieException ("Invalid max-age " - + "attribute: " + e.getMessage()); - } - cookie.setExpiryDate( - new Date(System.currentTimeMillis() + age * 1000L)); - } + /** + * Performs RFC 2965 compliant {@link org.apache.commons.httpclient.Cookie} validation + * + * @param host the host from which the {@link org.apache.commons.httpclient.Cookie} was received + * @param port the port from which the {@link org.apache.commons.httpclient.Cookie} was received + * @param path the path from which the {@link org.apache.commons.httpclient.Cookie} was received + * @param secure true when the {@link org.apache.commons.httpclient.Cookie} was received using a + * secure connection + * @param cookieParam The cookie to validate + * @throws MalformedCookieException if an exception occurs during + * validation + */ + public void validate(String host, int port, String path, + boolean secure, final Cookie cookieParam) + throws MalformedCookieException { + + LOG.trace("enter RFC2965Spec.validate(String, int, String, " + + "boolean, Cookie)"); + + // before we do anything, lets check validity of arguments + validateArgs(host, port, path); + + if (!(cookieParam instanceof Cookie2)) { + // old-style cookies are validated according to the old rules + CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); + rfc2109Spec.validate(host, port, path, secure, cookieParam); + return; } - else if (paramName.equals("comment")) { - if (cookie.getComment() == null) - cookie.setComment(paramValue); + /* validate cookie2 cookies */ + Cookie2 cookie = (Cookie2) cookieParam; + // validate cookie name + CookieAttributeHandler handler = getAttributeHandler(Cookie2.COOKIE_NAME_KEY); + handler.validate(cookie, null); + // validate cookie path attribute + handler = getAttributeHandler(Cookie2.PATH); + handler.validate(cookie, path); + // validate cookie domain attribute + handler = getAttributeHandler(Cookie2.DOMAIN); + handler.validate(cookie, host); + // validate cookie port attribute + handler = getAttributeHandler(Cookie2.PORT); + handler.validate(cookie, String.valueOf(port)); + // validate cookie version attribute + handler = getAttributeHandler(Cookie2.VERSION); + handler.validate(cookie, null); + } + /** + * Return true if the cookie should be submitted with a request + * with given attributes, false otherwise. + * @param host the host to which the request is being submitted + * @param port the port to which the request is being submitted (ignored) + * @param path the path to which the request is being submitted + * @param secure true if the request is using a secure connection + * @param cookieParam {@link Cookie} to be matched + * @return true if the cookie matches the criterium + */ + public boolean match(String host, int port, String path, + boolean secure, final Cookie cookieParam) { + + LOG.trace("enter RFC2965.match(" + + "String, int, String, boolean, Cookie"); + + // before we do anything, lets check validity of arguments + validateArgs(host, port, path); + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } - else if (paramName.equals("secure")) { - cookie.setSecure(true); + if (!(cookieParam instanceof Cookie2)) { + // old-style cookies are matched according to the old rules + CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); + return rfc2109Spec.match(host, port, path, secure, cookieParam); + } + /* match cookie2 cookies */ + Cookie2 cookie = (Cookie2) cookieParam; + // match cookie path attribute + CookieAttributeHandler handler = getAttributeHandler(Cookie2.PATH); + if (!handler.match(cookie, path)) + return false; + // match cookie domain attribute + handler = getAttributeHandler(Cookie2.DOMAIN); + if (!handler.match(cookie, host)) + return false; + // match cookie port attribute + handler = getAttributeHandler(Cookie2.PORT); + if (!handler.match(cookie, String.valueOf(port))) + return false; + // check if cookie has expired + if (cookie.isPersistent() && cookie.isExpired()) + return false; + // finally make sure that if cookie Secure attribute is set, then this + // request is made using a secure connection + if (cookie.getSecure()) + return secure; + // if we get to this stage, we have a match + return true; + } + + /** + * Return a string suitable for sending in a "Cookie" header as + * defined in RFC 2965 + * @param cookieParam a {@link org.apache.commons.httpclient.Cookie} to be formatted as string + * @return a string suitable for sending in a "Cookie" header. + */ + public String formatCookie(Cookie cookieParam) { + LOG.trace("enter RFC2965Spec.formatCookie(Cookie)"); + + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } - else if (paramName.equals("version")) { - if (cookie.getVersion() == -1) { - if (paramValue == null) { - throw new MalformedCookieException( - "Missing value for version attribute"); - } - try { - cookie.setVersion(Integer.parseInt(paramValue)); - } catch (NumberFormatException e) { - throw new MalformedCookieException("Invalid version: " - + e.getMessage()); - } - } + if (!(cookieParam instanceof Cookie2)) { + // old-style cookies are formatted according to the old rules + CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); + return rfc2109Spec.formatCookie(cookieParam); } - else if (paramName.equals("port")) { - if (!cookie.isPortAttributeSpecified()) { - if ((paramValue == null) || (paramValue.trim().equals(""))) { - // If the Port attribute is present but has no value, the - // cookie MUST only be sent to the request-port it was received from. - // Since the default port list only contains request-port, we don't - // need to do anything here. - cookie.setPortAttributeBlank(true); - } - else { - int[] ports = parsePortAttribute(paramValue); - cookie.setPorts(ports); - } - cookie.setPortAttributeSpecified(true); + /* format cookie2 cookie */ + Cookie2 cookie = (Cookie2) cookieParam; + final StringBuffer buffer = new StringBuffer(); + // format cookie version + CookieAttributeHandler handler = getAttributeHandler(Cookie2.VERSION); + handler.format(buffer, cookie); + // format cookie attributes + formatCookieAttributes(buffer, cookie); + return buffer.toString(); + } + + /** + * Create a RFC 2965 compliant "Cookie" header value containing all + * {@link org.apache.commons.httpclient.Cookie}s suitable for + * sending in a "Cookie" header + * @param cookies an array of {@link org.apache.commons.httpclient.Cookie}s to be formatted + * @return a string suitable for sending in a Cookie header. + */ + public String formatCookies(Cookie[] cookies) { + LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])"); + + if (cookies == null) { + throw new IllegalArgumentException("Cookies may not be null"); + } + // check if cookies array contains a set-cookie (old style) cookie + boolean hasOldStyleCookie = false; + for (int i = 0; i < cookies.length; i++) { + if (!(cookies[i] instanceof Cookie2)) { + hasOldStyleCookie = true; + break; } } - else if (paramName.equals("commenturl")) { + // TODO(jain): check this logic? + if (hasOldStyleCookie) { + // delegate old-style cookie formatting to rfc2109Spec + CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); + return rfc2109Spec.formatCookies(cookies); + } - if (cookie.getCommentURL() == null) - cookie.setCommentURL(paramValue); + /* format cookie2 cookies */ + final StringBuffer buffer = new StringBuffer(); + // format cookie version + CookieAttributeHandler handler = getAttributeHandler(Cookie2.VERSION); + handler.format(buffer, null); + for (int i = 0; i < cookies.length; i++) { + Cookie2 cookie = (Cookie2) cookies[i]; + // format cookie attributes + formatCookieAttributes(buffer, cookie); } - else if (paramName.equals("Discard")) { - cookie.setDiscard(true); + return buffer.toString(); + } + /** + * Return a string suitable for sending in a "Cookie" header + * as defined in RFC 2965. + * @param buffer The string buffer to use for output + * @param cookie The {@link Cookie2} to be formatted as string + */ + private void formatCookieAttributes(final StringBuffer buffer, final Cookie2 cookie) { + // format cookie name and value + CookieAttributeHandler handler = getAttributeHandler(Cookie2.COOKIE_NAME_KEY); + handler.format(buffer, cookie); + // format domain attribute + handler = getAttributeHandler(Cookie2.DOMAIN); + handler.format(buffer, cookie); + // format path attribute + handler = getAttributeHandler(Cookie2.PATH); + handler.format(buffer, cookie); + // format port attribute + handler = getAttributeHandler(Cookie2.PORT); + handler.format(buffer, cookie); + } + + /** + * Retrieves valid Port attribute value for the given ports array. + * e.g. "8000,8001,8002" + * + * @param ports int array of ports + */ + private String createPortAttribute(int[] ports) { + StringBuffer portValue = new StringBuffer(); + for (int i = 0, len = ports.length; i < len; i++) { + if (i > 0) { + portValue.append(","); + } + portValue.append(ports[i]); } - else { - //TODO: should we throw an exception here? - //should the header be considered malformed - if (LOG.isDebugEnabled()) - LOG.debug("Unrecognized cookie attribute: " + - attribute.toString()); - } + return portValue.toString(); } - private int[] parsePortAttribute(final String paramValue) + /** + * Parses the given Port attribute value (e.g. "8000,8001,8002") + * into an array of ports. + * + * @param portValue port attribute value + * @return parsed array of ports + * @throws MalformedCookieException if there is a problem in + * parsing due to invalid portValue. + */ + private int[] parsePortAttribute(final String portValue) throws MalformedCookieException { - StringTokenizer st = new StringTokenizer(paramValue, ","); + StringTokenizer st = new StringTokenizer(portValue, ","); int[] ports = new int[st.countTokens()]; - try { int i = 0; while(st.hasMoreTokens()) { ports[i] = Integer.parseInt(st.nextToken().trim()); + if (ports[i] < 0) { + throw new MalformedCookieException ("Invalid Port attribute."); + } ++i; } } catch (NumberFormatException e) { @@ -407,78 +496,306 @@ } /** - * Performs RFC 2965 compliant {@link org.apache.commons.httpclient.Cookie} validation + * Validates host, port, path parameters. Refactored out since it is reqd by + * many methods. Validation rules are: + * * - * @param host the host from which the {@link org.apache.commons.httpclient.Cookie} was received - * @param port the port from which the {@link org.apache.commons.httpclient.Cookie} was received - * @param path the path from which the {@link org.apache.commons.httpclient.Cookie} was received - * @param secure true when the {@link org.apache.commons.httpclient.Cookie} was received using a - * secure connection - * @param cookie The cookie to validate - * @throws MalformedCookieException if an exception occurs during - * validation + * @param host host name where cookie was received from or being sent to. + * @param port port of host where cookie was received from or being sent to. + * @param path path on host where cookie was received from or being sent to. */ - public void validate(String host, int port, String path, - boolean secure, final Cookie cookie) throws MalformedCookieException { + private void validateArgs(String host, int port, String path) { + if (host == null) { + throw new IllegalArgumentException( + "Host of origin may not be null"); + } + if (host.trim().equals("")) { + throw new IllegalArgumentException( + "Host of origin may not be blank"); + } + if (port < 0) { + throw new IllegalArgumentException("Invalid port: " + port); + } + if (path == null) { + throw new IllegalArgumentException( + "Path of origin may not be null."); + } + } - LOG.trace("enter RFC2965Spec.validate(String, int, String, " - + "boolean, Cookie)"); + /** + * Gets attribute handler {@link CookieAttributeHandler} for the + * given attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @throws IllegalStateException if handler not found for the + * specified attribute. + */ + private CookieAttributeHandler getAttributeHandler(final String name) + throws IllegalStateException { + CookieAttributeHandler handler = + (CookieAttributeHandler)(attributeHandlerMap.get(name)); + if (handler == null) { + throw new IllegalStateException("Handler not registered for " + + name + " attribute."); + } else { + return handler; + } + } - if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) { - // old-style cookies are validated according to the old rules - CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); - rfc2109Spec.validate(host, port, path, secure, cookie); + /** + * Gets 'effective host name' as defined in RFC 2965. + *

+ * If a host name contains no dots, the effective host name is + * that name with the string .local appended to it. Otherwise + * the effective host name is the same as the host name. Note + * that all effective host names contain at least one dot. + * + * @param host host name where cookie is received from or being sent to. + * @return + */ + private String getEffectiveHost(String host) { + String effectiveHost = host; + if (host.indexOf('.') < 0) { + effectiveHost += ".local"; } - else if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE2_KEY)) { - // check validity of arguments - checkCommonArguments(host, port, path); + return effectiveHost; + } - if (path.trim().equals("")) { - path = PATH_DELIM; + /** + * Performs domain-match as defined by the RFC2965. + *

+ * Host A's name domain-matches host B's if + *

    + *
      their host name strings string-compare equal; or
    + *
      A is a HDN string and has the form NB, where N is a non-empty + * name string, B has the form .B', and B' is a HDN string. (So, + * x.y.com domain-matches .Y.com but not Y.com.)
    + *
+ * + * @param host host name where cookie is received from or being sent to. + * @param domain The cookie domain attribute. + * @return true if the specified host matches the given domain. + */ + public boolean domainMatch(String host, String domain) { + boolean match = host.equals(domain) + || (domain.startsWith(".") && host.endsWith(domain)); + + return match; + } + + /** + * Returns true if the given port exists in the given + * ports list. + * + * @param port port of host where cookie was received from or being sent to. + * @param ports port list + * @return true returns true if the given port exists in + * the given ports list; false otherwise. + */ + private boolean portMatch(int port, int[] ports) { + boolean portInList = false; + for (int i = 0, len = ports.length; i < len; i++) { + if (port == ports[i]) { + portInList = true; + break; } - host = host.toLowerCase(); + } + return portInList; + } - // validate cookie name - if (cookie.getName().indexOf(' ') != -1) { - throw new MalformedCookieException("Cookie name may not contain blanks"); + /** + * Casts the given {@link Cookie} cookie to {@link Cookie2} cookie. + * @param cookieParam {@link Cookie} + * @return {@link Cookie2} + */ + private Cookie2 getCookie2Cookie(Cookie cookieParam) { + if (!(cookieParam instanceof Cookie2)) { + throw new IllegalArgumentException("Expected Cookie2 cookie."); + } + return ((Cookie2) cookieParam); + } + + /** + * "Path" attribute handler for RFC 2965 cookie spec. + */ + private class Cookie2PathAttributeHandler + implements CookieAttributeHandler { + + /** + * Parse cookie path attribute. + * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String) + */ + public void parse(Cookie cookieParam, String path) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } - if (cookie.getName().startsWith("$")) { - throw new MalformedCookieException("Cookie name may not start with $"); + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (!cookie.isPathAttributeSpecified()) { + if (path == null) { + throw new MalformedCookieException( + "Missing value for path attribute"); + } + if (path.trim().equals("")) { + throw new MalformedCookieException( + "Blank value for path attribute"); + } + cookie.setPath(path); + cookie.setPathAttributeSpecified(true); } + } - // validate cookie Version attribute. A user agent rejects - // cookie if the Version attribute is missing. - if (cookie.getVersion() == -1) { - throw new MalformedCookieException( - "Missing value for Version attribute. Violates RFC 2965."); + /** + * Validate cookie path attribute. The value for the Path attribute must be a + * prefix of the request-URI (case-sensitive matching). + * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String) + */ + public void validate(Cookie cookieParam, String path) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (path == null) { + throw new IllegalArgumentException( + "Path of origin host may not be null."); + } + if (cookie.getPath() == null) { + throw new MalformedCookieException("Invalid cookie state: " + + "path attribute is null."); + } + if (path.trim().equals("")) { + path = PATH_DELIM; + } - // validate cookie Path attribute. The value for the Path - // attribute must be a prefix of the request-URI (case-sensitive). - // TODO (jain): refactor into pathMatch method for clarity if (!pathMatch(path, cookie.getPath())) { throw new MalformedCookieException( "Illegal path attribute \"" + cookie.getPath() + "\". Path of origin: \"" + path + "\""); } - // validate cookie Domain attribute. - final String cookieDomain = cookie.getDomain().toLowerCase(); + } + /** + * Match cookie path attribute. The value for the Path attribute must be a + * prefix of the request-URI (case-sensitive matching). + * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String) + */ + public boolean match(Cookie cookieParam, String path) { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (path == null) { + throw new IllegalArgumentException( + "Path of destination host may not be null."); + } + if (cookie.getPath() == null) { + LOG.warn("Invalid cookie state: path attribute is null."); + return false; + } + if (path.trim().equals("")) { + path = PATH_DELIM; + } + + if (!pathMatch(path, cookie.getPath())) { + return false; + } + return true; + } + + /** + * Format cookie Path attribute. + * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie) + */ + public void format(StringBuffer buffer, Cookie cookieParam) { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if ((cookie.getPath() != null) && (cookie.isPathAttributeSpecified())) { + buffer.append("; "); + formatter.format(buffer, new NameValuePair("$Path", cookie.getPath())); + } + } + } + + /** + * "Domain" cookie attribute handler for RFC 2965 cookie spec. + */ + private class Cookie2DomainAttributeHandler + implements CookieAttributeHandler { + + /** + * Parse cookie domain attribute. + * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String) + */ + public void parse(Cookie cookieParam, String domain) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (!cookie.isDomainAttributeSpecified()) { + //TODO (jain): how do we handle the case when domain is specified and equals host? + if (domain == null) { + throw new MalformedCookieException( + "Missing value for domain attribute"); + } + if (domain.trim().equals("")) { + throw new MalformedCookieException( + "Blank value for domain attribute"); + } + domain = domain.toLowerCase(); + // put a leading dot if domain does not start with a dot + if (!domain.startsWith(".")) + domain = "." + domain; + cookie.setDomain(domain); + cookie.setDomainAttributeSpecified(true); + } + } + + /** + * Validate cookie domain attribute. + * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String) + */ + public void validate(Cookie cookieParam, String host) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (host == null) { + throw new IllegalArgumentException( + "Host of origin may not be null"); + } + if (host.trim().equals("")) { + throw new IllegalArgumentException( + "Host of origin may not be blank"); + } + if (cookie.getDomain() == null) { + throw new MalformedCookieException("Invalid cookie state: " + + "domain not specified"); + } + host = host.toLowerCase(); + String cookieDomain = cookie.getDomain().toLowerCase(); + if (cookie.isDomainAttributeSpecified()) { // Domain attribute must start with a dot if (!cookieDomain.startsWith(".")) { - throw new MalformedCookieException("Domain attribute \"" - + cookie.getDomain() - + "\" violates RFC 2109: domain must start with a dot"); + throw new MalformedCookieException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot"); } // Domain attribute must contain atleast one embedded dot, // or the value must be equal to .local. int dotIndex = cookieDomain.indexOf('.', 1); - if ((dotIndex < 0 || dotIndex == cookieDomain.length() - 1) + if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1)) && (!cookieDomain.equals(".local"))) { - throw new MalformedCookieException( "Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2965: the value contains no embedded dots " @@ -486,7 +803,7 @@ } // The effective host name must domain-match domain attribute. - String effectiveHost = getEffectiveHost(host, cookieDomain); + String effectiveHost = getEffectiveHost(host); if (!domainMatch(effectiveHost, cookieDomain)) { throw new MalformedCookieException( "Domain attribute \"" + cookie.getDomain() @@ -503,8 +820,7 @@ + cookie.getDomain() + "\" violates RFC 2965: " + "effective host minus domain may not contain any dots"); } - } - else { + } else { // Domain was not specified in header. In this case, domain must // string match request host (case-insensitive). if (!cookie.getDomain().equals(host)) { @@ -514,281 +830,360 @@ + host + "\""); } } - - // validate cookie Port attribute. - // If the Port attribute is not specified in header, the - // cookie can be sent to any port. Otherwise, the request port - // must be in the cookie's port list. - if (cookie.isPortAttributeSpecified()) { - if (!portMatch(port, cookie.getPorts())) { - throw new MalformedCookieException( - "Port attribute violates RFC 2965: " - + "Request port not found in cookie's port list."); - } - } } - else { - throw new MalformedCookieException("RFC 2956 violation. Header name of cookie must be either " + - "\"set-cookie\" or \"set-cookie2\"."); - } - } - private String getEffectiveHost(String host, final String cookieDomain) { - String effectiveHost = host; - if (cookieDomain.equals(".local") && (host.indexOf('.') < 0)) { - effectiveHost += cookieDomain; - } - return effectiveHost; - } - - /** - * Returns true if the given port exists in the given - * ports list. Used to validate cookie. - * - * @param port - * @param ports - * @return true returns true if the given port exists in - * the given ports list; false otherwise. - */ - private boolean portMatch(int port, int[] ports) { - boolean portInList = false; - for (int i = 0, len = ports.length; i < len; i++) { - if (port == ports[i]) { - portInList = true; - break; - } - } - return portInList; - } - - /** - * Return true if the cookie should be submitted with a request - * with given attributes, false otherwise. - * @param host the host to which the request is being submitted - * @param port the port to which the request is being submitted (ignored) - * @param path the path to which the request is being submitted - * @param secure true if the request is using a secure connection - * @param cookie {@link Cookie} to be matched - * @return true if the cookie matches the criterium - */ - public boolean match(String host, int port, String path, - boolean secure, final Cookie cookie) { - - LOG.trace("enter RFC2965.match(" - + "String, int, String, boolean, Cookie"); - - if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) { - - // old-style cookies are matched according to the old rules - CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); - return rfc2109Spec.match(host, port, path, secure, cookie); - } - else if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE2_KEY)) { - checkCommonArguments(host, port, path); - - if (cookie == null) { + /** + * Match cookie domain attribute. + * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String) + */ + public boolean match(Cookie cookieParam, String host) { + if (cookieParam == null) { throw new IllegalArgumentException("Cookie may not be null"); } - if (path.trim().equals("")) { - path = PATH_DELIM; + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (host == null) { + throw new IllegalArgumentException( + "Destination Host may not be null"); } + if (host.trim().equals("")) { + throw new IllegalArgumentException( + "Destination Host may not be blank"); + } if (cookie.getDomain() == null) { LOG.warn("Invalid cookie state: domain not specified"); return false; } - if (cookie.getPath() == null) { - LOG.warn("Invalid cookie state: path not specified"); - return false; - } host = host.toLowerCase(); - - // match cookie Domain attribute. String cookieDomain = cookie.getDomain(); - String effectiveHost = getEffectiveHost(host, cookieDomain); + String effectiveHost = getEffectiveHost(host); // The effective host name MUST domain-match the Domain // attribute of the cookie. if (!domainMatch(effectiveHost, cookieDomain)) { return false; } - - // match cookie Port attribute. - if (cookie.isPortAttributeSpecified()) { - // If Port attribute is specified, the port must be in - // the cookie port list. - if (!portMatch(port, cookie.getPorts())) { - return false; - } - } - - // match cookie Path attribute. The request-URI MUST path-match - // the Path attribute of the cookie. - if (!pathMatch(path, cookie.getPath())) { + // effective host name minus domain must not contain any dots + String effectiveHostWithoutDomain = + effectiveHost.substring(0, effectiveHost.length() + - cookieDomain.length()); + if (effectiveHostWithoutDomain.indexOf('.') != -1) { return false; } + return true; + } - // validate cookie's age - if (cookie.getExpiryDate() != null - && !cookie.getExpiryDate().after(new Date())) { - return false; + /** + * Format cookie domain attribute. + * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie) + */ + public void format(StringBuffer buffer, Cookie cookieParam) { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } - - // finally make sure that if cookie Secure attribute is set, then this - // request is using a secure connection - if (cookie.getSecure()) { - return secure; + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (cookie.getDomain() != null + && cookie.isDomainAttributeSpecified()) { + buffer.append("; "); + formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain())); } - - return true; } - else { - return false; - } } /** - * Compares the given cookies and returns true if they match - * according to the rules specified in RFC 2965 (section 3.3.3); otherwise - * returns false. - * - * @param cookie1 - * @param cookie2 - * @return true if cookies match; otherwise false. + * "Port" cookie attribute handler for RFC 2965 cookie spec. */ - public boolean cookieMatch(Cookie cookie1, Cookie cookie2) { - //TODO (jain): how is the cookie name compared - return cookie1.getName().equals(cookie2.getName()) && - cookie1.getDomain().equalsIgnoreCase(cookie2.getDomain()) && - cookie1.getPath().equals(cookie2.getPath()); - } + private class Cookie2PortAttributeHandler + implements CookieAttributeHandler { - /** - * Return a string suitable for sending in a "Cookie" header - * as defined in RFC 2965. - * @param buffer The string buffer to use for output - * @param cookie The {@link org.apache.commons.httpclient.Cookie} to be formatted as string - */ - private void formatCookieAttributes(final StringBuffer buffer, final Cookie cookie) { - String value = cookie.getValue(); - if (value == null) { - value = ""; + /** + * Parse cookie port attribute. + * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String) + */ + public void parse(Cookie cookieParam, String portValue) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (!cookie.isPortAttributeSpecified()) { + if ((portValue == null) || (portValue.trim().equals(""))) { + // If the Port attribute is present but has no value, the + // cookie can only be sent to the request-port. + // Since the default port list contains only request-port, we don't + // need to do anything here. + cookie.setPortAttributeBlank(true); + } else { + int[] ports = parsePortAttribute(portValue); + cookie.setPorts(ports); + } + cookie.setPortAttributeSpecified(true); + } } - this.formatter.format(buffer, new NameValuePair(cookie.getName(), value)); - if (cookie.getDomain() != null - && cookie.isDomainAttributeSpecified()) { - buffer.append("; "); - this.formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain())); + /** + * Validate cookie port attribute. If the Port attribute was specified + * in header, the request port must be in cookie's port list. + * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String) + */ + public void validate(Cookie cookieParam, String value) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + int port = -1; + try { + port = Integer.parseInt(value); + } catch (NumberFormatException e) { + port = -1; + } + if (port < 0) { + throw new IllegalArgumentException("Invalid port of host."); + } + + if (cookie.isPortAttributeSpecified()) { + if (!portMatch(port, cookie.getPorts())) { + throw new MalformedCookieException( + "Port attribute violates RFC 2965: " + + "Request port not found in cookie's port list."); + } + } } - if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) { - buffer.append("; "); - this.formatter.format(buffer, new NameValuePair("$Path", cookie.getPath())); - } - if (cookie.isPortAttributeSpecified()) { - buffer.append("; "); - String portValue = ""; - if (!cookie.isPortAttributeBlank()) { - portValue = createPortAttributeValue(cookie.getPorts()); + /** + * Match cookie port attribute. If the Port attribute is not specified + * in header, the cookie can be sent to any port. Otherwise, the request port + * must be in the cookie's port list. + * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String) + */ + public boolean match(Cookie cookieParam, String value) { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } - this.formatter.format(buffer, new NameValuePair("$Port", portValue)); + Cookie2 cookie = getCookie2Cookie(cookieParam); + int port = -1; + try { + port = Integer.parseInt(value); + } catch (NumberFormatException e) { + port = -1; + } + if (port < 0) { + throw new IllegalArgumentException("Invalid port of destination: " + value); + } + + if (cookie.isPortAttributeSpecified()) { + if (cookie.getPorts() == null) { + LOG.warn("Invalid cookie state: port not specified"); + return false; + } + if (!portMatch(port, cookie.getPorts())) { + return false; + } + } + return true; } - } - private String createPortAttributeValue(int[] ports) { - StringBuffer portValue = new StringBuffer(); - for (int i = 0, len = ports.length; i < len; i++) { - if (i > 0) { - portValue.append(","); + /** + * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie) + */ + public void format(StringBuffer buffer, Cookie cookieParam) { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } - portValue.append(ports[i]); + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (cookie.isPortAttributeSpecified()) { + String portValue = ""; + if (!cookie.isPortAttributeBlank()) { + portValue = createPortAttribute(cookie.getPorts()); + } + buffer.append("; "); + formatter.format(buffer, new NameValuePair("$Port", portValue)); + } } - return portValue.toString(); } - /** - * Return a string suitable for sending in a "Cookie" header as - * defined in RFC 2965 - * @param cookie a {@link org.apache.commons.httpclient.Cookie} to be formatted as string - * @return a string suitable for sending in a "Cookie" header. - */ - public String formatCookie(Cookie cookie) { - LOG.trace("enter RFC2965Spec.formatCookie(Cookie)"); + /** + * "Name" cookie attribute handler for RFC 2965 cookie spec. + */ + private class Cookie2NameAttributeHandler + implements CookieAttributeHandler { - if (cookie == null) { - throw new IllegalArgumentException("Cookie may not be null"); - } - int version = cookie.getVersion(); - if (version < 1 || - cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) { - // delegate cookie formatting to rfc2109Spec - CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); - return rfc2109Spec.formatCookie(cookie); - } + /** + * Parse cookie name. + * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String) + */ + public void parse(Cookie cookie, String value) + throws MalformedCookieException { + } - StringBuffer buffer = new StringBuffer(); - this.formatter.format(buffer, - new NameValuePair("$Version", Integer.toString(version))); - buffer.append("; "); - formatCookieAttributes(buffer, cookie); + /** + * validate cookie name. + * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String) + */ + public void validate(Cookie cookieParam, String value) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (cookie.getName().indexOf(' ') != -1) { + throw new MalformedCookieException("Cookie name may not contain blanks"); + } + if (cookie.getName().startsWith("$")) { + throw new MalformedCookieException("Cookie name may not start with $"); + } + } - return buffer.toString(); - } + /** + * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String) + */ + public boolean match(Cookie cookie, String value) { + return true; + } + /** + * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie) + */ + public void format(StringBuffer buffer, Cookie cookieParam) { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + // format cookie name and value + String value = cookie.getValue(); + if (value == null) { + value = ""; + } + buffer.append("; "); + formatter.format(buffer, new NameValuePair(cookie.getName(), value)); + } + } + + /** + * "Max-age" cookie attribute handler for RFC 2965 cookie spec. + */ + private class Cookie2MaxageAttributeHandler + implements CookieAttributeHandler { + + /** + * Parse cookie max-age attribute. + * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String) + */ + public void parse(Cookie cookieParam, String value) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (cookie.getExpiryDate() == null) { + if (value == null) { + throw new MalformedCookieException( + "Missing value for max-age attribute"); + } + int age = -1; + try { + age = Integer.parseInt(value); + } catch (NumberFormatException e) { + age = -1; + } + if (age < 0) { + throw new MalformedCookieException ("Invalid max-age attribute."); + } + cookie.setExpiryDate( + new Date(System.currentTimeMillis() + age * 1000L)); + } + } + + /** + * validate cookie max-age attribute. + * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String) + */ + public void validate(Cookie cookie, String value) { + } + + /** + * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String) + */ + public boolean match(Cookie cookie, String value) { + return true; + } + + /** + * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie) + */ + public void format(StringBuffer buffer, Cookie cookie) { + } + } + /** - * Create a RFC 2965 compliant "Cookie" header value containing all - * {@link org.apache.commons.httpclient.Cookie}s in cookies suitable for sending in a "Cookie" - * header - * @param cookies an array of {@link org.apache.commons.httpclient.Cookie}s to be formatted - * @return a string suitable for sending in a Cookie header. + * "Version" cookie attribute handler for RFC 2965 cookie spec. */ - public String formatCookies(Cookie[] cookies) { - LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])"); + private class Cookie2VersionAttributeHandler + implements CookieAttributeHandler { - if (cookies == null) { - throw new IllegalArgumentException("Cookies may not be null"); - } - int lowestVersion = Integer.MAX_VALUE; - // pick the lowest version - for (int i = 0; i < cookies.length; i++) { - Cookie cookie = cookies[i]; - if (cookie.getVersion() < lowestVersion) { - lowestVersion = cookie.getVersion(); + /** + * Parse cookie version attribute. + * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String) + */ + public void parse(Cookie cookieParam, String value) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (!cookie.isVersionAttributeSpecified()) { + if (value == null) { + throw new MalformedCookieException( + "Missing value for version attribute"); + } + int version = -1; + try { + version = Integer.parseInt(value); + } catch (NumberFormatException e) { + version = -1; + } + if (version < 0) { + throw new MalformedCookieException("Invalid cookie version."); + } + cookie.setVersion(Integer.parseInt(value)); + cookie.setVersionAttributeSpecified(true); + } } - boolean hasOldStyleCookie = false; - // check if cookies array contains set-cookie (old style) cookie - for (int i = 0; i < cookies.length; i++) { - if (cookies[i].getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) { - hasOldStyleCookie = true; - break; + /** + * validate cookie version attribute. Version attribute is REQUIRED. + * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String) + */ + public void validate(Cookie cookieParam, String value) + throws MalformedCookieException { + if (cookieParam == null) { + throw new IllegalArgumentException("Cookie may not be null"); } + Cookie2 cookie = getCookie2Cookie(cookieParam); + if (!cookie.isVersionAttributeSpecified()) { + throw new MalformedCookieException( + "Violates RFC 2965. Version attribute is required."); + } + //TODO (jain): other versions for set-cookie2 ? + if (cookie.getVersion() != 1) { + throw new MalformedCookieException( + "Violates RFC 2965. Invalid value for Version attribute." + + "Must be \"1\"."); + } } - if ((lowestVersion < 1) || hasOldStyleCookie) { - // delegate cookie formatting to rfc2109Spec - CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109); - return rfc2109Spec.formatCookies(cookies); + /** + * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String) + */ + public boolean match(Cookie cookie, String value) { + return true; } - final StringBuffer buffer = new StringBuffer(); - this.formatter.format(buffer, - new NameValuePair("$Version", Integer.toString(lowestVersion))); - - for (int i = 0; i < cookies.length; i++) { - buffer.append("; "); - formatCookieAttributes(buffer, cookies[i]); + /** + * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie) + */ + public void format(StringBuffer buffer, Cookie cookie) { + formatter.format(buffer, new NameValuePair("$Version", "1")); } - return buffer.toString(); } - - /** - * Gets the highest cookie version supported by this cookie specification. - * - * @return highest cookie version supported. - */ - public int getCookieVersion() { - return 1; - } } Index: src/java/org/apache/commons/httpclient/cookie/Cookie2.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/Cookie2.java (revision 0) +++ src/java/org/apache/commons/httpclient/cookie/Cookie2.java (revision 0) @@ -0,0 +1,379 @@ +/* + * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $ + * $Revision: 1.1 $ + * $Date: 2005/08/29 05:01:58 $ + * + * ==================================================================== + * + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.httpclient; + +import java.io.Serializable; +import java.text.RuleBasedCollator; +import java.util.Comparator; +import java.util.Date; +import java.util.Locale; + +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.cookie.CookieSpec; +import org.apache.commons.httpclient.util.LangUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * Cookie class for {@link org.apache.commons.httpclient.cookie.RFC2965Spec} + * cookie specification. It extends {@link Cookie} class and adds newer cookie + * attributes and functions required for this specification. + *

+ * + * @author Samit Jain (jain.samit@gmail.com) + */ +public class Cookie2 extends Cookie { + + // string constants for cookie attributes + public static final String DOMAIN = "domain"; + public static final String PATH = "path"; + public static final String PORT = "port"; + public static final String VERSION = "version"; + public static final String SECURE = "secure"; + public static final String MAXAGE = "max-age"; + public static final String COMMENT = "comment"; + public static final String COMMENTURL = "commenturl"; + public static final String DISCARD = "discard"; + + public static final String COOKIE_NAME_KEY = "cookieName"; + + /** + * Default constructor. Creates a blank cookie + */ + public Cookie2() { + super(null, "noname", null, null, null, false); + } + + /** + * Creates a cookie with the given name, value and domain attribute. + * + * @param name the cookie name + * @param value the cookie value + * @param domain the domain this cookie can be sent to + */ + public Cookie2(String domain, String name, String value) { + super(domain, name, value); + } + + /** + * Creates a cookie with the given name, value, domain attribute, + * path attribute, expiration attribute, and secure attribute + * + * @param name the cookie name + * @param value the cookie value + * @param domain the domain this cookie can be sent to + * @param path the path prefix for which this cookie can be sent + * @param expires the {@link Date} at which this cookie expires, + * or null if the cookie expires at the end + * of the session + * @param secure if true this cookie can only be sent over secure + * connections + * @throws IllegalArgumentException If cookie name is null or blank, + * cookie name contains a blank, or cookie name starts with character $ + * + */ + public Cookie2(String domain, String name, String value, + String path, Date expires, boolean secure) { + super(domain, name, value, path, expires, secure); + } + + /** + * Creates a cookie with the given name, value, domain attribute, + * path attribute, expiration attribute, secure attribute, and ports + * attribute. + * + * @param name the cookie name + * @param value the cookie value + * @param domain the domain this cookie can be sent to + * @param path the path prefix for which this cookie can be sent + * @param expires the {@link Date} at which this cookie expires, + * or null if the cookie expires at the end + * of the session + * @param secure if true this cookie can only be sent over secure + * connections + * @param ports the ports for which this cookie can be sent + * @throws IllegalArgumentException If cookie name is null or blank, + * cookie name contains a blank, or cookie name starts with character $ + * + */ + public Cookie2(String domain, String name, String value, + String path, Date expires, boolean secure, int[] ports) { + super(domain, name, value, path, expires, secure); + setPorts(ports); + } + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described by the information at this URL. + * + * @see #setCommentURL(String) + */ + public String getCommentURL() { + return cookieCommentURL; + } + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described by the information at this URL. + * + * @param commentURL + * + * @see #getCommentURL() + */ + public void setCommentURL(String commentURL) { + this.cookieCommentURL = commentURL; + } + + /** + * Get the Port attribute. It restricts the ports to which a cookie + * may be returned in a Cookie request header. + * + * @see #setPorts(int[]) + */ + public int[] getPorts() { + return cookiePorts; + } + + /** + * Set the Port attribute. It restricts the ports to which a cookie + * may be returned in a Cookie request header. + * + * @param ports + * + * @see #getPorts() + */ + public void setPorts(int[] ports) { + this.cookiePorts = ports; + } + + /** + * Set the Discard attribute. + * + * Note: Discard attribute overrides Max-age. + * + * @see #isPersistent() + */ + public void setDiscard(boolean toDiscard) { + discard = toDiscard; + } + + /** + * Returns false if the cookie should be discarded at the end + * of the "session"; true otherwise. + * + * @return false if the cookie should be discarded at the end + * of the "session"; true otherwise + */ + public boolean isPersistent() { + return (null != getExpiryDate()) && !discard; + } + + /** + * Indicates whether the cookie had a domain attribute specified in the + * Set-Cookie2 response header. This property + * has two uses: + *
    + *
  • This value is important for generating the Cookie request header + * because the specification requires that the Cookie header + * should only include a domain attribute if the domain was specified in the + * Set-Cookie2 header. + *
  • + *
  • This also ensures that if domain attribute appears more than once in + * the Set-Cookie2 header, only the first appearance of domain + * attribute is used. + *
  • + *
+ * @param value true if the cookie's domain is specified + * in response header. + * + * @see #isDomainAttributeSpecified + */ + public void setDomainAttributeSpecified(boolean value) { + hasDomainAttribute = value; + } + + /** + * @return true if cookie domain attribute was specified in the + * Set-Cookie2 header. + * + * @see #setDomainAttributeSpecified + */ + public boolean isDomainAttributeSpecified() { + return hasDomainAttribute; + } + + /** + * Indicates whether the cookie had a port attribute specified in the + * Set-Cookie2 response header. This property + * has two uses: + *
    + *
  • This value is important for generating the Cookie request header + * because the specification requires that the Cookie header + * should only include a port attribute if the port was specified in the + * Set-Cookie2 header. + *
  • + *
  • This also ensures that if port attribute appears more than once in + * the Set-Cookie2 header, only the first appearance of the port + * attribute is used. + *
  • + *
+ * + * @param value true if port attribute is specified in response + * header. + * + * @see #isPortAttributeSpecified + */ + public void setPortAttributeSpecified(boolean value) { + hasPortAttribute = value; + } + + /** + * @return true if cookie port attribute was specified in the + * Set-Cookie2 header. + * + * @see #setPortAttributeSpecified + */ + public boolean isPortAttributeSpecified() { + return hasPortAttribute; + } + + /** + * Indicates whether the Port attribute in Set-Cookie2 header + * contains no value (is of the form Port=""). + *

+ * This value is required for generating + * the Cookie request header because the specification requires that if + * Set-Cookie2 header contains a blank value for port attribute, + * the Cookie header should also contain a port attribute with no value. + * + * @param value true if port attribute is specified as blank in response + * header. + * + * @see #isPortAttributeBlank + */ + public void setPortAttributeBlank(boolean value) { + isPortAttributeBlank = value; + } + + /** + * @return true if the port attribute in Set-Cookie2 header + * had no value (was of the form Port=""). + * + * @see #setPortAttributeBlank + */ + public boolean isPortAttributeBlank() { + return isPortAttributeBlank; + } + + /** + * Indicates whether the cookie had a version attribute specified in the + * Set-Cookie2 response header. This property has two uses: + *

    + *
  • Only the first occurence of the version attribute in Set-Cookie2 + * header is used, others ignored.
  • + *
  • Since version attribute is required for valid cookies, this property + * helps in checking this condition.
  • + *
+ * + * @param value true if version attribute is specified in response + * header. + * @see #isVersionAttributeSpecified() + */ + public void setVersionAttributeSpecified(boolean value) { + hasVersionAttribute = value; + } + + /** + * @return true if cookie version attribute was specified in the + * Set-Cookie2 header. + * + * @see #setVersionAttributeSpecified + */ + public boolean isVersionAttributeSpecified() { + return hasVersionAttribute; + } + + /** + * Return a textual representation of the cookie. + * + * @return string. + */ + public String toExternalForm() { + CookieSpec spec = + CookiePolicy.getCookieSpec(CookiePolicy.RFC_2965); + return spec.formatCookie(this); + } + + /** + * Comment URL attribute + */ + private String cookieCommentURL; + + /** + * Port attribute. + */ + private int[] cookiePorts; + + /** + * Discard attribute. + */ + private boolean discard = false; + + /** + * Indicates if the set-cookie header included a Domain attribute for this + * cookie + */ + private boolean hasDomainAttribute = false; + + /** + * Indicates if the set-cookie2 header included a Port attribute for this + * cookie + */ + private boolean hasPortAttribute = false; + + /** + * Indicates if the set-cookie2 header's Port attribute did not have + * any value. + */ + private boolean isPortAttributeBlank = false; + + /** + * Indicates if the set-cookie2 header included a Version attribute + */ + private boolean hasVersionAttribute = false; + + /** + * Log object for this class + */ + private static final Log LOG = LogFactory.getLog(Cookie2.class); + +} + Index: src/java/org/apache/commons/httpclient/cookie/CookieAttributeHandler.java =================================================================== --- src/java/org/apache/commons/httpclient/cookie/CookieAttributeHandler.java (revision 0) +++ src/java/org/apache/commons/httpclient/cookie/CookieAttributeHandler.java (revision 0) @@ -0,0 +1,57 @@ +package org.apache.commons.httpclient.cookie; + +import org.apache.commons.httpclient.Cookie; + +/** + * Ths interface represents a cookie attribute handler responsible + * for parsing, validating, matching and formatting a specific + * cookie attribute, such as path, domain, port, etc. + * + * Different cookie specifications can provide a specific + * implementation for this class based on their cookie handling + * rules. + * + * @author jain.samit@gmail.com (Samit Jain) + */ +public interface CookieAttributeHandler { + + /** + * Parse the given cookie attribute value and update the corresponding + * {@link org.apache.commons.httpclient.Cookie} property. + * + * @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated + * @param value cookie attribute value from the cookie response header + */ + public void parse(Cookie cookie, String value) + throws MalformedCookieException; + + /** + * Peforms cookie validation for the given attribute value. + * + * @param cookie {@link org.apache.commons.httpclient.Cookie} to validate + * @param value specific property of the host the cookie was received from. + * @throws MalformedCookieException if cookie validation fails for this attribute + */ + public void validate(Cookie cookie, String value) + throws MalformedCookieException; + + /** + * Matches the given value (property of the destination host where request is being + * submitted) with the corresponding cookie attribute. + * + * @param cookie {@link org.apache.commons.httpclient.Cookie} to match + * @param value specific property of the destination host + * @return true if the match is successful; false otherwise + */ + public boolean match(Cookie cookie, String value); + + /** + * Format the cookie attribute suitable for sending in a cookie request header and + * append it to the given buffer. + * + * @param buffer the string buffer to use for output + * @param cookie {@link org.apache.commons.httpclient.Cookie} to get the attribute + * from. + */ + public void format(StringBuffer buffer, Cookie cookie); +} Index: src/java/org/apache/commons/httpclient/HttpMethodBase.java =================================================================== --- src/java/org/apache/commons/httpclient/HttpMethodBase.java (revision 264062) +++ src/java/org/apache/commons/httpclient/HttpMethodBase.java (working copy) @@ -1198,12 +1198,15 @@ } } - Header buildCookie2RequestHeader(CookieSpec spec) { + /** + * Builds Cookie2 request header. + * @param spec + */ + protected Header buildCookie2RequestHeader(CookieSpec spec) { ParameterFormatter formatter = new ParameterFormatter(); StringBuffer buffer = new StringBuffer(); - formatter.format(buffer, - new NameValuePair("$Version", - Integer.toString(spec.getCookieVersion()))); + formatter.format(buffer, new NameValuePair("$Version", + Integer.toString(CookiePolicy.getCookieVersionBySpec(spec)))); Header header = new Header("Cookie2", buffer.toString(), true); return header; } @@ -1512,7 +1515,6 @@ Header header = headers[i]; Cookie[] cookies = null; try { - System.out.println("(jain) Header[" + i + "]: " + headers[i]); cookies = parser.parse( host, conn.getPort(), @@ -1530,9 +1532,6 @@ for (int j = 0; j < cookies.length; j++) { Cookie cookie = cookies[j]; try { - System.out.println("(jain) Cookie [" + j + "]: " + - cookies[j].getName() + " = " + - cookies[j].getValue() + " has version " + cookies[j].getVersion()); parser.validate( host, conn.getPort(), Index: src/java/org/apache/commons/httpclient/Header.java =================================================================== --- src/java/org/apache/commons/httpclient/Header.java (revision 264062) +++ src/java/org/apache/commons/httpclient/Header.java (working copy) @@ -46,10 +46,6 @@ */ private boolean isAutogenerated = false; - public static String SET_COOKIE_KEY = "set-cookie"; - - public static String SET_COOKIE2_KEY = "set-cookie2"; - /** * Default constructor. */