/* * Copyright (C) 2015 Square, Inc. * * 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. */ package okhttp3; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; import okhttp3.internal.Util; import okhttp3.internal.http.HttpDate; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public final class CookieTest { HttpUrl url = HttpUrl.parse("https://example.com/"); @Test public void simpleCookie() throws Exception { Cookie cookie = Cookie.parse(url, "SID=31d4d96e407aad42"); assertEquals("SID=31d4d96e407aad42; path=/", cookie.toString()); } @Test public void noEqualsSign() throws Exception { assertNull(Cookie.parse(url, "foo")); assertNull(Cookie.parse(url, "foo; Path=/")); } @Test public void emptyName() throws Exception { assertNull(Cookie.parse(url, "=b")); assertNull(Cookie.parse(url, " =b")); assertNull(Cookie.parse(url, "\r\t \n=b")); } @Test public void spaceInName() throws Exception { assertEquals("a b", Cookie.parse(url, "a b=cd").name()); } @Test public void spaceInValue() throws Exception { assertEquals("c d", Cookie.parse(url, "ab=c d").value()); } @Test public void trimLeadingAndTrailingWhitespaceFromName() throws Exception { assertEquals("a", Cookie.parse(url, " a=b").name()); assertEquals("a", Cookie.parse(url, "a =b").name()); assertEquals("a", Cookie.parse(url, "\r\t \na\n\t \n=b").name()); } @Test public void emptyValue() throws Exception { assertEquals("", Cookie.parse(url, "a=").value()); assertEquals("", Cookie.parse(url, "a= ").value()); assertEquals("", Cookie.parse(url, "a=\r\t \n").value()); } @Test public void trimLeadingAndTrailingWhitespaceFromValue() throws Exception { assertEquals("", Cookie.parse(url, "a= ").value()); assertEquals("b", Cookie.parse(url, "a= b").value()); assertEquals("b", Cookie.parse(url, "a=b ").value()); assertEquals("b", Cookie.parse(url, "a=\r\t \nb\n\t \n").value()); } @Test public void invalidCharacters() throws Exception { assertEquals(null, Cookie.parse(url, "a\u0000b=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u0000d")); assertEquals(null, Cookie.parse(url, "a\u0001b=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u0001d")); assertEquals(null, Cookie.parse(url, "a\u0009b=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u0009d")); assertEquals(null, Cookie.parse(url, "a\u001fb=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u001fd")); assertEquals(null, Cookie.parse(url, "a\u007fb=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u007fd")); assertEquals(null, Cookie.parse(url, "a\u0080b=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u0080d")); assertEquals(null, Cookie.parse(url, "a\u00ffb=cd")); assertEquals(null, Cookie.parse(url, "ab=c\u00ffd")); } @Test public void maxAge() throws Exception { assertEquals(51000L, Cookie.parse(50000L, url, "a=b; Max-Age=1").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(50000L, url, "a=b; Max-Age=9223372036854724").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(50000L, url, "a=b; Max-Age=9223372036854725").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(50000L, url, "a=b; Max-Age=9223372036854726").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(9223372036854773807L, url, "a=b; Max-Age=1").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(9223372036854773807L, url, "a=b; Max-Age=2").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(9223372036854773807L, url, "a=b; Max-Age=3").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(50000L, url, "a=b; Max-Age=10000000000000000000").expiresAt()); } @Test public void maxAgeNonPositive() throws Exception { assertEquals(Long.MIN_VALUE, Cookie.parse(50000L, url, "a=b; Max-Age=-1").expiresAt()); assertEquals(Long.MIN_VALUE, Cookie.parse(50000L, url, "a=b; Max-Age=0").expiresAt()); assertEquals(Long.MIN_VALUE, Cookie.parse(50000L, url, "a=b; Max-Age=-9223372036854775808").expiresAt()); assertEquals(Long.MIN_VALUE, Cookie.parse(50000L, url, "a=b; Max-Age=-9223372036854775809").expiresAt()); assertEquals(Long.MIN_VALUE, Cookie.parse(50000L, url, "a=b; Max-Age=-10000000000000000000").expiresAt()); } @Test public void domainAndPath() throws Exception { Cookie cookie = Cookie.parse(url, "SID=31d4d96e407aad42; Path=/; Domain=example.com"); assertEquals("example.com", cookie.domain()); assertEquals("/", cookie.path()); assertFalse(cookie.hostOnly()); assertEquals("SID=31d4d96e407aad42; domain=example.com; path=/", cookie.toString()); } @Test public void secureAndHttpOnly() throws Exception { Cookie cookie = Cookie.parse(url, "SID=31d4d96e407aad42; Path=/; Secure; HttpOnly"); assertTrue(cookie.secure()); assertTrue(cookie.httpOnly()); assertEquals("SID=31d4d96e407aad42; path=/; secure; httponly", cookie.toString()); } @Test public void expiresDate() throws Exception { assertEquals(date("1970-01-01T00:00:00.000+0000"), new Date( Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 00:00:00 GMT").expiresAt())); assertEquals(date("2021-06-09T10:18:14.000+0000"), new Date( Cookie.parse(url, "a=b; Expires=Wed, 09 Jun 2021 10:18:14 GMT").expiresAt())); assertEquals(date("1994-11-06T08:49:37.000+0000"), new Date( Cookie.parse(url, "a=b; Expires=Sun, 06 Nov 1994 08:49:37 GMT").expiresAt())); } @Test public void awkwardDates() throws Exception { assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 70 00:00:00 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 01 January 1970 00:00:00 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 01 Janucember 1970 00:00:00 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 1 Jan 1970 00:00:00 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 0:00:00 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 00:0:00 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 00:00:0 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=00:00:00 Thu, 01 Jan 1970 GMT").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=00:00:00 1970 Jan 01").expiresAt()); assertEquals(0L, Cookie.parse(url, "a=b; Expires=00:00:00 1970 Jan 1").expiresAt()); } @Test public void invalidYear() throws Exception { assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1600 00:00:00 GMT").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 19999 00:00:00 GMT").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 00:00:00 GMT").expiresAt()); } @Test public void invalidMonth() throws Exception { assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Foo 1970 00:00:00 GMT").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Foocember 1970 00:00:00 GMT").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 1970 00:00:00 GMT").expiresAt()); } @Test public void invalidDayOfMonth() throws Exception { assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 32 Jan 1970 00:00:00 GMT").expiresAt()); assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, Jan 1970 00:00:00 GMT").expiresAt()); } @Test public void invalidHour() throws Exception { assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 24:00:00 GMT").expiresAt()); } @Test public void invalidMinute() throws Exception { assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 00:60:00 GMT").expiresAt()); } @Test public void invalidSecond() throws Exception { assertEquals(HttpDate.MAX_DATE, Cookie.parse(url, "a=b; Expires=Thu, 01 Jan 1970 00:00:60 GMT").expiresAt()); } @Test public void domainMatches() throws Exception { Cookie cookie = Cookie.parse(url, "a=b; domain=example.com"); assertTrue(cookie.matches(HttpUrl.parse("http://example.com"))); assertTrue(cookie.matches(HttpUrl.parse("http://www.example.com"))); assertFalse(cookie.matches(HttpUrl.parse("http://square.com"))); } /** If no domain is present, match only the origin domain. */ @Test public void domainMatchesNoDomain() throws Exception { Cookie cookie = Cookie.parse(url, "a=b"); assertTrue(cookie.matches(HttpUrl.parse("http://example.com"))); assertFalse(cookie.matches(HttpUrl.parse("http://www.example.com"))); assertFalse(cookie.matches(HttpUrl.parse("http://square.com"))); } /** Ignore an optional leading `.` in the domain. */ @Test public void domainMatchesIgnoresLeadingDot() throws Exception { Cookie cookie = Cookie.parse(url, "a=b; domain=.example.com"); assertTrue(cookie.matches(HttpUrl.parse("http://example.com"))); assertTrue(cookie.matches(HttpUrl.parse("http://www.example.com"))); assertFalse(cookie.matches(HttpUrl.parse("http://square.com"))); } /** Ignore the entire attribute if the domain ends with `.`. */ @Test public void domainIgnoredWithTrailingDot() throws Exception { Cookie cookie = Cookie.parse(url, "a=b; domain=example.com."); assertTrue(cookie.matches(HttpUrl.parse("http://example.com"))); assertFalse(cookie.matches(HttpUrl.parse("http://www.example.com"))); assertFalse(cookie.matches(HttpUrl.parse("http://square.com"))); } @Test public void idnDomainMatches() throws Exception { Cookie cookie = Cookie.parse(HttpUrl.parse("http://☃.net/"), "a=b; domain=☃.net"); assertTrue(cookie.matches(HttpUrl.parse("http://☃.net/"))); assertTrue(cookie.matches(HttpUrl.parse("http://xn--n3h.net/"))); assertTrue(cookie.matches(HttpUrl.parse("http://www.☃.net/"))); assertTrue(cookie.matches(HttpUrl.parse("http://www.xn--n3h.net/"))); } @Test public void punycodeDomainMatches() throws Exception { Cookie cookie = Cookie.parse(HttpUrl.parse("http://xn--n3h.net/"), "a=b; domain=xn--n3h.net"); assertTrue(cookie.matches(HttpUrl.parse("http://☃.net/"))); assertTrue(cookie.matches(HttpUrl.parse("http://xn--n3h.net/"))); assertTrue(cookie.matches(HttpUrl.parse("http://www.☃.net/"))); assertTrue(cookie.matches(HttpUrl.parse("http://www.xn--n3h.net/"))); } @Test public void domainMatchesIpAddress() throws Exception { HttpUrl urlWithIp = HttpUrl.parse("http://123.45.234.56/"); assertNull(Cookie.parse(urlWithIp, "a=b; domain=234.56")); assertEquals("123.45.234.56", Cookie.parse(urlWithIp, "a=b; domain=123.45.234.56").domain()); } /** * These public suffixes were selected by inspecting the publicsuffix.org list. It's possible they * may change in the future. If this test begins to fail, please double check they are still * present in the public suffix list. */ @Test public void domainIsPublicSuffix() { HttpUrl ascii = HttpUrl.parse("https://foo1.foo.bar.elb.amazonaws.com"); assertNotNull(Cookie.parse(ascii, "a=b; domain=foo.bar.elb.amazonaws.com")); assertNull(Cookie.parse(ascii, "a=b; domain=bar.elb.amazonaws.com")); assertNull(Cookie.parse(ascii, "a=b; domain=com")); HttpUrl unicode = HttpUrl.parse("https://長.長.長崎.jp"); assertNotNull(Cookie.parse(unicode, "a=b; domain=長.長崎.jp")); assertNull(Cookie.parse(unicode, "a=b; domain=長崎.jp")); HttpUrl punycode = HttpUrl.parse("https://xn--ue5a.xn--ue5a.xn--8ltr62k.jp"); assertNotNull(Cookie.parse(punycode, "a=b; domain=xn--ue5a.xn--8ltr62k.jp")); assertNull(Cookie.parse(punycode, "a=b; domain=xn--8ltr62k.jp")); } @Test public void hostOnly() throws Exception { assertTrue(Cookie.parse(url, "a=b").hostOnly()); assertFalse(Cookie.parse(url, "a=b; domain=example.com").hostOnly()); } @Test public void defaultPath() throws Exception { assertEquals("/foo", Cookie.parse(HttpUrl.parse("http://example.com/foo/bar"), "a=b").path()); assertEquals("/foo", Cookie.parse(HttpUrl.parse("http://example.com/foo/"), "a=b").path()); assertEquals("/", Cookie.parse(HttpUrl.parse("http://example.com/foo"), "a=b").path()); assertEquals("/", Cookie.parse(HttpUrl.parse("http://example.com/"), "a=b").path()); } @Test public void defaultPathIsUsedIfPathDoesntHaveLeadingSlash() throws Exception { assertEquals("/foo", Cookie.parse(HttpUrl.parse("http://example.com/foo/bar"), "a=b; path=quux").path()); assertEquals("/foo", Cookie.parse(HttpUrl.parse("http://example.com/foo/bar"), "a=b; path=").path()); } @Test public void pathAttributeDoesntNeedToMatch() throws Exception { assertEquals("/quux", Cookie.parse(HttpUrl.parse("http://example.com/"), "a=b; path=/quux").path()); assertEquals("/quux", Cookie.parse(HttpUrl.parse("http://example.com/foo/bar"), "a=b; path=/quux").path()); } @Test public void httpOnly() throws Exception { assertFalse(Cookie.parse(url, "a=b").httpOnly()); assertTrue(Cookie.parse(url, "a=b; HttpOnly").httpOnly()); } @Test public void secure() throws Exception { assertFalse(Cookie.parse(url, "a=b").secure()); assertTrue(Cookie.parse(url, "a=b; Secure").secure()); } @Test public void maxAgeTakesPrecedenceOverExpires() throws Exception { // Max-Age = 1, Expires = 2. In either order. assertEquals(1000L, Cookie.parse( 0L, url, "a=b; Max-Age=1; Expires=Thu, 01 Jan 1970 00:00:02 GMT").expiresAt()); assertEquals(1000L, Cookie.parse( 0L, url, "a=b; Expires=Thu, 01 Jan 1970 00:00:02 GMT; Max-Age=1").expiresAt()); // Max-Age = 2, Expires = 1. In either order. assertEquals(2000L, Cookie.parse( 0L, url, "a=b; Max-Age=2; Expires=Thu, 01 Jan 1970 00:00:01 GMT").expiresAt()); assertEquals(2000L, Cookie.parse( 0L, url, "a=b; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=2").expiresAt()); } /** If a cookie incorrectly defines multiple 'Max-Age' attributes, the last one defined wins. */ @Test public void lastMaxAgeWins() throws Exception { assertEquals(3000L, Cookie.parse( 0L, url, "a=b; Max-Age=2; Max-Age=4; Max-Age=1; Max-Age=3").expiresAt()); } /** If a cookie incorrectly defines multiple 'Expires' attributes, the last one defined wins. */ @Test public void lastExpiresAtWins() throws Exception { assertEquals(3000L, Cookie.parse(0L, url, "a=b; " + "Expires=Thu, 01 Jan 1970 00:00:02 GMT; " + "Expires=Thu, 01 Jan 1970 00:00:04 GMT; " + "Expires=Thu, 01 Jan 1970 00:00:01 GMT; " + "Expires=Thu, 01 Jan 1970 00:00:03 GMT").expiresAt()); } @Test public void maxAgeOrExpiresMakesCookiePersistent() throws Exception { assertFalse(Cookie.parse(0L, url, "a=b").persistent()); assertTrue(Cookie.parse(0L, url, "a=b; Max-Age=1").persistent()); assertTrue(Cookie.parse(0L, url, "a=b; Expires=Thu, 01 Jan 1970 00:00:01 GMT").persistent()); } @Test public void parseAll() throws Exception { Headers headers = new Headers.Builder() .add("Set-Cookie: a=b") .add("Set-Cookie: c=d") .build(); List<Cookie> cookies = Cookie.parseAll(url, headers); assertEquals(2, cookies.size()); assertEquals("a=b; path=/", cookies.get(0).toString()); assertEquals("c=d; path=/", cookies.get(1).toString()); } @Test public void builder() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .domain("example.com") .build(); assertEquals("a", cookie.name()); assertEquals("b", cookie.value()); assertEquals(HttpDate.MAX_DATE, cookie.expiresAt()); assertEquals("example.com", cookie.domain()); assertEquals("/", cookie.path()); assertFalse(cookie.secure()); assertFalse(cookie.httpOnly()); assertFalse(cookie.persistent()); assertFalse(cookie.hostOnly()); } @Test public void builderNameValidation() throws Exception { try { new Cookie.Builder().name(null); fail(); } catch (NullPointerException expected) { } try { new Cookie.Builder().name(" a "); fail(); } catch (IllegalArgumentException expected) { } } @Test public void builderValueValidation() throws Exception { try { new Cookie.Builder().value(null); fail(); } catch (NullPointerException expected) { } try { new Cookie.Builder().value(" b "); fail(); } catch (IllegalArgumentException expected) { } } @Test public void builderClampsMaxDate() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("example.com") .expiresAt(Long.MAX_VALUE) .build(); assertEquals("a=b; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/", cookie.toString()); } @Test public void builderExpiresAt() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("example.com") .expiresAt(date("1970-01-01T00:00:01.000+0000").getTime()) .build(); assertEquals("a=b; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/", cookie.toString()); } @Test public void builderClampsMinDate() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("example.com") .expiresAt(date("1970-01-01T00:00:00.000+0000").getTime()) .build(); assertEquals("a=b; max-age=0; path=/", cookie.toString()); } @Test public void builderDomainValidation() throws Exception { try { new Cookie.Builder().hostOnlyDomain(null); fail(); } catch (NullPointerException expected) { } try { new Cookie.Builder().hostOnlyDomain("a/b"); fail(); } catch (IllegalArgumentException expected) { } } @Test public void builderDomain() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("squareup.com") .build(); assertEquals("squareup.com", cookie.domain()); assertTrue(cookie.hostOnly()); } @Test public void builderPath() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("example.com") .path("/foo") .build(); assertEquals("/foo", cookie.path()); } @Test public void builderPathValidation() throws Exception { try { new Cookie.Builder().path(null); fail(); } catch (NullPointerException expected) { } try { new Cookie.Builder().path("foo"); fail(); } catch (IllegalArgumentException expected) { } } @Test public void builderSecure() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("example.com") .secure() .build(); assertEquals(true, cookie.secure()); } @Test public void builderHttpOnly() throws Exception { Cookie cookie = new Cookie.Builder() .name("a") .value("b") .hostOnlyDomain("example.com") .httpOnly() .build(); assertEquals(true, cookie.httpOnly()); } @Test public void equalsAndHashCode() throws Exception { List<String> cookieStrings = Arrays.asList( "a=b; Path=/c; Domain=example.com; Max-Age=5; Secure; HttpOnly", "a= ; Path=/c; Domain=example.com; Max-Age=5; Secure; HttpOnly", "a=b; Domain=example.com; Max-Age=5; Secure; HttpOnly", "a=b; Path=/c; Max-Age=5; Secure; HttpOnly", "a=b; Path=/c; Domain=example.com; Secure; HttpOnly", "a=b; Path=/c; Domain=example.com; Max-Age=5; HttpOnly", "a=b; Path=/c; Domain=example.com; Max-Age=5; Secure; " ); for (String stringA : cookieStrings) { Cookie cookieA = Cookie.parse(0, url, stringA); for (String stringB : cookieStrings) { Cookie cookieB = Cookie.parse(0, url, stringB); if (Objects.equals(stringA, stringB)) { assertEquals(cookieA.hashCode(), cookieB.hashCode()); assertEquals(cookieA, cookieB); } else { assertFalse(cookieA.hashCode() == cookieB.hashCode()); assertFalse(cookieA.equals(cookieB)); } } assertFalse(cookieA.equals(null)); } } private Date date(String s) throws ParseException { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); format.setTimeZone(Util.UTC); return format.parse(s); } }