diff options
Diffstat (limited to 'android_webview')
19 files changed, 1638 insertions, 6 deletions
diff --git a/android_webview/DEPS b/android_webview/DEPS index 514d673d..0a18ac3 100644 --- a/android_webview/DEPS +++ b/android_webview/DEPS @@ -13,4 +13,5 @@ include_rules = [ "+chrome/browser/component", "+content/public", "+jni", + "+net", ] diff --git a/android_webview/android_webview.gyp b/android_webview/android_webview.gyp index 1f68dda..42fd686 100644 --- a/android_webview/android_webview.gyp +++ b/android_webview/android_webview.gyp @@ -26,10 +26,14 @@ 'common/android_webview_message_generator.h', 'common/render_view_messages.cc', 'common/render_view_messages.h', + 'browser/net/aw_network_delegate.cc', + 'browser/net/aw_network_delegate.h', 'browser/renderer_host/aw_render_view_host_ext.cc', 'browser/renderer_host/aw_render_view_host_ext.h', 'browser/renderer_host/aw_resource_dispatcher_host_delegate.cc', 'browser/renderer_host/aw_resource_dispatcher_host_delegate.h', + 'browser/aw_cookie_access_policy.cc', + 'browser/aw_cookie_access_policy.h', 'lib/aw_browser_dependency_factory_impl.cc', 'lib/aw_browser_dependency_factory_impl.h', 'lib/aw_content_browser_client.cc', @@ -61,6 +65,7 @@ '<(android_product_out)/obj/lib/libwebview.so', '<(android_product_out)/system/lib/libwebview.so', '<(android_product_out)/symbols/system/lib/libwebview.so', + '<(android_product_out)/symbols/data/data/org.chromium.android_webview/lib/libwebview.so', ], 'action': [ '<(install_binary_script)', diff --git a/android_webview/browser/aw_cookie_access_policy.cc b/android_webview/browser/aw_cookie_access_policy.cc new file mode 100644 index 0000000..4809fdb --- /dev/null +++ b/android_webview/browser/aw_cookie_access_policy.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/browser/aw_cookie_access_policy.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/browser_thread.h" + +using base::AutoLock; +using content::BrowserThread; + +namespace android_webview { + +namespace { +base::LazyInstance<AwCookieAccessPolicy>::Leaky g_lazy_instance; +} // namespace + +AwCookieAccessPolicy::~AwCookieAccessPolicy() { +} + +AwCookieAccessPolicy::AwCookieAccessPolicy() + : allow_access_(false) { +} + +AwCookieAccessPolicy* AwCookieAccessPolicy::GetInstance() { + return g_lazy_instance.Pointer(); +} + +bool AwCookieAccessPolicy::GetGlobalAllowAccess() { + AutoLock lock(lock_); + return allow_access_; +} + +void AwCookieAccessPolicy::SetGlobalAllowAccess(bool allow) { + AutoLock lock(lock_); + allow_access_ = allow; +} + +bool AwCookieAccessPolicy::OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list) { + return GetGlobalAllowAccess(); +} + +bool AwCookieAccessPolicy::OnCanSetCookie(const net::URLRequest& request, + const std::string& cookie_line, + net::CookieOptions* options) { + return GetGlobalAllowAccess(); +} + +bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_view_id) { + return GetGlobalAllowAccess(); +} + +bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url, + const GURL& first_party, + const std::string& cookie_line, + content::ResourceContext* context, + int render_process_id, + int render_view_id, + net::CookieOptions* options) { + return GetGlobalAllowAccess(); +} + +} // namespace android_webview diff --git a/android_webview/browser/aw_cookie_access_policy.h b/android_webview/browser/aw_cookie_access_policy.h new file mode 100644 index 0000000..b25e4f6 --- /dev/null +++ b/android_webview/browser/aw_cookie_access_policy.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ANDROID_WEBVIEW_BROWSER_AW_COOKIE_ACCESS_POLICY_H_ +#define ANDROID_WEBVIEW_BROWSER_AW_COOKIE_ACCESS_POLICY_H_ + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/synchronization/lock.h" +#include "net/cookies/canonical_cookie.h" + +namespace content { +class ResourceContext; +} + +namespace net { +class CookieOptions; +class URLRequest; +} + +class GURL; + +namespace android_webview { + +// Manages the cookie access (both setting and getting) policy for WebView. +class AwCookieAccessPolicy { + public: + static AwCookieAccessPolicy* GetInstance(); + + // These manage the global access state shared across requests regardless of + // source (i.e. network or JavaScript). + bool GetGlobalAllowAccess(); + void SetGlobalAllowAccess(bool allow); + + // These are the functions called when operating over cookies from the + // network. See NetworkDelegate for further descriptions. + bool OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list); + bool OnCanSetCookie(const net::URLRequest& request, + const std::string& cookie_line, + net::CookieOptions* options); + + // These are the functions called when operating over cookies from the + // renderer. See ContentBrowserClient for further descriptions. + bool AllowGetCookie(const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_view_id); + bool AllowSetCookie(const GURL& url, + const GURL& first_party, + const std::string& cookie_line, + content::ResourceContext* context, + int render_process_id, + int render_view_id, + net::CookieOptions* options); + + private: + friend struct base::DefaultLazyInstanceTraits<AwCookieAccessPolicy>; + + AwCookieAccessPolicy(); + ~AwCookieAccessPolicy(); + bool allow_access_; + base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(AwCookieAccessPolicy); +}; + +} // namespace android_webview + +#endif // ANDROID_WEBVIEW_BROWSER_AW_COOKIE_ACCESS_POLICY_H_ diff --git a/android_webview/browser/net/aw_network_delegate.cc b/android_webview/browser/net/aw_network_delegate.cc new file mode 100644 index 0000000..0cbb2e4 --- /dev/null +++ b/android_webview/browser/net/aw_network_delegate.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/browser/net/aw_network_delegate.h" + +#include "android_webview/browser/aw_cookie_access_policy.h" +#include "net/base/net_errors.h" +#include "net/base/completion_callback.h" +#include "net/url_request/url_request.h" + +namespace android_webview { + +AwNetworkDelegate::AwNetworkDelegate() { +} + +AwNetworkDelegate::~AwNetworkDelegate() { +} + +int AwNetworkDelegate::OnBeforeURLRequest( + net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) { + return net::OK; +} + +int AwNetworkDelegate::OnBeforeSendHeaders( + net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) { + return net::OK; +} + +void AwNetworkDelegate::OnSendHeaders(net::URLRequest* request, + const net::HttpRequestHeaders& headers) { +} + +int AwNetworkDelegate::OnHeadersReceived( + net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { + return net::OK; +} + +void AwNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) { +} + +void AwNetworkDelegate::OnResponseStarted(net::URLRequest* request) { +} + +void AwNetworkDelegate::OnRawBytesRead(const net::URLRequest& request, + int bytes_read) { +} + +void AwNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { +} + +void AwNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) { +} + +void AwNetworkDelegate::OnPACScriptError(int line_number, + const string16& error) { +} + +net::NetworkDelegate::AuthRequiredResponse AwNetworkDelegate::OnAuthRequired( + net::URLRequest* request, + const net::AuthChallengeInfo& auth_info, + const AuthCallback& callback, + net::AuthCredentials* credentials) { + return AUTH_REQUIRED_RESPONSE_NO_ACTION; +} + +bool AwNetworkDelegate::OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list) { + return AwCookieAccessPolicy::GetInstance()->OnCanGetCookies(request, + cookie_list); +} + +bool AwNetworkDelegate::OnCanSetCookie(const net::URLRequest& request, + const std::string& cookie_line, + net::CookieOptions* options) { + return AwCookieAccessPolicy::GetInstance()->OnCanSetCookie(request, + cookie_line, + options); +} + +bool AwNetworkDelegate::OnCanAccessFile(const net::URLRequest& request, + const FilePath& path) const { + return true; +} + +bool AwNetworkDelegate::OnCanThrottleRequest( + const net::URLRequest& request) const { + return false; +} + +int AwNetworkDelegate::OnBeforeSocketStreamConnect( + net::SocketStream* stream, + const net::CompletionCallback& callback) { + return net::OK; +} + +void AwNetworkDelegate::OnRequestWaitStateChange(const net::URLRequest& request, + RequestWaitState state) { +} + +} // namespace android_webview diff --git a/android_webview/browser/net/aw_network_delegate.h b/android_webview/browser/net/aw_network_delegate.h new file mode 100644 index 0000000..c4505f9 --- /dev/null +++ b/android_webview/browser/net/aw_network_delegate.h @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ANDROID_WEBVIEW_BROWSER_NET_AW_NETWORK_DELEGATE_H_ +#define ANDROID_WEBVIEW_BROWSER_NET_AW_NETWORK_DELEGATE_H_ + +#include "base/basictypes.h" +#include "net/base/network_delegate.h" + +namespace android_webview { + +// WebView's implementation of the NetworkDelegate. +class AwNetworkDelegate : public net::NetworkDelegate { + public: + AwNetworkDelegate(); + virtual ~AwNetworkDelegate(); + + private: + // NetworkDelegate implementation. + virtual int OnBeforeURLRequest(net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) OVERRIDE; + virtual int OnBeforeSendHeaders(net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) OVERRIDE; + virtual void OnSendHeaders(net::URLRequest* request, + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual int OnHeadersReceived( + net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers) + OVERRIDE; + virtual void OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnRawBytesRead(const net::URLRequest& request, + int bytes_read) OVERRIDE; + virtual void OnCompleted(net::URLRequest* request, bool started) OVERRIDE; + virtual void OnURLRequestDestroyed(net::URLRequest* request) OVERRIDE; + virtual void OnPACScriptError(int line_number, + const string16& error) OVERRIDE; + virtual net::NetworkDelegate::AuthRequiredResponse OnAuthRequired( + net::URLRequest* request, + const net::AuthChallengeInfo& auth_info, + const AuthCallback& callback, + net::AuthCredentials* credentials) OVERRIDE; + virtual bool OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list) OVERRIDE; + virtual bool OnCanSetCookie(const net::URLRequest& request, + const std::string& cookie_line, + net::CookieOptions* options) OVERRIDE; + virtual bool OnCanAccessFile(const net::URLRequest& request, + const FilePath& path) const OVERRIDE; + virtual bool OnCanThrottleRequest( + const net::URLRequest& request) const OVERRIDE; + virtual int OnBeforeSocketStreamConnect( + net::SocketStream* stream, + const net::CompletionCallback& callback) OVERRIDE; + virtual void OnRequestWaitStateChange(const net::URLRequest& request, + RequestWaitState state) OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(AwNetworkDelegate); +}; + +} // namespace android_webview + +#endif // ANDROID_WEBVIEW_BROWSER_NET_AW_NETWORK_DELEGATE_H_ diff --git a/android_webview/build/install_binary b/android_webview/build/install_binary index 24aaa83..ad63e0d 100755 --- a/android_webview/build/install_binary +++ b/android_webview/build/install_binary @@ -7,7 +7,7 @@ if [ "$3" = "" ] then - echo "Usage: install_binary path/to/binary path/to/target1 path/to/target2 path/to/symbols" + echo "Usage: install_binary path/to/binary path/to/target1 path/to/target2 path/to/symbols path/to/symbols2" exit 1 fi @@ -15,9 +15,12 @@ SOURCE=$1 TARGET=$2 TARGET2=$3 SYMBOLS=$4 +SYMBOLS2=$5 mkdir -p $(dirname $SYMBOLS) cp $SOURCE $SYMBOLS +# Create a hard link to avoid the additional copy to the secondary location. +ln $SYMBOLS $SYMBOLS2 $STRIP --strip-unneeded $SOURCE -o $TARGET cp $TARGET $TARGET2 diff --git a/android_webview/java/src/org/chromium/android_webview/CookieManager.java b/android_webview/java/src/org/chromium/android_webview/CookieManager.java new file mode 100644 index 0000000..038baa3 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/CookieManager.java @@ -0,0 +1,190 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview; + +import android.net.ParseException; +import android.util.Log; + +import org.chromium.base.JNINamespace; +import org.chromium.base.ThreadUtils; + +import java.util.concurrent.Callable; + +/** + * CookieManager manages cookies according to RFC2109 spec. + * + * Methods in this class are thread safe. + */ +@JNINamespace("android_webview") +public final class CookieManager { + private static final String LOGTAG = "CookieManager"; + + /** + * Control whether cookie is enabled or disabled + * @param accept TRUE if accept cookie + */ + public synchronized void setAcceptCookie(boolean accept) { + final boolean finalAccept = accept; + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + nativeSetAcceptCookie(finalAccept); + } + }); + } + + private final Callable<Boolean> acceptCookieCallable = new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + return nativeAcceptCookie(); + } + }; + + /** + * Return whether cookie is enabled + * @return TRUE if accept cookie + */ + public synchronized boolean acceptCookie() { + return ThreadUtils.runOnUiThreadBlockingNoException(acceptCookieCallable); + } + + /** + * Set cookie for a given url. The old cookie with same host/path/name will + * be removed. The new cookie will be added if it is not expired or it does + * not have expiration which implies it is session cookie. + * @param url The url which cookie is set for + * @param value The value for set-cookie: in http response header + */ + public void setCookie(final String url, final String value) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + nativeSetCookie(url, value); + } + }); + } + + /** + * Get cookie(s) for a given url so that it can be set to "cookie:" in http + * request header. + * @param url The url needs cookie + * @return The cookies in the format of NAME=VALUE [; NAME=VALUE] + */ + public String getCookie(final String url) { + String cookie = ThreadUtils.runOnUiThreadBlockingNoException(new Callable<String>() { + @Override + public String call() throws Exception { + return nativeGetCookie(url.toString()); + } + }); + // Return null if the string is empty to match legacy behavior + return cookie == null || cookie.trim().isEmpty() ? null : cookie; + } + + private final Runnable removeSessionCookieRunnable = new Runnable() { + @Override + public void run() { + nativeRemoveSessionCookie(); + } + }; + + /** + * Remove all session cookies, which are cookies without expiration date + */ + public void removeSessionCookie() { + ThreadUtils.runOnUiThread(removeSessionCookieRunnable); + } + + private final Runnable removeAllCookieRunnable = new Runnable() { + @Override + public void run() { + nativeRemoveAllCookie(); + } + }; + + /** + * Remove all cookies + */ + public void removeAllCookie() { + ThreadUtils.runOnUiThread(removeAllCookieRunnable); + } + + private final Callable<Boolean> hasCookiesCallable = new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + return nativeHasCookies(); + } + }; + + /** + * Return true if there are stored cookies. + */ + public synchronized boolean hasCookies() { + return ThreadUtils.runOnUiThreadBlockingNoException(hasCookiesCallable); + } + + private final Runnable removeExpiredCookieRunnable = new Runnable() { + @Override + public void run() { + nativeRemoveExpiredCookie(); + } + }; + + /** + * Remove all expired cookies + */ + public void removeExpiredCookie() { + ThreadUtils.runOnUiThread(removeExpiredCookieRunnable); + } + + private static final Callable<Boolean> allowFileSchemeCookiesCallable = + new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + return nativeAllowFileSchemeCookies(); + } + }; + + /** + * Whether cookies are accepted for file scheme URLs. + */ + public static boolean allowFileSchemeCookies() { + return ThreadUtils.runOnUiThreadBlockingNoException(allowFileSchemeCookiesCallable); + } + + /** + * Sets whether cookies are accepted for file scheme URLs. + * + * Use of cookies with file scheme URLs is potentially insecure. Do not use this feature unless + * you can be sure that no unintentional sharing of cookie data can take place. + * <p> + * Note that calls to this method will have no effect if made after a WebView or CookieManager + * instance has been created. + */ + public static void setAcceptFileSchemeCookies(boolean accept) { + final boolean finalAccept = accept; + ThreadUtils.runOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + nativeSetAcceptFileSchemeCookies(finalAccept); + } + }); + } + + private native void nativeSetAcceptCookie(boolean accept); + private native boolean nativeAcceptCookie(); + + private native void nativeSetCookie(String url, String value); + private native String nativeGetCookie(String url); + + private native void nativeRemoveSessionCookie(); + private native void nativeRemoveAllCookie(); + private native void nativeRemoveExpiredCookie(); + + private native boolean nativeHasCookies(); + + static native boolean nativeAllowFileSchemeCookies(); + static native void nativeSetAcceptFileSchemeCookies(boolean accept); +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java new file mode 100755 index 0000000..c2a5868 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java @@ -0,0 +1,246 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview.test; + +import android.test.MoreAsserts; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; + +import org.chromium.android_webview.CookieManager; +import org.chromium.base.test.Feature; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.test.Criteria; +import org.chromium.content.browser.test.CriteriaHelper; +import org.chromium.content.browser.test.TestContentViewClient.OnEvaluateJavaScriptResultHelper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tests for the CookieManager. + */ +public class CookieManagerTest extends AndroidWebViewTestBase { + + private CookieManager mCookieManager; + private TestAwContentsClient mContentViewClient; + private ContentViewCore mContentViewCore; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mCookieManager = new CookieManager(); + mContentViewClient = new TestAwContentsClient(); + mContentViewCore = + createAwTestContainerViewOnMainSync(mContentViewClient).getContentViewCore(); + mContentViewCore.getContentSettings().setJavaScriptEnabled(true); + assertNotNull(mCookieManager); + } + + @SmallTest + @Feature({"Android-WebView", "Privacy"}) + public void testAllowFileSchemeCookies() throws Throwable { + assertFalse(CookieManager.allowFileSchemeCookies()); + CookieManager.setAcceptFileSchemeCookies(true); + assertTrue(CookieManager.allowFileSchemeCookies()); + CookieManager.setAcceptFileSchemeCookies(false); + assertFalse(CookieManager.allowFileSchemeCookies()); + } + + @MediumTest + @Feature({"Android-WebView", "Privacy"}) + public void testAcceptCookie() throws Throwable { + TestWebServer webServer = null; + try { + webServer = new TestWebServer(false); + String path = "/cookie_test.html"; + String responseStr = + "<html><head><title>TEST!</title></head><body>HELLO!</body></html>"; + String url = webServer.setResponse(path, responseStr, null); + + mCookieManager.setAcceptCookie(false); + mCookieManager.removeAllCookie(); + assertFalse(mCookieManager.acceptCookie()); + assertFalse(mCookieManager.hasCookies()); + + loadUrlSync(mContentViewCore, mContentViewClient.getOnPageFinishedHelper(), url); + setCookie("test1", "value1"); + assertNull(mCookieManager.getCookie(url)); + + List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>(); + responseHeaders.add( + Pair.create("Set-Cookie", "header-test1=header-value1; path=" + path)); + url = webServer.setResponse(path, responseStr, responseHeaders); + loadUrlSync(mContentViewCore, mContentViewClient.getOnPageFinishedHelper(), url); + assertNull(mCookieManager.getCookie(url)); + + mCookieManager.setAcceptCookie(true); + assertTrue(mCookieManager.acceptCookie()); + + url = webServer.setResponse(path, responseStr, null); + loadUrlSync(mContentViewCore, mContentViewClient.getOnPageFinishedHelper(), url); + setCookie("test2", "value2"); + waitForCookie(url); + String cookie = mCookieManager.getCookie(url); + assertNotNull(cookie); + validateCookies(cookie, "test2"); + + responseHeaders = new ArrayList<Pair<String, String>>(); + responseHeaders.add( + Pair.create("Set-Cookie", "header-test2=header-value2 path=" + path)); + url = webServer.setResponse(path, responseStr, responseHeaders); + loadUrlSync(mContentViewCore, mContentViewClient.getOnPageFinishedHelper(), url); + waitForCookie(url); + cookie = mCookieManager.getCookie(url); + assertNotNull(cookie); + validateCookies(cookie, "test2", "header-test2"); + + // clean up all cookies + mCookieManager.removeAllCookie(); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + private void setCookie(final String name, final String value) + throws Throwable { + OnEvaluateJavaScriptResultHelper onEvaluateJavaScriptResultHelper = + mContentViewClient.getOnEvaluateJavaScriptResultHelper(); + int currentCallCount = onEvaluateJavaScriptResultHelper.getCallCount(); + final AtomicInteger requestId = new AtomicInteger(); + runTestOnUiThread(new Runnable() { + @Override + public void run() { + requestId.set(mContentViewCore.evaluateJavaScript( + "var expirationDate = new Date();" + + "expirationDate.setDate(expirationDate.getDate() + 5);" + + "document.cookie='" + name + "=" + value + + "; expires=' + expirationDate.toUTCString();")); + } + }); + onEvaluateJavaScriptResultHelper.waitForCallback(currentCallCount); + assertEquals("Response ID mismatch when evaluating JavaScript.", + requestId.get(), onEvaluateJavaScriptResultHelper.getId()); + } + + private void waitForCookie(final String url) throws InterruptedException { + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + return mCookieManager.getCookie(url) != null; + } + })); + } + + private void validateCookies(String responseCookie, String... expectedCookieNames) { + String[] cookies = responseCookie.split(";"); + Set<String> foundCookieNames = new HashSet<String>(); + for (String cookie : cookies) { + foundCookieNames.add(cookie.substring(0, cookie.indexOf("=")).trim()); + } + MoreAsserts.assertEquals( + foundCookieNames, new HashSet<String>(Arrays.asList(expectedCookieNames))); + } + + @MediumTest + @Feature({"Android-WebView", "Privacy"}) + public void testRemoveAllCookie() throws InterruptedException { + // enable cookie + mCookieManager.setAcceptCookie(true); + assertTrue(mCookieManager.acceptCookie()); + + // first there should be no cookie stored + mCookieManager.removeAllCookie(); + assertFalse(mCookieManager.hasCookies()); + + String url = "http://www.example.com"; + String cookie = "name=test"; + mCookieManager.setCookie(url, cookie); + assertEquals(cookie, mCookieManager.getCookie(url)); + + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + return mCookieManager.hasCookies(); + } + })); + + // clean up all cookies + mCookieManager.removeAllCookie(); + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + return !mCookieManager.hasCookies(); + } + })); + } + + @MediumTest + @Feature({"Android-WebView", "Privacy"}) + @SuppressWarnings("deprecation") + public void testCookieExpiration() throws InterruptedException { + // enable cookie + mCookieManager.setAcceptCookie(true); + assertTrue(mCookieManager.acceptCookie()); + mCookieManager.removeAllCookie(); + assertFalse(mCookieManager.hasCookies()); + + final String url = "http://www.example.com"; + final String cookie1 = "cookie1=peter"; + final String cookie2 = "cookie2=sue"; + final String cookie3 = "cookie3=marc"; + + mCookieManager.setCookie(url, cookie1); // session cookie + + Date date = new Date(); + date.setTime(date.getTime() + 1000 * 600); + String value2 = cookie2 + "; expires=" + date.toGMTString(); + mCookieManager.setCookie(url, value2); // expires in 10min + + long expiration = 3000; + date = new Date(); + date.setTime(date.getTime() + expiration); + String value3 = cookie3 + "; expires=" + date.toGMTString(); + mCookieManager.setCookie(url, value3); // expires in 3s + + String allCookies = mCookieManager.getCookie(url); + assertTrue(allCookies.contains(cookie1)); + assertTrue(allCookies.contains(cookie2)); + assertTrue(allCookies.contains(cookie3)); + + mCookieManager.removeSessionCookie(); + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + String c = mCookieManager.getCookie(url); + return !c.contains(cookie1) && c.contains(cookie2) && c.contains(cookie3); + } + })); + + Thread.sleep(expiration + 1000); // wait for cookie to expire + mCookieManager.removeExpiredCookie(); + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + String c = mCookieManager.getCookie(url); + return !c.contains(cookie1) && c.contains(cookie2) && !c.contains(cookie3); + } + })); + + mCookieManager.removeAllCookie(); + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + return mCookieManager.getCookie(url) == null; + } + })); + } +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java b/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java new file mode 100644 index 0000000..8508a51 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java @@ -0,0 +1,431 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview.test; + +import android.util.Base64; +import android.util.Log; +import android.util.Pair; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; +import org.apache.http.RequestLine; +import org.apache.http.StatusLine; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.DefaultHttpServerConnection; +import org.apache.http.impl.cookie.DateUtils; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.params.HttpParams; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.X509TrustManager; + +/** + * Simple http test server for testing. + * + * Based heavily on the CTSWebServer in Android. + */ +public class TestWebServer { + private static final String TAG = "TestWebServer"; + private static final int SERVER_PORT = 4444; + private static final int SSL_SERVER_PORT = 4445; + + public static final String SHUTDOWN_PREFIX = "/shutdown"; + + private static TestWebServer sInstance; + private static Hashtable<Integer, String> sReasons; + + private ServerThread mServerThread; + private String mServerUri; + private boolean mSsl; + + private static class Response { + final String mResponseStr; + final List<Pair<String, String>> mResponseHeaders; + + Response(String responseStr, List<Pair<String, String>> responseHeaders) { + mResponseStr = responseStr; + mResponseHeaders = responseHeaders == null ? + new ArrayList<Pair<String, String>>() : responseHeaders; + } + } + + private Map<String, Response> mResponseMap = new HashMap<String, Response>(); + + /** + * Create and start a local HTTP server instance. + * @param ssl True if the server should be using secure sockets. + * @throws Exception + */ + public TestWebServer(boolean ssl) throws Exception { + if (sInstance != null) { + // attempt to start a new instance while one is still running + // shut down the old instance first + sInstance.shutdown(); + } + sInstance = this; + mSsl = ssl; + if (mSsl) { + mServerUri = "https://localhost:" + SSL_SERVER_PORT; + } else { + mServerUri = "http://localhost:" + SERVER_PORT; + } + mServerThread = new ServerThread(this, mSsl); + mServerThread.start(); + } + + /** + * Terminate the http server. + */ + public void shutdown() { + try { + // Avoid a deadlock between two threads where one is trying to call + // close() and the other one is calling accept() by sending a GET + // request for shutdown and having the server's one thread + // sequentially call accept() and close(). + URL url = new URL(mServerUri + SHUTDOWN_PREFIX); + URLConnection connection = openConnection(url); + connection.connect(); + + // Read the input from the stream to send the request. + InputStream is = connection.getInputStream(); + is.close(); + + // Block until the server thread is done shutting down. + mServerThread.join(); + + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } catch (KeyManagementException e) { + throw new IllegalStateException(e); + } + + sInstance = null; + } + + /** + * Sets a response to be returned when a particular request path is passed + * in (with the option to specify additional headers). + * + * @param requestPath The path to respond to. + * @param resposneString The response body that will be returned. + * @param responseHeaders Any additional headers that should be returned along with the + * response (null is acceptable). + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponse( + String requestPath, String resposneString, + List<Pair<String, String>> responseHeaders) { + mResponseMap.put(requestPath, new Response(resposneString, responseHeaders)); + return mServerUri + requestPath; + } + + private URLConnection openConnection(URL url) + throws IOException, NoSuchAlgorithmException, KeyManagementException { + if (mSsl) { + // Install hostname verifiers and trust managers that don't do + // anything in order to get around the client not trusting + // the test server due to a lack of certificates. + + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setHostnameVerifier(new TestHostnameVerifier()); + + SSLContext context = SSLContext.getInstance("TLS"); + TestTrustManager trustManager = new TestTrustManager(); + context.init(null, new TestTrustManager[] {trustManager}, null); + connection.setSSLSocketFactory(context.getSocketFactory()); + + return connection; + } else { + return url.openConnection(); + } + } + + /** + * {@link X509TrustManager} that trusts everybody. This is used so that + * the client calling {@link TestWebServer#shutdown()} can issue a request + * for shutdown by blindly trusting the {@link TestWebServer}'s + * credentials. + */ + private static class TestTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // Trust the TestWebServer... + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // Trust the TestWebServer... + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + + /** + * {@link HostnameVerifier} that verifies everybody. This permits + * the client to trust the web server and call + * {@link TestWebServer#shutdown()}. + */ + private static class TestHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + /** + * Generate a response to the given request. + * @throws InterruptedException + */ + private HttpResponse getResponse(HttpRequest request) throws InterruptedException { + RequestLine requestLine = request.getRequestLine(); + HttpResponse httpResponse = null; + Log.i(TAG, requestLine.getMethod() + ": " + requestLine.getUri()); + String uriString = requestLine.getUri(); + URI uri = URI.create(uriString); + String path = uri.getPath(); + + Response response = mResponseMap.get(path); + if (path.equals(SHUTDOWN_PREFIX)) { + httpResponse = createResponse(HttpStatus.SC_OK); + } else if (response == null) { + httpResponse = createResponse(HttpStatus.SC_NOT_FOUND); + } else { + httpResponse = createResponse(HttpStatus.SC_OK); + httpResponse.setEntity(createEntity(response.mResponseStr)); + for (Pair<String, String> header : response.mResponseHeaders) { + httpResponse.addHeader(header.first, header.second); + } + } + StatusLine sl = httpResponse.getStatusLine(); + Log.i(TAG, sl.getStatusCode() + "(" + sl.getReasonPhrase() + ")"); + setDateHeaders(httpResponse); + return httpResponse; + } + + private void setDateHeaders(HttpResponse response) { + long time = System.currentTimeMillis(); + response.addHeader("Date", DateUtils.formatDate(new Date(), DateUtils.PATTERN_RFC1123)); + } + + /** + * Create an empty response with the given status. + */ + private HttpResponse createResponse(int status) { + HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_0, status, null); + + if (sReasons == null) { + sReasons = new Hashtable<Integer, String>(); + sReasons.put(HttpStatus.SC_UNAUTHORIZED, "Unauthorized"); + sReasons.put(HttpStatus.SC_NOT_FOUND, "Not Found"); + sReasons.put(HttpStatus.SC_FORBIDDEN, "Forbidden"); + sReasons.put(HttpStatus.SC_MOVED_TEMPORARILY, "Moved Temporarily"); + } + // Fill in error reason. Avoid use of the ReasonPhraseCatalog, which is Locale-dependent. + String reason = sReasons.get(status); + + if (reason != null) { + StringBuffer buf = new StringBuffer("<html><head><title>"); + buf.append(reason); + buf.append("</title></head><body>"); + buf.append(reason); + buf.append("</body></html>"); + response.setEntity(createEntity(buf.toString())); + } + return response; + } + + /** + * Create a string entity for the given content. + */ + private StringEntity createEntity(String content) { + try { + StringEntity entity = new StringEntity(content); + entity.setContentType("text/html"); + return entity; + } catch (UnsupportedEncodingException e) { + Log.w(TAG, e); + } + return null; + } + + private static class ServerThread extends Thread { + private TestWebServer mServer; + private ServerSocket mSocket; + private boolean mIsSsl; + private boolean mIsCancelled; + private SSLContext mSslContext; + + /** + * Defines the keystore contents for the server, BKS version. Holds just a + * single self-generated key. The subject name is "Test Server". + */ + private static final String SERVER_KEYS_BKS = + "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" + + "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" + + "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" + + "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" + + "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" + + "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" + + "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" + + "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" + + "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" + + "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" + + "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" + + "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" + + "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" + + "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" + + "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" + + "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" + + "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" + + "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" + + "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" + + "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" + + "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" + + "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" + + "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" + + "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw="; + + private String PASSWORD = "android"; + + /** + * Loads a keystore from a base64-encoded String. Returns the KeyManager[] + * for the result. + */ + private KeyManager[] getKeyManagers() throws Exception { + byte[] bytes = Base64.decode(SERVER_KEYS_BKS, Base64.DEFAULT); + InputStream inputStream = new ByteArrayInputStream(bytes); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(inputStream, PASSWORD.toCharArray()); + inputStream.close(); + + String algorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm); + keyManagerFactory.init(keyStore, PASSWORD.toCharArray()); + + return keyManagerFactory.getKeyManagers(); + } + + + public ServerThread(TestWebServer server, boolean ssl) throws Exception { + super("ServerThread"); + mServer = server; + mIsSsl = ssl; + int retry = 3; + while (true) { + try { + if (mIsSsl) { + mSslContext = SSLContext.getInstance("TLS"); + mSslContext.init(getKeyManagers(), null, null); + mSocket = mSslContext.getServerSocketFactory().createServerSocket( + SSL_SERVER_PORT); + } else { + mSocket = new ServerSocket(SERVER_PORT); + } + return; + } catch (IOException e) { + Log.w(TAG, e); + if (--retry == 0) { + throw e; + } + // sleep in case server socket is still being closed + Thread.sleep(1000); + } + } + } + + @Override + public void run() { + HttpParams params = new BasicHttpParams(); + params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0); + while (!mIsCancelled) { + try { + Socket socket = mSocket.accept(); + DefaultHttpServerConnection conn = new DefaultHttpServerConnection(); + conn.bind(socket, params); + + // Determine whether we need to shutdown early before + // parsing the response since conn.close() will crash + // for SSL requests due to UnsupportedOperationException. + HttpRequest request = conn.receiveRequestHeader(); + if (isShutdownRequest(request)) { + mIsCancelled = true; + } + + HttpResponse response = mServer.getResponse(request); + conn.sendResponseHeader(response); + conn.sendResponseEntity(response); + conn.close(); + + } catch (IOException e) { + // normal during shutdown, ignore + Log.w(TAG, e); + } catch (HttpException e) { + Log.w(TAG, e); + } catch (InterruptedException e) { + Log.w(TAG, e); + } catch (UnsupportedOperationException e) { + // DefaultHttpServerConnection's close() throws an + // UnsupportedOperationException. + Log.w(TAG, e); + } + } + try { + mSocket.close(); + } catch (IOException ignored) { + // safe to ignore + } + } + + private boolean isShutdownRequest(HttpRequest request) { + RequestLine requestLine = request.getRequestLine(); + String uriString = requestLine.getUri(); + URI uri = URI.create(uriString); + String path = uri.getPath(); + return path.equals(SHUTDOWN_PREFIX); + } + } +} diff --git a/android_webview/lib/aw_browser_dependency_factory_impl.cc b/android_webview/lib/aw_browser_dependency_factory_impl.cc index f940b104..f31bb64 100644 --- a/android_webview/lib/aw_browser_dependency_factory_impl.cc +++ b/android_webview/lib/aw_browser_dependency_factory_impl.cc @@ -5,8 +5,12 @@ #include "android_webview/lib/aw_browser_dependency_factory_impl.h" // TODO(joth): Componentize or remove chrome/... dependencies. +#include "android_webview/browser/net/aw_network_delegate.h" #include "android_webview/native/aw_contents_container.h" +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/lazy_instance.h" +#include "base/memory/ref_counted.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" @@ -14,6 +18,11 @@ #include "chrome/browser/ui/tab_contents/tab_contents.h" #include "content/public/browser/web_contents.h" #include "ipc/ipc_message.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +using content::BrowserContext; +using content::WebContents; namespace android_webview { @@ -49,13 +58,44 @@ void AwBrowserDependencyFactoryImpl::InstallInstance() { SetInstance(g_lazy_instance.Pointer()); } -content::WebContents* -AwBrowserDependencyFactoryImpl::CreateWebContents(bool incognito) { +// Initializing the Network Delegate here is only a temporary solution until we +// build an Android WebView specific BrowserContext that can handle building +// this internally. +void AwBrowserDependencyFactoryImpl::InitializeNetworkDelegateOnIOThread( + net::URLRequestContextGetter* normal_context, + net::URLRequestContextGetter* incognito_context) { + network_delegate_.reset(new AwNetworkDelegate()); + normal_context->GetURLRequestContext()->set_network_delegate( + network_delegate_.get()); + incognito_context->GetURLRequestContext()->set_network_delegate( + network_delegate_.get()); +} + +void AwBrowserDependencyFactoryImpl::EnsureNetworkDelegateInitialized() { + if (initialized_network_delegate_) + return; + initialized_network_delegate_ = true; Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile(); - if (incognito) - profile = profile->GetOffTheRecordProfile(); + profile->GetRequestContext()->GetNetworkTaskRunner()->PostTask( + FROM_HERE, + base::Bind( + &AwBrowserDependencyFactoryImpl::InitializeNetworkDelegateOnIOThread, + base::Unretained(this), + make_scoped_refptr(profile->GetRequestContext()), + make_scoped_refptr( + profile->GetOffTheRecordProfile()->GetRequestContext()))); +} + +content::BrowserContext* AwBrowserDependencyFactoryImpl::GetBrowserContext( + bool incognito) { + EnsureNetworkDelegateInitialized(); + Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile(); + return incognito ? profile->GetOffTheRecordProfile() : profile; +} - return content::WebContents::Create(profile, 0, MSG_ROUTING_NONE, 0); +WebContents* AwBrowserDependencyFactoryImpl::CreateWebContents(bool incognito) { + return content::WebContents::Create( + GetBrowserContext(incognito), 0, MSG_ROUTING_NONE, 0); } AwContentsContainer* AwBrowserDependencyFactoryImpl::CreateContentsContainer( diff --git a/android_webview/lib/aw_browser_dependency_factory_impl.h b/android_webview/lib/aw_browser_dependency_factory_impl.h index fae835f..4b9a003 100644 --- a/android_webview/lib/aw_browser_dependency_factory_impl.h +++ b/android_webview/lib/aw_browser_dependency_factory_impl.h @@ -8,9 +8,16 @@ #include "android_webview/native/aw_browser_dependency_factory.h" #include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" + +namespace net { +class URLRequestContextGetter; +} namespace android_webview { +class AwNetworkDelegate; + class AwBrowserDependencyFactoryImpl : public AwBrowserDependencyFactory { public: AwBrowserDependencyFactoryImpl(); @@ -20,6 +27,7 @@ class AwBrowserDependencyFactoryImpl : public AwBrowserDependencyFactory { static void InstallInstance(); // AwBrowserDependencyFactory + virtual content::BrowserContext* GetBrowserContext(bool incognito) OVERRIDE; virtual content::WebContents* CreateWebContents(bool incognito) OVERRIDE; virtual AwContentsContainer* CreateContentsContainer( content::WebContents* contents) OVERRIDE; @@ -27,6 +35,16 @@ class AwBrowserDependencyFactoryImpl : public AwBrowserDependencyFactory { OVERRIDE; private: + void InitializeNetworkDelegateOnIOThread( + net::URLRequestContextGetter* normal_context, + net::URLRequestContextGetter* incognito_context); + void EnsureNetworkDelegateInitialized(); + + // Constructed and assigned on the IO thread. + scoped_ptr<AwNetworkDelegate> network_delegate_; + // Set on the UI thread. + bool initialized_network_delegate_; + DISALLOW_COPY_AND_ASSIGN(AwBrowserDependencyFactoryImpl); }; diff --git a/android_webview/lib/aw_content_browser_client.cc b/android_webview/lib/aw_content_browser_client.cc index aeaedf2..2a07343 100644 --- a/android_webview/lib/aw_content_browser_client.cc +++ b/android_webview/lib/aw_content_browser_client.cc @@ -4,6 +4,7 @@ #include "android_webview/lib/aw_content_browser_client.h" +#include "android_webview/browser/aw_cookie_access_policy.h" #include "android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h" namespace android_webview { @@ -20,4 +21,38 @@ void AwContentBrowserClient::ResourceDispatcherHostCreated() { AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated(); } +bool AwContentBrowserClient::AllowGetCookie(const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_view_id) { + // Not base-calling into ChromeContentBrowserClient as we are not dependent + // on chrome/ for any cookie policy decisions. + return AwCookieAccessPolicy::GetInstance()->AllowGetCookie(url, + first_party, + cookie_list, + context, + render_process_id, + render_view_id); +} + +bool AwContentBrowserClient::AllowSetCookie(const GURL& url, + const GURL& first_party, + const std::string& cookie_line, + content::ResourceContext* context, + int render_process_id, + int render_view_id, + net::CookieOptions* options) { + // Not base-calling into ChromeContentBrowserClient as we are not dependent + // on chrome/ for any cookie policy decisions. + return AwCookieAccessPolicy::GetInstance()->AllowSetCookie(url, + first_party, + cookie_line, + context, + render_process_id, + render_view_id, + options); +} + } // namespace android_webview diff --git a/android_webview/lib/aw_content_browser_client.h b/android_webview/lib/aw_content_browser_client.h index 0972032..8b75c99 100644 --- a/android_webview/lib/aw_content_browser_client.h +++ b/android_webview/lib/aw_content_browser_client.h @@ -18,6 +18,19 @@ class AwContentBrowserClient : public chrome::ChromeContentBrowserClient { // Overriden methods from ContentBrowserClient. virtual void ResourceDispatcherHostCreated() OVERRIDE; + virtual bool AllowGetCookie(const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_view_id) OVERRIDE; + virtual bool AllowSetCookie(const GURL& url, + const GURL& first_party, + const std::string& cookie_line, + content::ResourceContext* context, + int render_process_id, + int render_view_id, + net::CookieOptions* options) OVERRIDE; }; } // namespace android_webview diff --git a/android_webview/native/android_webview_jni_registrar.cc b/android_webview/native/android_webview_jni_registrar.cc index cb63594..cabfbbd 100644 --- a/android_webview/native/android_webview_jni_registrar.cc +++ b/android_webview/native/android_webview_jni_registrar.cc @@ -6,6 +6,7 @@ #include "android_webview/native/android_web_view_util.h" #include "android_webview/native/aw_contents.h" +#include "android_webview/native/cookie_manager.h" #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" #include "chrome/browser/component/web_contents_delegate_android/component_jni_registrar.h" @@ -15,6 +16,7 @@ namespace android_webview { static base::android::RegistrationMethod kWebViewRegisteredMethods[] = { { "AndroidWebViewUtil", RegisterAndroidWebViewUtil }, { "AwContents", RegisterAwContents }, + { "CookieManager", RegisterCookieManager }, }; bool RegisterJni(JNIEnv* env) { diff --git a/android_webview/native/aw_browser_dependency_factory.h b/android_webview/native/aw_browser_dependency_factory.h index ebf03fd..7914069 100644 --- a/android_webview/native/aw_browser_dependency_factory.h +++ b/android_webview/native/aw_browser_dependency_factory.h @@ -8,6 +8,7 @@ #include "base/basictypes.h" namespace content { +class BrowserContext; class JavaScriptDialogCreator; class WebContents; } @@ -35,6 +36,9 @@ class AwBrowserDependencyFactory { // Returns the singleton instance. |SetInstance| must have been called. static AwBrowserDependencyFactory* GetInstance(); + // Returns the current browser context based on the specified mode. + virtual content::BrowserContext* GetBrowserContext(bool incognito) = 0; + // Constructs and returns ownership of a WebContents instance. virtual content::WebContents* CreateWebContents(bool incognito) = 0; diff --git a/android_webview/native/cookie_manager.cc b/android_webview/native/cookie_manager.cc new file mode 100644 index 0000000..fbbfc06 --- /dev/null +++ b/android_webview/native/cookie_manager.cc @@ -0,0 +1,303 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/native/cookie_manager.h" + +#include "android_webview/browser/aw_cookie_access_policy.h" +#include "android_webview/native/aw_browser_dependency_factory.h" +#include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_restrictions.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_options.h" +#include "net/cookies/cookie_store.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" +#include "jni/CookieManager_jni.h" + +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertJavaStringToUTF16; +using content::BrowserThread; +using net::CookieList; +using net::CookieMonster; +using net::URLRequestContextGetter; + +// This class is only available when building the chromium back-end for andriod +// webview: it is required where API backward compatibility demands that the UI +// thread must block waiting on other threads e.g. to obtain a synchronous +// return value. Long term, asynchronous overloads of all such methods will be +// added in the public API, and and no new uses of this will be allowed. +class ScopedAllowWaitForLegacyWebViewApi { + private: + base::ThreadRestrictions::ScopedAllowWait wait; +}; + +// CookieManager should be refactored to not require all tasks accessing the +// CookieStore to be piped through the IO thread. It is currently required as +// the URLRequestContext provides the easiest mechanism for accessing the +// CookieStore, but the CookieStore is threadsafe. In the future, we may +// instead want to inject an explicit CookieStore dependency into this object +// during process initialization to avoid depending on the URLRequestContext. +// +// In addition to the IO thread being the easiest access mechanism, it is also +// used because the async cookie tasks need to be processed on a different +// thread than the caller from Java, which can be any thread. But, the calling +// thread will be 'dead' blocked waiting on the async task to complete, so we +// need it to complete on a 'live' (non-blocked) thread that is still pumping +// messages. +// +// We could refactor to only provide an asynchronous (but thread safe) API on +// the native side, and move this support for legacy synchronous blocking +// messages into a java-side worker thread. + +namespace android_webview { + +namespace { + +typedef base::Callback<void(scoped_refptr<URLRequestContextGetter>, + base::WaitableEvent*)> CookieTask; + +// Executes the |callback| task on the IO thread and |wait_for_completion| +// should only be true if the Java API method returns a value or is explicitly +// stated to be synchronous. +static void ExecCookieTask(const CookieTask& callback, + const bool wait_for_completion) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + content::BrowserContext* context = + android_webview::AwBrowserDependencyFactory::GetInstance()-> + GetBrowserContext(false); + if (!context) + return; + + scoped_refptr<URLRequestContextGetter> context_getter( + context->GetRequestContext()); + + if (wait_for_completion) { + base::WaitableEvent completion(false, false); + + context->GetRequestContext()->GetNetworkTaskRunner()->PostTask( + FROM_HERE, base::Bind(callback, context_getter, &completion)); + + ScopedAllowWaitForLegacyWebViewApi wait; + completion.Wait(); + } else { + base::WaitableEvent* cb = NULL; + context->GetRequestContext()->GetNetworkTaskRunner()->PostTask( + FROM_HERE, base::Bind(callback, context_getter, cb)); + } +} + +} // namespace + +static void SetAcceptCookie(JNIEnv* env, jobject obj, jboolean accept) { + AwCookieAccessPolicy::GetInstance()->SetGlobalAllowAccess(accept); +} + +static jboolean AcceptCookie(JNIEnv* env, jobject obj) { + return AwCookieAccessPolicy::GetInstance()->GetGlobalAllowAccess(); +} + +namespace { + +// The CookieManager API does not return a value for SetCookie, +// so we don't need to propagate the |success| value back to the caller. +static void SetCookieCompleted(bool success) { +} + +static void SetCookieAsyncHelper( + const GURL& host, + const std::string& value, + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + DCHECK(!completion); + net::CookieOptions options; + options.set_include_httponly(); + + context_getter->GetURLRequestContext()->cookie_store()-> + SetCookieWithOptionsAsync(host, value, options, + base::Bind(&SetCookieCompleted)); +} + +} // namespace + +static void SetCookie(JNIEnv* env, jobject obj, jstring url, jstring value) { + GURL host(ConvertJavaStringToUTF16(env, url)); + std::string cookie_value(ConvertJavaStringToUTF8(env, value)); + + ExecCookieTask(base::Bind(&SetCookieAsyncHelper, host, cookie_value), false); +} + +namespace { + +static void GetCookieValueCompleted(base::WaitableEvent* completion, + std::string* result, + const std::string& value) { + *result = value; + DCHECK(completion); + completion->Signal(); +} + +static void GetCookieValueAsyncHelper( + const GURL& host, + std::string* result, + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + + net::CookieOptions options; + options.set_include_httponly(); + + context_getter->GetURLRequestContext()->cookie_store()-> + GetCookiesWithOptionsAsync(host, options, + base::Bind(&GetCookieValueCompleted, + completion, + result)); +} + +} // namespace + +static jstring GetCookie(JNIEnv* env, jobject obj, jstring url) { + GURL host(ConvertJavaStringToUTF16(env, url)); + std::string cookie_value; + ExecCookieTask(base::Bind(&GetCookieValueAsyncHelper, host, &cookie_value), + true); + + return base::android::ConvertUTF8ToJavaString(env, cookie_value).Release(); +} + +namespace { + +static void RemoveSessionCookieCompleted(int num_deleted) { + // The CookieManager API does not return a value for removeSessionCookie, + // so we don't need to propagate the |num_deleted| value back to the caller. +} + +static void RemoveSessionCookieAsyncHelper( + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + DCHECK(!completion); + net::CookieOptions options; + options.set_include_httponly(); + + CookieMonster* monster = context_getter->GetURLRequestContext()-> + cookie_store()->GetCookieMonster(); + monster->DeleteSessionCookiesAsync(base::Bind(&RemoveSessionCookieCompleted)); +} + +} // namespace + +static void RemoveSessionCookie(JNIEnv* env, jobject obj) { + ExecCookieTask(base::Bind(&RemoveSessionCookieAsyncHelper), false); +} + +namespace { + +static void RemoveAllCookieCompleted(int num_deleted) { + // The CookieManager API does not return a value for removeAllCookie, + // so we don't need to propagate the |num_deleted| value back to the caller. +} + +static void RemoveAllCookieAsyncHelper( + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + DCHECK(!completion); + CookieMonster* monster = context_getter->GetURLRequestContext()-> + cookie_store()->GetCookieMonster(); + monster->DeleteAllAsync(base::Bind(&RemoveAllCookieCompleted)); +} + +} // namespace + +static void RemoveAllCookie(JNIEnv* env, jobject obj) { + ExecCookieTask(base::Bind(&RemoveAllCookieAsyncHelper), false); +} + +static void RemoveExpiredCookie(JNIEnv* env, jobject obj) { + // HasCookies will call GetAllCookiesAsync, which in turn will force a GC. + HasCookies(env, obj); +} + +namespace { + +static void HasCookiesCompleted(base::WaitableEvent* completion, + bool* result, + const CookieList& cookies) { + *result = cookies.size() != 0; + DCHECK(completion); + completion->Signal(); +} + +static void HasCookiesAsyncHelper( + bool* result, + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + + CookieMonster* monster = context_getter->GetURLRequestContext()-> + cookie_store()->GetCookieMonster(); + monster->GetAllCookiesAsync(base::Bind(&HasCookiesCompleted, completion, + result)); +} + +} // namespace + +static jboolean HasCookies(JNIEnv* env, jobject obj) { + bool has_cookies; + ExecCookieTask(base::Bind(&HasCookiesAsyncHelper, &has_cookies), true); + return has_cookies; +} + +namespace { + +static void AllowFileSchemeCookiesAsyncHelper( + bool* accept, + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + + CookieMonster* monster = context_getter->GetURLRequestContext()-> + cookie_store()->GetCookieMonster(); + *accept = monster->IsCookieableScheme("file"); + + DCHECK(completion); + completion->Signal(); +} + +} // namespace + +static jboolean AllowFileSchemeCookies(JNIEnv* env, jclass obj) { + bool accept; + ExecCookieTask(base::Bind(&AllowFileSchemeCookiesAsyncHelper, &accept), true); + return accept; +} + +namespace { + +static void SetAcceptFileSchemeCookiesAsyncHelper( + bool accept, + scoped_refptr<URLRequestContextGetter> context_getter, + base::WaitableEvent* completion) { + + CookieMonster* monster = context_getter->GetURLRequestContext()-> + cookie_store()->GetCookieMonster(); + monster->SetEnableFileScheme(accept); + + DCHECK(completion); + completion->Signal(); +} + +} // namespace + +static void SetAcceptFileSchemeCookies(JNIEnv* env, jclass obj, + jboolean accept) { + ExecCookieTask(base::Bind(&SetAcceptFileSchemeCookiesAsyncHelper, accept), + true); +} + +bool RegisterCookieManager(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // android_webview namespace diff --git a/android_webview/native/cookie_manager.h b/android_webview/native/cookie_manager.h new file mode 100644 index 0000000..2bb2ca6 --- /dev/null +++ b/android_webview/native/cookie_manager.h @@ -0,0 +1,16 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ANDROID_WEBVIEW_NATIVE_COOKIE_MANAGER_H_ +#define ANDROID_WEBVIEW_NATIVE_COOKIE_MANAGER_H_ + +#include <jni.h> + +namespace android_webview { + +bool RegisterCookieManager(JNIEnv* env); + +} // namespace android_webview; + +#endif // ANDROID_WEBVIEW_NATIVE_COOKIE_MANAGER_H_ diff --git a/android_webview/native/webview_native.gyp b/android_webview/native/webview_native.gyp index 0cbb379..3e20fc9 100644 --- a/android_webview/native/webview_native.gyp +++ b/android_webview/native/webview_native.gyp @@ -31,6 +31,8 @@ 'aw_contents.h', 'aw_web_contents_delegate.cc', 'aw_web_contents_delegate.h', + 'cookie_manager.cc', + 'cookie_manager.h', 'intercepted_request_data.cc', 'intercepted_request_data.h', ], @@ -41,6 +43,7 @@ 'sources': [ '../java/src/org/chromium/android_webview/AndroidWebViewUtil.java', '../java/src/org/chromium/android_webview/AwContents.java', + '../java/src/org/chromium/android_webview/CookieManager.java', '../java/src/org/chromium/android_webview/InterceptedRequestData.java', ], 'variables': { |