#include "base/memory/raw_ptr.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#if BUILDFLAG(IS_WIN)
#include <objbase.h>
#include <windows.h>
#include <shlobj.h>
#include <wrl/client.h>
#endif
#include <string>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "net/base/filename_util.h"
#include "net/base/net_errors.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/gtest_util.h"
#include "url/gurl.h"
namespace content {
namespace {
const char16_t kSuccessTitle[] = u"Title Of Awesomeness";
const char16_t kErrorTitle[] = u"Error";
base::FilePath TestFilePath() {
base::ScopedAllowBlockingForTesting allow_blocking;
return GetTestFilePath("", "title2.html");
}
base::FilePath AbsoluteFilePath(const base::FilePath& file_path) {
base::ScopedAllowBlockingForTesting allow_blocking;
return base::MakeAbsoluteFilePath(file_path);
}
class TestFileAccessContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
struct FileAccessAllowedArgs {
base::FilePath path;
base::FilePath absolute_path;
base::FilePath profile_path;
};
TestFileAccessContentBrowserClient() = default;
void set_blocked_path(const base::FilePath& blocked_path) {
blocked_path_ = AbsoluteFilePath(blocked_path);
}
TestFileAccessContentBrowserClient(
const TestFileAccessContentBrowserClient&) = delete;
TestFileAccessContentBrowserClient& operator=(
const TestFileAccessContentBrowserClient&) = delete;
~TestFileAccessContentBrowserClient() override = default;
bool IsFileAccessAllowed(const base::FilePath& path,
const base::FilePath& absolute_path,
const base::FilePath& profile_path) override {
access_allowed_args_.push_back(
FileAccessAllowedArgs{path, absolute_path, profile_path});
return blocked_path_ != absolute_path;
}
const std::vector<FileAccessAllowedArgs>& access_allowed_args() const {
return access_allowed_args_;
}
void ClearAccessAllowedArgs() { access_allowed_args_.clear(); }
private:
base::FilePath blocked_path_;
std::vector<FileAccessAllowedArgs> access_allowed_args_;
};
class FileURLLoaderFactoryBrowserTest : public ContentBrowserTest {
public:
FileURLLoaderFactoryBrowserTest() = default;
void SetUpOnMainThread() override {
base::FilePath test_data_path;
EXPECT_TRUE(base::PathService::Get(DIR_TEST_DATA, &test_data_path));
embedded_test_server()->ServeFilesFromDirectory(test_data_path);
EXPECT_TRUE(embedded_test_server()->Start());
}
base::FilePath ProfilePath() const {
return shell()
->web_contents()
->GetSiteInstance()
->GetBrowserContext()
->GetPath();
}
GURL RedirectToFileURL() const {
return embedded_test_server()->GetURL(
"/server-redirect?" + net::FilePathToFileURL(TestFilePath()).spec());
}
};
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest, Basic) {
TestFileAccessContentBrowserClient test_browser_client;
EXPECT_TRUE(NavigateToURL(shell(), net::FilePathToFileURL(TestFilePath())));
EXPECT_EQ(kSuccessTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(1u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(TestFilePath(), test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(TestFilePath()),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
}
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest, FileAccessNotAllowed) {
TestFileAccessContentBrowserClient test_browser_client;
test_browser_client.set_blocked_path(TestFilePath());
TestNavigationObserver navigation_observer(shell()->web_contents());
EXPECT_FALSE(NavigateToURL(shell(), net::FilePathToFileURL(TestFilePath())));
EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
EXPECT_THAT(navigation_observer.last_net_error_code(),
net::test::IsError(net::ERR_ACCESS_DENIED));
EXPECT_EQ(net::FilePathToFileURL(TestFilePath()),
shell()->web_contents()->GetURL());
EXPECT_EQ(kErrorTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(1u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(TestFilePath(), test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(TestFilePath()),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
}
#if BUILDFLAG(IS_POSIX)
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest, SymlinksToFiles) {
TestFileAccessContentBrowserClient test_browser_client;
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath absolute_temp_dir = AbsoluteFilePath(temp_dir.GetPath());
base::FilePath sym_link = absolute_temp_dir.AppendASCII("link.bin");
ASSERT_TRUE(
base::CreateSymbolicLink(AbsoluteFilePath(TestFilePath()), sym_link));
EXPECT_TRUE(NavigateToURL(shell(), net::FilePathToFileURL(sym_link)));
EXPECT_EQ(kSuccessTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(1u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(sym_link, test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(sym_link),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
test_browser_client.ClearAccessAllowedArgs();
test_browser_client.set_blocked_path(TestFilePath());
TestNavigationObserver navigation_observer3(shell()->web_contents());
EXPECT_FALSE(NavigateToURL(shell(), net::FilePathToFileURL(sym_link)));
EXPECT_FALSE(navigation_observer3.last_navigation_succeeded());
EXPECT_THAT(navigation_observer3.last_net_error_code(),
net::test::IsError(net::ERR_ACCESS_DENIED));
EXPECT_EQ(net::FilePathToFileURL(sym_link),
shell()->web_contents()->GetURL());
EXPECT_EQ(kErrorTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(1u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(sym_link, test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(sym_link),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
}
#elif BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest, ResolveShortcutTest) {
TestFileAccessContentBrowserClient test_browser_client;
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath lnk_path =
temp_dir.GetPath().Append(FILE_PATH_LITERAL("foo.lnk"));
base::FilePath test = TestFilePath();
{
Microsoft::WRL::ComPtr<IShellLink> shell;
ASSERT_TRUE(SUCCEEDED(::CoCreateInstance(
CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shell))));
Microsoft::WRL::ComPtr<IPersistFile> persist;
ASSERT_TRUE(SUCCEEDED(shell.As<IPersistFile>(&persist)));
EXPECT_TRUE(SUCCEEDED(shell->SetPath(TestFilePath().value().c_str())));
EXPECT_TRUE(SUCCEEDED(shell->SetDescription(L"ResolveShortcutTest")));
std::wstring lnk_string = lnk_path.value();
EXPECT_TRUE(SUCCEEDED(persist->Save(lnk_string.c_str(), TRUE)));
}
EXPECT_TRUE(NavigateToURL(
shell(), net::FilePathToFileURL(lnk_path),
net::FilePathToFileURL(TestFilePath()) ));
EXPECT_EQ(kSuccessTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(2u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(lnk_path, test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(lnk_path),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
EXPECT_EQ(TestFilePath(), test_browser_client.access_allowed_args()[1].path);
EXPECT_EQ(AbsoluteFilePath(TestFilePath()),
test_browser_client.access_allowed_args()[1].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[1].profile_path);
test_browser_client.ClearAccessAllowedArgs();
test_browser_client.set_blocked_path(lnk_path);
TestNavigationObserver navigation_observer2(shell()->web_contents());
EXPECT_FALSE(NavigateToURL(shell(), net::FilePathToFileURL(lnk_path)));
EXPECT_FALSE(navigation_observer2.last_navigation_succeeded());
EXPECT_THAT(navigation_observer2.last_net_error_code(),
net::test::IsError(net::ERR_ACCESS_DENIED));
EXPECT_EQ(net::FilePathToFileURL(lnk_path),
shell()->web_contents()->GetURL());
EXPECT_EQ(kErrorTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(1u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(lnk_path, test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(lnk_path),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
test_browser_client.ClearAccessAllowedArgs();
test_browser_client.set_blocked_path(TestFilePath());
TestNavigationObserver navigation_observer3(shell()->web_contents());
EXPECT_FALSE(NavigateToURL(shell(), net::FilePathToFileURL(lnk_path)));
EXPECT_FALSE(navigation_observer3.last_navigation_succeeded());
EXPECT_THAT(navigation_observer3.last_net_error_code(),
net::test::IsError(net::ERR_ACCESS_DENIED));
EXPECT_EQ(net::FilePathToFileURL(TestFilePath()),
shell()->web_contents()->GetURL());
EXPECT_EQ(kErrorTitle, shell()->web_contents()->GetTitle());
ASSERT_EQ(2u, test_browser_client.access_allowed_args().size());
EXPECT_EQ(lnk_path, test_browser_client.access_allowed_args()[0].path);
EXPECT_EQ(AbsoluteFilePath(lnk_path),
test_browser_client.access_allowed_args()[0].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[0].profile_path);
EXPECT_EQ(TestFilePath(), test_browser_client.access_allowed_args()[1].path);
EXPECT_EQ(AbsoluteFilePath(TestFilePath()),
test_browser_client.access_allowed_args()[1].absolute_path);
EXPECT_EQ(ProfilePath(),
test_browser_client.access_allowed_args()[1].profile_path);
}
#endif
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest,
RedirectToFileUrlMainFrame) {
TestFileAccessContentBrowserClient test_browser_client;
TestNavigationObserver navigation_observer(shell()->web_contents());
EXPECT_FALSE(NavigateToURL(shell(), RedirectToFileURL()));
EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
EXPECT_THAT(navigation_observer.last_net_error_code(),
net::test::IsError(net::ERR_UNSAFE_REDIRECT));
EXPECT_EQ(RedirectToFileURL(), shell()->web_contents()->GetURL());
EXPECT_EQ(kErrorTitle, shell()->web_contents()->GetTitle());
EXPECT_TRUE(test_browser_client.access_allowed_args().empty());
TestNavigationObserver navigation_observer2(shell()->web_contents());
shell()->Reload();
navigation_observer2.Wait();
EXPECT_FALSE(navigation_observer2.last_navigation_succeeded());
EXPECT_THAT(navigation_observer2.last_net_error_code(),
net::test::IsError(net::ERR_UNSAFE_REDIRECT));
EXPECT_EQ(RedirectToFileURL(), shell()->web_contents()->GetURL());
EXPECT_EQ(kErrorTitle, shell()->web_contents()->GetTitle());
EXPECT_TRUE(test_browser_client.access_allowed_args().empty());
}
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest,
RedirectToFileUrlFetch) {
TestFileAccessContentBrowserClient test_browser_client;
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
std::string fetch_redirect_to_file = base::StringPrintf(
"(async () => {"
" try {"
" var resp = (await fetch('%s'));"
" return 'ok';"
" } catch (error) {"
" return 'error';"
" }"
"})();",
RedirectToFileURL().spec().c_str());
EXPECT_EQ("error", EvalJs(shell()->web_contents()->GetPrimaryMainFrame(),
fetch_redirect_to_file));
EXPECT_TRUE(test_browser_client.access_allowed_args().empty());
}
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest,
RedirectToFileUrlSubFrame) {
TestFileAccessContentBrowserClient test_browser_client;
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/page_with_iframe.html")));
LOG(WARNING) << embedded_test_server()->GetURL("/page_with_iframe.html");
TestNavigationObserver navigation_observer(shell()->web_contents());
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test_iframe",
RedirectToFileURL()));
navigation_observer.Wait();
EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
EXPECT_THAT(navigation_observer.last_net_error_code(),
net::test::IsError(net::ERR_UNSAFE_REDIRECT));
EXPECT_TRUE(test_browser_client.access_allowed_args().empty());
}
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest,
LifetimeOfClonedFactory) {
GURL opener_url(GetTestUrl("", "title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), opener_url));
WebContentsAddedObserver popup_observer;
ASSERT_TRUE(ExecJs(shell(), "window.open('', '_blank')"));
WebContents* popup = popup_observer.GetWebContents();
EXPECT_EQ(true, EvalJs(popup, "!!window.opener"));
shell()->Close();
const char kScriptToWaitForNoOpener[] = R"(
new Promise(function (resolve, reject) {
function CheckAndWaitMoreIfNeeded() {
if (!window.opener)
resolve('OPENER GONE');
setTimeout(CheckAndWaitMoreIfNeeded, 50);
}
CheckAndWaitMoreIfNeeded();
});
)";
EXPECT_EQ("OPENER GONE", EvalJs(popup, kScriptToWaitForNoOpener));
const char kScriptTemplateToTriggerSubresourceFetch[] = R"(
new Promise(function (resolve, reject) {
var img = document.createElement('img');
img.src = $1;
img.onload = _ => resolve('OK');
img.onerror = e => resolve('ERR: ' + e);
});
)";
GURL img_url = GetTestUrl("site_isolation", "png-corp.png");
EXPECT_EQ("OK",
EvalJs(popup, JsReplace(kScriptTemplateToTriggerSubresourceFetch,
img_url)));
}
class FileURLLoaderFactoryDisabledSecurityBrowserTest
: public FileURLLoaderFactoryBrowserTest {
public:
FileURLLoaderFactoryDisabledSecurityBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kDisableWebSecurity);
FileURLLoaderFactoryBrowserTest::SetUpCommandLine(command_line);
}
};
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryDisabledSecurityBrowserTest,
SubresourcesInSandboxedFileFrame) {
GURL main_url(GetTestUrl("", "title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
{
GURL sandboxed_url(GetTestUrl("", "title2.html"));
TestNavigationObserver nav_observer(shell()->web_contents());
const char kScriptTemplateToAddSubframe[] = R"(
f = document.createElement('iframe');
f.sandbox = 'allow-scripts';
f.src = $1;
document.body.appendChild(f);
)";
ASSERT_TRUE(ExecJs(shell(),
JsReplace(kScriptTemplateToAddSubframe, sandboxed_url)));
nav_observer.Wait();
}
RenderFrameHost* main_frame = shell()->web_contents()->GetPrimaryMainFrame();
RenderFrameHost* child_frame = ChildFrameAt(main_frame, 0);
const char kScriptTemplateToTriggerSubresourceFetch[] = R"(
new Promise(function (resolve, reject) {
var img = document.createElement('img');
img.src = $1;
img.onload = _ => resolve('OK');
img.onerror = e => resolve('ERR: ' + e);
});
)";
GURL img_url = GetTestUrl("site_isolation", "png-corp.png");
EXPECT_EQ("OK", EvalJs(child_frame,
JsReplace(kScriptTemplateToTriggerSubresourceFetch,
img_url)));
}
IN_PROC_BROWSER_TEST_F(FileURLLoaderFactoryBrowserTest, LastModified) {
const char kLastModified[] = "1994-11-15T12:45:26.000Z";
base::FilePath path;
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(base::CreateTemporaryFile(&path));
base::Time last_modified_time;
ASSERT_TRUE(base::Time::FromString(kLastModified, &last_modified_time));
ASSERT_TRUE(base::TouchFile(path, base::Time::Now(),
last_modified_time));
}
EXPECT_TRUE(NavigateToURL(shell(), net::FilePathToFileURL(path)));
EXPECT_THAT(content::EvalJs(shell()->web_contents(), "document.lastModified")
.ExtractString(),
testing::MatchesRegex(R"(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d)"));
EXPECT_EQ(kLastModified,
content::EvalJs(shell()->web_contents(),
"new Date(document.lastModified).toISOString()"));
}
}
}