| Index: chrome/browser/apps/guest_view/web_view_browsertest.cc
|
| diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
|
| index d5676863461c262ea31a1b988c515783d6c5cc9f..d9a44a1d681daebd9be894de48b0be2649c5df08 100644
|
| --- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
|
| +++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
|
| @@ -3,13 +3,19 @@
|
| // found in the LICENSE file.
|
|
|
| #include <queue>
|
| +#include <set>
|
| #include <utility>
|
|
|
| +#include "base/callback_helpers.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/files/scoped_temp_dir.h"
|
| +#include "base/guid.h"
|
| #include "base/location.h"
|
| #include "base/macros.h"
|
| #include "base/path_service.h"
|
| #include "base/process/process.h"
|
| #include "base/single_thread_task_runner.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| #include "base/strings/stringprintf.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| #include "base/thread_task_runner_handle.h"
|
| @@ -18,6 +24,10 @@
|
| #include "chrome/browser/apps/app_browsertest_util.h"
|
| #include "chrome/browser/chrome_content_browser_client.h"
|
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
|
| +#include "chrome/browser/download/download_history.h"
|
| +#include "chrome/browser/download/download_prefs.h"
|
| +#include "chrome/browser/download/download_service.h"
|
| +#include "chrome/browser/download/download_service_factory.h"
|
| #include "chrome/browser/lifetime/application_lifetime.h"
|
| #include "chrome/browser/pdf/pdf_extension_test_util.h"
|
| #include "chrome/browser/prerender/prerender_link_manager.h"
|
| @@ -48,6 +58,7 @@
|
| #include "content/public/common/child_process_host.h"
|
| #include "content/public/common/content_switches.h"
|
| #include "content/public/test/browser_test_utils.h"
|
| +#include "content/public/test/download_test_observer.h"
|
| #include "content/public/test/fake_speech_recognition_manager.h"
|
| #include "content/public/test/test_renderer_host.h"
|
| #include "extensions/browser/api/declarative/rules_registry.h"
|
| @@ -309,7 +320,7 @@ class MockDownloadWebContentsDelegate : public content::WebContentsDelegate {
|
| orig_delegate_->CanDownload(
|
| url, request_method,
|
| base::Bind(&MockDownloadWebContentsDelegate::DownloadDecided,
|
| - base::Unretained(this)));
|
| + base::Unretained(this), callback));
|
| }
|
|
|
| void WaitForCanDownload(bool expect_allow) {
|
| @@ -326,7 +337,7 @@ class MockDownloadWebContentsDelegate : public content::WebContentsDelegate {
|
| message_loop_runner_->Run();
|
| }
|
|
|
| - void DownloadDecided(bool allow) {
|
| + void DownloadDecided(const base::Callback<void(bool)>& callback, bool allow) {
|
| EXPECT_FALSE(decision_made_);
|
| decision_made_ = true;
|
|
|
| @@ -334,9 +345,11 @@ class MockDownloadWebContentsDelegate : public content::WebContentsDelegate {
|
| EXPECT_EQ(expect_allow_, allow);
|
| if (message_loop_runner_.get())
|
| message_loop_runner_->Quit();
|
| + callback.Run(allow);
|
| return;
|
| }
|
| last_download_allowed_ = allow;
|
| + callback.Run(allow);
|
| }
|
|
|
| void Reset() {
|
| @@ -2203,6 +2216,17 @@ IN_PROC_BROWSER_TEST_P(WebViewTest, DownloadPermission) {
|
| "web_view/download");
|
| ASSERT_TRUE(guest_web_contents);
|
|
|
| + base::ScopedTempDir temporary_download_dir;
|
| + ASSERT_TRUE(temporary_download_dir.CreateUniqueTempDir());
|
| + DownloadPrefs::FromBrowserContext(guest_web_contents->GetBrowserContext())
|
| + ->SetDownloadPath(temporary_download_dir.path());
|
| +
|
| + std::unique_ptr<content::DownloadTestObserver> completion_observer(
|
| + new content::DownloadTestObserverTerminal(
|
| + content::BrowserContext::GetDownloadManager(
|
| + guest_web_contents->GetBrowserContext()),
|
| + 1, content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
|
| +
|
| // Replace WebContentsDelegate with mock version so we can intercept download
|
| // requests.
|
| content::WebContentsDelegate* delegate = guest_web_contents->GetDelegate();
|
| @@ -2227,6 +2251,344 @@ IN_PROC_BROWSER_TEST_P(WebViewTest, DownloadPermission) {
|
| EXPECT_TRUE(content::ExecuteScript(guest_web_contents,
|
| "startDownload('download-link-3')"));
|
| mock_delegate->WaitForCanDownload(false); // Expect to not allow.
|
| + completion_observer->WaitForFinished();
|
| +}
|
| +
|
| +namespace {
|
| +
|
| +const char kDownloadPathPrefix[] = "/download_cookie_isolation_test";
|
| +
|
| +// EmbeddedTestServer request handler for use with DownloadCookieIsolation test.
|
| +// Responds with the next status code in |status_codes| if the 'Cookie' header
|
| +// sent with the request matches the query() part of the URL. Otherwise, fails
|
| +// the request with an HTTP 403. The body of the response is the value of the
|
| +// Cookie header.
|
| +std::unique_ptr<net::test_server::HttpResponse> HandleDownloadRequestWithCookie(
|
| + std::queue<net::HttpStatusCode>* status_codes,
|
| + const net::test_server::HttpRequest& request) {
|
| + if (request.relative_url.find(kDownloadPathPrefix) != 0) {
|
| + return std::unique_ptr<net::test_server::HttpResponse>();
|
| + }
|
| +
|
| + std::string cookie_to_expect = request.GetURL().query();
|
| + const auto cookie_header_it = request.headers.find("cookie");
|
| + std::unique_ptr<net::test_server::BasicHttpResponse> response;
|
| +
|
| + // Return a 403 if there's no cookie or if the cookie doesn't match.
|
| + if (cookie_header_it == request.headers.end() ||
|
| + cookie_header_it->second != cookie_to_expect) {
|
| + response.reset(new net::test_server::BasicHttpResponse);
|
| + response->set_code(net::HTTP_FORBIDDEN);
|
| + response->set_content_type("text/plain");
|
| + response->set_content("Forbidden");
|
| + return std::move(response);
|
| + }
|
| +
|
| + DCHECK(!status_codes->empty());
|
| +
|
| + // We have a cookie. Send some content along with the next status code.
|
| + response.reset(new net::test_server::BasicHttpResponse);
|
| + response->set_code(status_codes->front());
|
| + response->set_content_type("application/octet-stream");
|
| + response->set_content(cookie_to_expect);
|
| + status_codes->pop();
|
| + return std::move(response);
|
| +}
|
| +
|
| +class DownloadHistoryWaiter : public DownloadHistory::Observer {
|
| + public:
|
| + explicit DownloadHistoryWaiter(content::BrowserContext* browser_context) {
|
| + DownloadService* service =
|
| + DownloadServiceFactory::GetForBrowserContext(browser_context);
|
| + download_history_ = service->GetDownloadHistory();
|
| + download_history_->AddObserver(this);
|
| + }
|
| +
|
| + ~DownloadHistoryWaiter() override { download_history_->RemoveObserver(this); }
|
| +
|
| + void WaitForStored(size_t download_count) {
|
| + if (stored_downloads_.size() >= download_count)
|
| + return;
|
| + stored_download_target_ = download_count;
|
| + Wait();
|
| + }
|
| +
|
| + void WaitForHistoryLoad() {
|
| + if (history_query_complete_)
|
| + return;
|
| + Wait();
|
| + }
|
| +
|
| + private:
|
| + void Wait() {
|
| + base::RunLoop run_loop;
|
| + quit_closure_ = run_loop.QuitClosure();
|
| + run_loop.Run();
|
| + }
|
| +
|
| + void OnDownloadStored(content::DownloadItem* item,
|
| + const history::DownloadRow& info) override {
|
| + stored_downloads_.insert(item);
|
| + if (!quit_closure_.is_null() &&
|
| + stored_downloads_.size() >= stored_download_target_) {
|
| + base::ResetAndReturn(&quit_closure_).Run();
|
| + }
|
| + }
|
| +
|
| + void OnHistoryQueryComplete() override {
|
| + history_query_complete_ = true;
|
| + if (!quit_closure_.is_null())
|
| + base::ResetAndReturn(&quit_closure_).Run();
|
| + }
|
| +
|
| + std::unordered_set<content::DownloadItem*> stored_downloads_;
|
| + size_t stored_download_target_ = 0;
|
| + bool history_query_complete_ = false;
|
| + base::Closure quit_closure_;
|
| + DownloadHistory* download_history_ = nullptr;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +// Downloads initiated from isolated guest parititons should use their
|
| +// respective cookie stores. In addition, if those downloads are resumed, they
|
| +// should continue to use their respective cookie stores.
|
| +IN_PROC_BROWSER_TEST_P(WebViewTest, DownloadCookieIsolation) {
|
| + // These are the status codes to be returned by
|
| + // HandleDownloadRequestWithCookie. The first two requests are going to result
|
| + // in interrupted downloads. The next two requests are going to succeed.
|
| + std::queue<net::HttpStatusCode> status_codes;
|
| + status_codes.push(net::HTTP_INTERNAL_SERVER_ERROR);
|
| + status_codes.push(net::HTTP_INTERNAL_SERVER_ERROR);
|
| + status_codes.push(net::HTTP_OK);
|
| + status_codes.push(net::HTTP_OK);
|
| +
|
| + embedded_test_server()->RegisterRequestHandler(
|
| + base::Bind(&HandleDownloadRequestWithCookie, &status_codes));
|
| + ASSERT_TRUE(StartEmbeddedTestServer()); // For serving guest pages.
|
| + LoadAndLaunchPlatformApp("web_view/download_cookie_isolation",
|
| + "created-webviews");
|
| +
|
| + content::WebContents* web_contents = GetFirstAppWindowWebContents();
|
| + ASSERT_TRUE(web_contents);
|
| +
|
| + base::ScopedTempDir temporary_download_dir;
|
| + ASSERT_TRUE(temporary_download_dir.CreateUniqueTempDir());
|
| + DownloadPrefs::FromBrowserContext(web_contents->GetBrowserContext())
|
| + ->SetDownloadPath(temporary_download_dir.path());
|
| +
|
| + content::DownloadManager* download_manager =
|
| + content::BrowserContext::GetDownloadManager(
|
| + web_contents->GetBrowserContext());
|
| +
|
| + std::unique_ptr<content::DownloadTestObserver> interrupted_observer(
|
| + new content::DownloadTestObserverInterrupted(
|
| + download_manager, 2,
|
| + content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
|
| +
|
| + EXPECT_TRUE(content::ExecuteScript(
|
| + web_contents,
|
| + base::StringPrintf(
|
| + "startDownload('first', '%s?cookie=first')",
|
| + embedded_test_server()->GetURL(kDownloadPathPrefix).spec().c_str())));
|
| +
|
| + EXPECT_TRUE(content::ExecuteScript(
|
| + web_contents,
|
| + base::StringPrintf(
|
| + "startDownload('second', '%s?cookie=second')",
|
| + embedded_test_server()->GetURL(kDownloadPathPrefix).spec().c_str())));
|
| +
|
| + // Both downloads should fail due to the HTTP_INTERNAL_SERVER_ERROR that was
|
| + // injected above to the request handler. This maps to
|
| + // DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED.
|
| + interrupted_observer->WaitForFinished();
|
| +
|
| + content::DownloadManager::DownloadVector downloads;
|
| + download_manager->GetAllDownloads(&downloads);
|
| + ASSERT_EQ(2u, downloads.size());
|
| +
|
| + CloseAppWindow(GetFirstAppWindow());
|
| +
|
| + std::unique_ptr<content::DownloadTestObserver> completion_observer(
|
| + new content::DownloadTestObserverTerminal(
|
| + download_manager, 2,
|
| + content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
|
| +
|
| + for (auto& download : downloads) {
|
| + ASSERT_TRUE(download->CanResume());
|
| + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
|
| + download->GetLastReason());
|
| + download->Resume();
|
| + }
|
| +
|
| + completion_observer->WaitForFinished();
|
| +
|
| + std::set<std::string> cookies;
|
| + for (auto& download : downloads) {
|
| + ASSERT_EQ(content::DownloadItem::COMPLETE, download->GetState());
|
| + ASSERT_TRUE(base::PathExists(download->GetTargetFilePath()));
|
| + std::string content;
|
| + ASSERT_TRUE(
|
| + base::ReadFileToString(download->GetTargetFilePath(), &content));
|
| + // Note that the contents of the file is the value of the cookie.
|
| + EXPECT_EQ(content, download->GetURL().query());
|
| + cookies.insert(content);
|
| + }
|
| +
|
| + ASSERT_EQ(2u, cookies.size());
|
| + ASSERT_TRUE(cookies.find("cookie=first") != cookies.end());
|
| + ASSERT_TRUE(cookies.find("cookie=second") != cookies.end());
|
| +}
|
| +
|
| +IN_PROC_BROWSER_TEST_P(WebViewTest, PRE_DownloadCookieIsolation_CrossSession) {
|
| + // These are the status codes to be returned by
|
| + // HandleDownloadRequestWithCookie. The first two requests are going to result
|
| + // in interrupted downloads. The next two requests are going to succeed.
|
| + std::queue<net::HttpStatusCode> status_codes;
|
| + status_codes.push(net::HTTP_INTERNAL_SERVER_ERROR);
|
| + status_codes.push(net::HTTP_INTERNAL_SERVER_ERROR);
|
| +
|
| + embedded_test_server()->RegisterRequestHandler(
|
| + base::Bind(&HandleDownloadRequestWithCookie, &status_codes));
|
| + ASSERT_TRUE(StartEmbeddedTestServer()); // For serving guest pages.
|
| + LoadAndLaunchPlatformApp("web_view/download_cookie_isolation",
|
| + "created-webviews");
|
| +
|
| + content::WebContents* web_contents = GetFirstAppWindowWebContents();
|
| + ASSERT_TRUE(web_contents);
|
| +
|
| + base::ScopedTempDir temporary_download_dir;
|
| + ASSERT_TRUE(temporary_download_dir.CreateUniqueTempDir());
|
| + DownloadPrefs::FromBrowserContext(web_contents->GetBrowserContext())
|
| + ->SetDownloadPath(temporary_download_dir.path());
|
| +
|
| + content::DownloadManager* download_manager =
|
| + content::BrowserContext::GetDownloadManager(
|
| + web_contents->GetBrowserContext());
|
| +
|
| + DownloadHistoryWaiter history_waiter(web_contents->GetBrowserContext());
|
| +
|
| + std::unique_ptr<content::DownloadTestObserver> interrupted_observer(
|
| + new content::DownloadTestObserverInterrupted(
|
| + download_manager, 2,
|
| + content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
|
| +
|
| + EXPECT_TRUE(content::ExecuteScript(
|
| + web_contents,
|
| + base::StringPrintf(
|
| + "startDownload('first', '%s?cookie=first')",
|
| + embedded_test_server()->GetURL(kDownloadPathPrefix).spec().c_str())));
|
| +
|
| + // Note that the second webview uses an in-memory partition.
|
| + EXPECT_TRUE(content::ExecuteScript(
|
| + web_contents,
|
| + base::StringPrintf(
|
| + "startDownload('second', '%s?cookie=second')",
|
| + embedded_test_server()->GetURL(kDownloadPathPrefix).spec().c_str())));
|
| +
|
| + // Both downloads should fail due to the HTTP_INTERNAL_SERVER_ERROR that was
|
| + // injected above to the request handler. This maps to
|
| + // DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED.
|
| + interrupted_observer->WaitForFinished();
|
| +
|
| + // Wait for both downloads to be stored.
|
| + history_waiter.WaitForStored(2);
|
| +
|
| + // Leak the temporary download directory. We'll retake ownership in the next
|
| + // browser session.
|
| + temporary_download_dir.Take();
|
| +}
|
| +
|
| +IN_PROC_BROWSER_TEST_P(WebViewTest, DownloadCookieIsolation_CrossSession) {
|
| + std::queue<net::HttpStatusCode> status_codes;
|
| + status_codes.push(net::HTTP_OK);
|
| + status_codes.push(net::HTTP_OK);
|
| +
|
| + embedded_test_server()->RegisterRequestHandler(
|
| + base::Bind(&HandleDownloadRequestWithCookie, &status_codes));
|
| + ASSERT_TRUE(StartEmbeddedTestServer()); // For serving guest pages.
|
| +
|
| + content::BrowserContext* browser_context = profile();
|
| + content::DownloadManager* download_manager =
|
| + content::BrowserContext::GetDownloadManager(browser_context);
|
| +
|
| + DownloadHistoryWaiter history_waiter(browser_context);
|
| + history_waiter.WaitForHistoryLoad();
|
| +
|
| + base::ScopedTempDir temporary_download_dir;
|
| + ASSERT_TRUE(temporary_download_dir.Set(
|
| + DownloadPrefs::FromBrowserContext(browser_context)->DownloadPath()));
|
| +
|
| + content::DownloadManager::DownloadVector saved_downloads;
|
| + download_manager->GetAllDownloads(&saved_downloads);
|
| + ASSERT_EQ(2u, saved_downloads.size());
|
| +
|
| + content::DownloadManager::DownloadVector downloads;
|
| + // We can't trivially resume the previous downloads because they are going to
|
| + // try to talk to the old EmbeddedTestServer instance. We need to update the
|
| + // URL to point to the new instance, which should only differ by the port
|
| + // number.
|
| + for (auto& download : saved_downloads) {
|
| + const std::string port_string =
|
| + base::UintToString(embedded_test_server()->port());
|
| + url::Replacements<char> replacements;
|
| + replacements.SetPort(port_string.c_str(),
|
| + url::Component(0, port_string.size()));
|
| + std::vector<GURL> url_chain;
|
| + url_chain.push_back(download->GetURL().ReplaceComponents(replacements));
|
| +
|
| + downloads.push_back(download_manager->CreateDownloadItem(
|
| + base::GenerateGUID(), download->GetId() + 2, download->GetFullPath(),
|
| + download->GetTargetFilePath(), url_chain, download->GetReferrerUrl(),
|
| + download->GetSiteUrl(), download->GetTabUrl(),
|
| + download->GetTabReferrerUrl(), download->GetMimeType(),
|
| + download->GetOriginalMimeType(), download->GetStartTime(),
|
| + download->GetEndTime(), download->GetETag(),
|
| + download->GetLastModifiedTime(), download->GetReceivedBytes(),
|
| + download->GetTotalBytes(), download->GetHash(), download->GetState(),
|
| + download->GetDangerType(), download->GetLastReason(),
|
| + download->GetOpened()));
|
| + }
|
| +
|
| + std::unique_ptr<content::DownloadTestObserver> completion_observer(
|
| + new content::DownloadTestObserverTerminal(
|
| + download_manager, 2,
|
| + content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
|
| +
|
| + for (auto& download : downloads) {
|
| + ASSERT_TRUE(download->CanResume());
|
| + ASSERT_TRUE(
|
| + temporary_download_dir.path().IsParent(download->GetTargetFilePath()));
|
| + ASSERT_TRUE(download->GetFullPath().empty());
|
| + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
|
| + download->GetLastReason());
|
| + download->Resume();
|
| + }
|
| +
|
| + completion_observer->WaitForFinished();
|
| +
|
| + // Of the two downloads, ?cookie=first will succeed and ?cookie=second will
|
| + // fail. The latter fails because the underlying storage partition was not
|
| + // persisted.
|
| +
|
| + content::DownloadItem* succeeded_download = downloads[0];
|
| + content::DownloadItem* failed_download = downloads[1];
|
| +
|
| + if (downloads[0]->GetState() == content::DownloadItem::INTERRUPTED)
|
| + std::swap(succeeded_download, failed_download);
|
| +
|
| + ASSERT_EQ(content::DownloadItem::COMPLETE, succeeded_download->GetState());
|
| + ASSERT_TRUE(base::PathExists(succeeded_download->GetTargetFilePath()));
|
| + std::string content;
|
| + ASSERT_TRUE(base::ReadFileToString(succeeded_download->GetTargetFilePath(),
|
| + &content));
|
| + // This is the cookie that should've been stored in the persisted storage
|
| + // partition.
|
| + EXPECT_STREQ("cookie=first", content.c_str());
|
| +
|
| + ASSERT_EQ(content::DownloadItem::INTERRUPTED, failed_download->GetState());
|
| + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN,
|
| + failed_download->GetLastReason());
|
| }
|
|
|
| // This test makes sure loading <webview> does not crash when there is an
|
|
|