@Test public void testRedirectsDoNotIncludeTooManyCookies() throws Exception { MockWebServer redirectTarget = new MockWebServer(); redirectTarget.enqueue(new MockResponse().setBody("A")); redirectTarget.start(); MockWebServer redirectSource = new MockWebServer(); redirectSource.enqueue( new MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .addHeader("Location: " + redirectTarget.url("/"))); redirectSource.start(); CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); HttpCookie cookie = new HttpCookie("c", "cookie"); cookie.setDomain(redirectSource.getHostName()); cookie.setPath("/"); String portList = Integer.toString(redirectSource.getPort()); cookie.setPortlist(portList); cookieManager.getCookieStore().add(redirectSource.url("/").uri(), cookie); client.setCookieJar(new JavaNetCookieJar(cookieManager)); get(redirectSource.url("/")); RecordedRequest request = redirectSource.takeRequest(); assertEquals("c=cookie", request.getHeader("Cookie")); for (String header : redirectTarget.takeRequest().getHeaders().names()) { if (header.startsWith("Cookie")) { fail(header); } } }
@Test public void testNetscapeResponse() throws Exception { CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); client.setCookieJar(new JavaNetCookieJar(cookieManager)); MockWebServer server = new MockWebServer(); server.start(); HttpUrl urlWithIpAddress = urlWithIpAddress(server, "/path/foo"); server.enqueue( new MockResponse() .addHeader( "Set-Cookie: a=android; " + "expires=Fri, 31-Dec-9999 23:59:59 GMT; " + "path=/path; " + "domain=" + urlWithIpAddress.host() + "; " + "secure")); get(urlWithIpAddress); List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies(); assertEquals(1, cookies.size()); HttpCookie cookie = cookies.get(0); assertEquals("a", cookie.getName()); assertEquals("android", cookie.getValue()); assertEquals(null, cookie.getComment()); assertEquals(null, cookie.getCommentURL()); assertEquals(false, cookie.getDiscard()); assertTrue(cookie.getMaxAge() > 100000000000L); assertEquals("/path", cookie.getPath()); assertEquals(true, cookie.getSecure()); assertEquals(0, cookie.getVersion()); }
private void removeFromPersistence(URI uri, List<HttpCookie> cookiesToRemove) { SharedPreferences.Editor editor = sharedPreferences.edit(); for (HttpCookie cookieToRemove : cookiesToRemove) { editor.remove(uri.toString() + SP_KEY_DELIMITER + cookieToRemove.getName()); } editor.apply(); }
@Override public Map<String, List<String>> get( final URI uri, final Map<String, List<String>> requestHeaders) { // pre-condition check if (uri == null || requestHeaders == null) { throw new IllegalArgumentException("Argument is null"); } final Map<String, List<String>> cookieMap = new java.util.HashMap<String, List<String>>(); // if there's no default CookieStore, no way for us to get any cookie if (getCookieStore() == null) { return Collections.unmodifiableMap(cookieMap); } final List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>(); final List<String> auth = requestHeaders.get(StandardAuthenticationProviderWithUserInHeaders.USER_FOR_COOKIE); for (final HttpCookie cookie : getCookieStore().get(uri)) { // apply path-matches rule (RFC 2965 sec. 3.3.4) if (pathMatches(uri.getPath(), cookie.getPath()) && isSameAuth(auth, cookie)) { cookies.add(cookie); } } lastAuth.set(auth); // apply sort rule (RFC 2965 sec. 3.3.4) final List<String> cookieHeader = sortByPath(cookies); cookieMap.put("Cookie", cookieHeader); return Collections.unmodifiableMap(cookieMap); }
// Private version of parse() that will store the original header used to // create the cookie, in the cookie itself. This can be useful for filtering // Set-Cookie[2] headers, using the internal parsing logic defined in this // class. private static List<HttpCookie> parse(String header, boolean retainHeader) { int version = guessCookieVersion(header); // if header start with set-cookie or set-cookie2, strip it off if (startsWithIgnoreCase(header, SET_COOKIE2)) { header = header.substring(SET_COOKIE2.length()); } else if (startsWithIgnoreCase(header, SET_COOKIE)) { header = header.substring(SET_COOKIE.length()); } List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>(); // The Netscape cookie may have a comma in its expires attribute, // while the comma is the delimiter in rfc 2965/2109 cookie header string. // so the parse logic is slightly different if (version == 0) { // Netscape draft cookie HttpCookie cookie = parseInternal(header, retainHeader); cookie.setVersion(0); cookies.add(cookie); } else { // rfc2965/2109 cookie // if header string contains more than one cookie, // it'll separate them with comma List<String> cookieStrings = splitMultiCookies(header); for (String cookieStr : cookieStrings) { HttpCookie cookie = parseInternal(cookieStr, retainHeader); cookie.setVersion(1); cookies.add(cookie); } } return cookies; }
/** * Sets cookies according to uri and responseHeaders * * @param uri the specified uri * @param responseHeaders a list of request headers * @throws IOException if some error of I/O operation occurs */ @Override public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException { if (uri == null || responseHeaders == null) { throw new IllegalArgumentException(); } // parse and construct cookies according to the map List<HttpCookie> cookies = parseCookie(responseHeaders); for (HttpCookie cookie : cookies) { // if the cookie doesn't have a domain, set one. The policy will do validation. if (cookie.getDomain() == null) { cookie.setDomain(uri.getHost()); } // if the cookie doesn't have a path, set one. If it does, validate it. if (cookie.getPath() == null) { cookie.setPath(pathToCookiePath(uri.getPath())); } else if (!HttpCookie.pathMatches(cookie, uri)) { continue; } // if the cookie has the placeholder port list "", set the port. Otherwise validate it. if ("".equals(cookie.getPortlist())) { cookie.setPortlist(Integer.toString(uri.getEffectivePort())); } else if (cookie.getPortlist() != null && !HttpCookie.portMatches(cookie, uri)) { continue; } // if the cookie conforms to the policy, add it into the store if (policy.shouldAccept(uri, cookie)) { store.add(uri, cookie); } } }
private List<HttpCookie> getValidCookies(URI uri) { List<HttpCookie> targetCookies = new ArrayList<HttpCookie>(); // If the stored URI does not have a path then it must match any URI in // the same domain for (URI storedUri : allCookies.keySet()) { // Check ith the domains match according to RFC 6265 if (checkDomainsMatch(storedUri.getHost(), uri.getHost())) { // Check if the paths match according to RFC 6265 if (checkPathsMatch(storedUri.getPath(), uri.getPath())) { targetCookies.addAll(allCookies.get(storedUri)); } } } // Check it there are expired cookies and remove them if (!targetCookies.isEmpty()) { List<HttpCookie> cookiesToRemoveFromPersistence = new ArrayList<HttpCookie>(); for (Iterator<HttpCookie> it = targetCookies.iterator(); it.hasNext(); ) { HttpCookie currentCookie = it.next(); if (currentCookie.hasExpired()) { cookiesToRemoveFromPersistence.add(currentCookie); it.remove(); } } if (!cookiesToRemoveFromPersistence.isEmpty()) { removeFromPersistence(uri, cookiesToRemoveFromPersistence); } } return targetCookies; }
@Override public void beforeRequest(Map<String, List<String>> headers) { CookieStore cookieStore = getCookieStore(); List<HttpCookie> cookies = cookieStore.get(URI.create(getURL())); if (!cookies.isEmpty()) { List<String> cookieHeader = headers.get("Cookie"); if (cookieHeader == null) cookieHeader = headers.get("cookie"); if (cookieHeader == null) headers.put("Cookie", cookieHeader = new ArrayList<>()); for (HttpCookie cookie : cookies) cookieHeader.add(cookie.getName() + "=" + cookie.getValue()); } }
public HttpCookie toHttpCookie() { HttpCookie cookie = new HttpCookie(name, value); cookie.setComment(comment); cookie.setCommentURL(commentURL); cookie.setDiscard(discard); cookie.setDomain(domain); cookie.setMaxAge((expiry - System.currentTimeMillis()) / 1000L); cookie.setPath(path); cookie.setPortlist(portList); cookie.setSecure(secure); cookie.setVersion(version); return cookie; }
public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException { // pre-condition check if (uri == null || requestHeaders == null) { throw new IllegalArgumentException("Argument is null"); } Map<String, List<String>> cookieMap = new java.util.HashMap<String, List<String>>(); // if there's no default CookieStore, no way for us to get any cookie if (cookieJar == null) return Collections.unmodifiableMap(cookieMap); boolean secureLink = "https".equalsIgnoreCase(uri.getScheme()); List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>(); String path = uri.getPath(); if (path == null || path.isEmpty()) { path = "/"; } for (HttpCookie cookie : cookieJar.get(uri)) { // apply path-matches rule (RFC 2965 sec. 3.3.4) // and check for the possible "secure" tag (i.e. don't send // 'secure' cookies over unsecure links) if (pathMatches(path, cookie.getPath()) && (secureLink || !cookie.getSecure())) { // Enforce httponly attribute if (cookie.isHttpOnly()) { String s = uri.getScheme(); if (!"http".equalsIgnoreCase(s) && !"https".equalsIgnoreCase(s)) { continue; } } // Let's check the authorize port list if it exists String ports = cookie.getPortlist(); if (ports != null && !ports.isEmpty()) { int port = uri.getPort(); if (port == -1) { port = "https".equals(uri.getScheme()) ? 443 : 80; } if (isInPortList(ports, port)) { cookies.add(cookie); } } else { cookies.add(cookie); } } } // apply sort rule (RFC 2965 sec. 3.3.4) List<String> cookieHeader = sortByPath(cookies); cookieMap.put("Cookie", cookieHeader); return Collections.unmodifiableMap(cookieMap); }
/** * Test the equality of two http cookies. * * <p>The result is <tt>true</tt> only if two cookies come from same domain (case-insensitive), * have same name (case-insensitive), and have same path (case-sensitive). * * @return <tt>true</tt> if 2 http cookies equal to each other; otherwise, <tt>false</tt> */ @Override public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof HttpCookie)) return false; HttpCookie other = (HttpCookie) obj; // One http cookie equals to another cookie (RFC 2965 sec. 3.3.3) if: // 1. they come from same domain (case-insensitive), // 2. have same name (case-insensitive), // 3. and have same path (case-sensitive). return equalsIgnoreCase(getName(), other.getName()) && equalsIgnoreCase(getDomain(), other.getDomain()) && Objects.equals(getPath(), other.getPath()); }
@Test public void testRfc2109Response() throws Exception { CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); client.setCookieJar(new JavaNetCookieJar(cookieManager)); MockWebServer server = new MockWebServer(); server.start(); HttpUrl urlWithIpAddress = urlWithIpAddress(server, "/path/foo"); server.enqueue( new MockResponse() .addHeader( "Set-Cookie: a=android; " + "Comment=this cookie is delicious; " + "Domain=" + urlWithIpAddress.host() + "; " + "Max-Age=60; " + "Path=/path; " + "Secure; " + "Version=1")); get(urlWithIpAddress); List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies(); assertEquals(1, cookies.size()); HttpCookie cookie = cookies.get(0); assertEquals("a", cookie.getName()); assertEquals("android", cookie.getValue()); assertEquals(null, cookie.getCommentURL()); assertEquals(false, cookie.getDiscard()); assertEquals(60.0, cookie.getMaxAge(), 1.0); // Converting to a fixed date can cause rounding! assertEquals("/path", cookie.getPath()); assertEquals(true, cookie.getSecure()); }
/* * sort cookies with respect to their path: those with more specific Path attributes * precede those with less specific, as defined in RFC 2965 sec. 3.3.4 */ private List<String> sortByPath(List<HttpCookie> cookies) { Collections.sort(cookies, new CookiePathComparator()); List<String> cookieHeader = new java.util.ArrayList<String>(); for (HttpCookie cookie : cookies) { // Netscape cookie spec and RFC 2965 have different format of Cookie // header; RFC 2965 requires a leading $Version="1" string while Netscape // does not. // The workaround here is to add a $Version="1" string in advance if (cookies.indexOf(cookie) == 0 && cookie.getVersion() > 0) { cookieHeader.add("$Version=\"1\""); } cookieHeader.add(cookie.toString()); } return cookieHeader; }
@Override public NettyHttpResponse cookie(HttpCookie httpCookie) { Cookie nettyCookie = new DefaultCookie(httpCookie.getName(), httpCookie.getValue()); nettyCookie.setDomain(httpCookie.getDomain()); nettyCookie.setPath(httpCookie.getPath()); nettyCookie.setSecure(httpCookie.getSecure()); if (0 <= (int) httpCookie.getMaxAge()) nettyCookie.setMaxAge((int) httpCookie.getMaxAge()); nettyCookie.setVersion(httpCookie.getVersion()); nettyCookie.setDiscard(httpCookie.getDiscard()); nettyCookie.setHttpOnly(true); CookieEncoder encoder = new CookieEncoder(true); encoder.addCookie(nettyCookie); return header(HttpHeaders.Names.SET_COOKIE, encoder.encode()); }
private void saveToPersistence(URI uri, HttpCookie cookie) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString( uri.toString() + SP_KEY_DELIMITER + cookie.getName(), new SerializableHttpCookie().encode(cookie)); editor.apply(); }
/** * Searches and gets all cookies in the cache by the specified uri in the request header. * * @param uri the specified uri to search for * @param requestHeaders a list of request headers * @return a map that record all such cookies, the map is unchangeable * @throws IOException if some error of I/O operation occurs */ @Override public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException { if (uri == null || requestHeaders == null) { throw new IllegalArgumentException(); } List<HttpCookie> result = new ArrayList<HttpCookie>(); for (HttpCookie cookie : store.get(uri)) { if (HttpCookie.pathMatches(cookie, uri) && HttpCookie.secureMatches(cookie, uri) && HttpCookie.portMatches(cookie, uri)) { result.add(cookie); } } return cookiesToHeaders(result); }
public void loadState(Activity a) { SharedPreferences prefs = a.getSharedPreferences("cookies", 0); String cookie = prefs.getString("RevTK", "no value"); Log.v(DEBUG_TAG, "loaded RevTK cookie: " + cookie); HttpCookie revTKCookie = new HttpCookie("RevTK", cookie); revTKCookie.setVersion(0); revTKCookie.setDomain("kanji.koohii.com"); try { if (!(cookie.equals("no value") || revTKCookie.hasExpired())) { cm.getCookieStore().add(new URI("http://kanji.koohii.com/"), revTKCookie); loggedIn = true; } } catch (URISyntaxException e) { // should not happen e.printStackTrace(); } }
/** * Convert a request header to OkHttp's cookies via {@link HttpCookie}. That extra step handles * multiple cookies in a single request header, which {@link Cookie#parse} doesn't support. */ private List<Cookie> decodeHeaderAsJavaNetCookies(HttpUrl url, String header) { List<HttpCookie> javaNetCookies; try { javaNetCookies = HttpCookie.parse(header); } catch (IllegalArgumentException e) { // Unfortunately sometimes java.net gives a Cookie like "$Version=1" which it can't parse! Internal.logger.log(WARNING, "Parsing request cookie failed for " + url.resolve("/..."), e); return Collections.emptyList(); } List<Cookie> result = new ArrayList<>(); for (HttpCookie javaNetCookie : javaNetCookies) { result.add( new Cookie.Builder() .name(javaNetCookie.getName()) .value(javaNetCookie.getValue()) .domain(url.host()) .build()); } return result; }
public void saveState(Activity a) { Log.v(DEBUG_TAG, "saveState() requested"); if (!loggedIn) { return; } Log.v(DEBUG_TAG, "saving state"); SharedPreferences prefs = a.getSharedPreferences("cookies", 0); SharedPreferences.Editor editor = prefs.edit(); for (HttpCookie cookie : cm.getCookieStore().getCookies()) { if (cookie.getName().equals("RevTK")) { Log.v(DEBUG_TAG, "put RevTK cookie"); editor.putString(cookie.getName(), cookie.getValue()); break; } } editor.apply(); }
@Override public int compare(final HttpCookie c1, final HttpCookie c2) { if (c1 == c2) { return 0; } if (c1 == null) { return -1; } if (c2 == null) { return 1; } // path rule only applies to the cookies with same name if (!c1.getName().equals(c2.getName())) { return 0; } // those with more specific Path attributes precede those with less // specific if (c1.getPath().startsWith(c2.getPath())) { return -1; } else if (c2.getPath().startsWith(c1.getPath())) { return 1; } else { return 0; } }
@Test public void testRedirectsDoNotIncludeTooManyCookies() throws Exception { MockWebServer redirectTarget = new MockWebServer(); redirectTarget.enqueue(new MockResponse().setBody("A")); redirectTarget.play(); MockWebServer redirectSource = new MockWebServer(); redirectSource.enqueue( new MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .addHeader("Location: " + redirectTarget.getUrl("/"))); redirectSource.play(); CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); HttpCookie cookie = new HttpCookie("c", "cookie"); cookie.setDomain(redirectSource.getCookieDomain()); cookie.setPath("/"); String portList = Integer.toString(redirectSource.getPort()); cookie.setPortlist(portList); cookieManager.getCookieStore().add(redirectSource.getUrl("/").toURI(), cookie); CookieHandler.setDefault(cookieManager); get(redirectSource, "/"); RecordedRequest request = redirectSource.takeRequest(); assertContains( request.getHeaders(), "Cookie: $Version=\"1\"; " + "c=\"cookie\";$Path=\"/\";$Domain=\"" + redirectSource.getCookieDomain() + "\";$Port=\"" + portList + "\""); for (String header : redirectTarget.takeRequest().getHeaders()) { if (header.startsWith("Cookie")) { fail(header); } } }
@Test public void testSendingCookiesFromStore() throws Exception { MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse()); server.play(); CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); HttpCookie cookieA = new HttpCookie("a", "android"); cookieA.setDomain(server.getCookieDomain()); cookieA.setPath("/"); cookieManager.getCookieStore().add(server.getUrl("/").toURI(), cookieA); HttpCookie cookieB = new HttpCookie("b", "banana"); cookieB.setDomain(server.getCookieDomain()); cookieB.setPath("/"); cookieManager.getCookieStore().add(server.getUrl("/").toURI(), cookieB); CookieHandler.setDefault(cookieManager); get(server, "/"); RecordedRequest request = server.takeRequest(); List<String> receivedHeaders = request.getHeaders(); assertContains( receivedHeaders, "Cookie: $Version=\"1\"; " + "a=\"android\";$Path=\"/\";$Domain=\"" + server.getCookieDomain() + "\"; " + "b=\"banana\";$Path=\"/\";$Domain=\"" + server.getCookieDomain() + "\""); }
// You wouldn't have thought it was that convoluted, but it is. private List<HttpCookie> cookies(URLConnection urlConnection) { List<HttpCookie> cookies = new ArrayList<HttpCookie>(); Map<String, List<String>> headerFields = urlConnection.getHeaderFields(); for (Map.Entry<String, List<String>> header : headerFields.entrySet()) { if ("Set-Cookie".equals(header.getKey())) { List<String> value = header.getValue(); for (String cookie : value) { cookies.addAll(HttpCookie.parse(cookie)); } } } return sort(cookies); }
/** * Get the real URI from the cookie "domain" and "path" attributes, if they are not set then uses * the URI provided (coming from the response) * * @param uri * @param cookie * @return */ private static URI cookieUri(URI uri, HttpCookie cookie) { URI cookieUri = uri; if (cookie.getDomain() != null) { // Remove the starting dot character of the domain, if exists (e.g: // .domain.com -> domain.com) String domain = cookie.getDomain(); if (domain.charAt(0) == '.') { domain = domain.substring(1); } try { cookieUri = new URI( uri.getScheme() == null ? "http" : uri.getScheme(), domain, cookie.getPath() == null ? "/" : cookie.getPath(), null); } catch (URISyntaxException e) { Log.w(TAG, e); } } return cookieUri; }
// java.net.HttpCookie is mutable public void useMutableInput(HttpCookie cookie) { if (cookie == null) { throw new NullPointerException(); } // Check whether cookie has expired if (cookie.hasExpired()) { // Cookie is no longer valid; handle condition by throwing an // exception } // Cookie may have expired since time of check doLogic(cookie); }
private static Map<String, List<String>> cookiesToHeaders(List<HttpCookie> cookies) { if (cookies.isEmpty()) { return Collections.emptyMap(); } StringBuilder result = new StringBuilder(); // If all cookies are version 1, add a version 1 header. No header for version 0 cookies. int minVersion = 1; for (HttpCookie cookie : cookies) { minVersion = Math.min(minVersion, cookie.getVersion()); } if (minVersion == 1) { result.append("$Version=\"1\"; "); } result.append(cookies.get(0).toString()); for (int i = 1; i < cookies.size(); i++) { result.append("; ").append(cookies.get(i).toString()); } return Collections.singletonMap("Cookie", Collections.singletonList(result.toString())); }
public boolean shouldAccept(URI uri, HttpCookie cookie) { String host; try { host = InetAddress.getByName(uri.getHost()).getCanonicalHostName(); } catch (UnknownHostException e) { host = uri.getHost(); } for (int i = 0; i < blacklist.length; i++) { if (HttpCookie.domainMatches(blacklist[i], host)) { return false; } } return CookiePolicy.ACCEPT_ORIGINAL_SERVER.shouldAccept(uri, cookie); }
public int compare(HttpCookie c1, HttpCookie c2) { if (c1 == c2) return 0; if (c1 == null) return -1; if (c2 == null) return 1; // path rule only applies to the cookies with same name if (!c1.getName().equals(c2.getName())) return 0; // those with more specific Path attributes precede those with less specific if (c1.getPath().startsWith(c2.getPath())) return -1; else if (c2.getPath().startsWith(c1.getPath())) return 1; else return 0; }
public HttpCookie toHttpCookie() { HttpCookie cookie = null; long expiration = 0; try { expiration = getExpiration(); } catch (Exception e) { } String domain = getDomain(); String path = getPath(); int secureFlag = 0; try { secureFlag = getSecure(); } catch (Exception e) { } // Currently no use or need to use the HTTP-only flag in cookies for now cookie = new HttpCookie(getName(), getValue()); cookie.setVersion(0); long currentTimeInSeconds = Calendar.getInstance().getTimeInMillis() / 1000; if (currentTimeInSeconds < expiration) { long maxAge = expiration - currentTimeInSeconds; cookie.setMaxAge(maxAge); } if (secureFlag != 0) { cookie.setSecure(true); } if (domain != null && domain.length() > 0) { cookie.setDomain(domain); if (path != null && path.length() > 0) { cookie.setPath(path); } else { cookie.setPath("/"); } return cookie; } return null; }
private static List<HttpCookie> parseCookie(Map<String, List<String>> responseHeaders) { List<HttpCookie> cookies = new ArrayList<HttpCookie>(); for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) { String key = entry.getKey(); // Only "Set-cookie" and "Set-cookie2" pair will be parsed if (key != null && (key.equalsIgnoreCase(VERSION_ZERO_HEADER) || key.equalsIgnoreCase(VERSION_ONE_HEADER))) { // parse list elements one by one for (String cookieStr : entry.getValue()) { try { for (HttpCookie cookie : HttpCookie.parse(cookieStr)) { cookies.add(cookie); } } catch (IllegalArgumentException ignored) { // this string is invalid, jump to the next one. } } } } return cookies; }