blob: e5afa8f37af64901f21bfd5b5d9cacc49105a162 [file] [log] [blame]
// Copyright 2016 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 "net/url_request/url_request_file_dir_job.h"
#include <memory>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "net/base/filename_util.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_scoped_task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::StartsWith;
using net::test::IsError;
using net::test::IsOk;
namespace net {
namespace {
const int kBufferSize = 4096;
// Snippets of JS code from net/base/dir_header.html.
const char kHeaderStart[] = "<script>start(\"";
const char kEntryStart[] = "<script>addRow(\"";
const char kParentDirLink[] = "<script>onHasParentDirectory();";
bool HasHeader(const std::string& response_body, const base::FilePath& dir) {
std::vector<std::string> lines = base::SplitString(
response_body, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
for (const std::string& line : lines) {
if (StartsWith(line, kHeaderStart, base::CompareCase::INSENSITIVE_ASCII))
return line.find(dir.BaseName().MaybeAsASCII()) != std::string::npos;
}
return false;
}
bool HasParentDirLink(const std::string& response_body) {
return response_body.find(kParentDirLink) != std::string::npos;
}
// There should not be any entries for the parent dir, so this should always
// return false.
bool HasParentDirEntry(const std::string& response_body) {
std::string needle = kEntryStart;
needle += "..\"";
return response_body.find(needle) != std::string::npos;
}
bool HasEntry(const std::string& response_body, const base::FilePath& entry) {
std::string needle = kEntryStart + entry.BaseName().MaybeAsASCII();
return response_body.find(needle) != std::string::npos;
}
int GetEntryCount(const std::string& response_body) {
int count = 0;
std::vector<std::string> lines = base::SplitString(
response_body, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
for (const std::string& line : lines) {
if (StartsWith(line, kEntryStart, base::CompareCase::INSENSITIVE_ASCII))
++count;
}
return count;
}
class TestJobFactory : public URLRequestJobFactory {
public:
explicit TestJobFactory(const base::FilePath& path) : path_(path) {}
~TestJobFactory() override = default;
URLRequestJob* MaybeCreateJobWithProtocolHandler(
const std::string& scheme,
URLRequest* request,
NetworkDelegate* network_delegate) const override {
URLRequestJob* job =
new URLRequestFileDirJob(request, network_delegate, path_);
return job;
}
URLRequestJob* MaybeInterceptRedirect(URLRequest* request,
NetworkDelegate* network_delegate,
const GURL& location) const override {
return nullptr;
}
URLRequestJob* MaybeInterceptResponse(
URLRequest* request,
NetworkDelegate* network_delegate) const override {
return nullptr;
}
bool IsHandledProtocol(const std::string& scheme) const override {
return scheme == "file";
}
bool IsSafeRedirectTarget(const GURL& location) const override {
return false;
}
private:
const base::FilePath path_;
DISALLOW_COPY_AND_ASSIGN(TestJobFactory);
};
class TestDirectoryURLRequestDelegate : public TestDelegate {
public:
TestDirectoryURLRequestDelegate() = default;
~TestDirectoryURLRequestDelegate() override = default;
void OnResponseStarted(URLRequest* request, int net_error) override {
got_response_started_ = true;
}
bool got_response_started() const { return got_response_started_; }
private:
bool got_response_started_ = false;
DISALLOW_COPY_AND_ASSIGN(TestDirectoryURLRequestDelegate);
};
class URLRequestFileDirTest : public TestWithScopedTaskEnvironment {
public:
URLRequestFileDirTest()
: buffer_(base::MakeRefCounted<IOBuffer>(kBufferSize)) {}
protected:
TestURLRequestContext context_;
TestDirectoryURLRequestDelegate delegate_;
scoped_refptr<IOBuffer> buffer_;
};
TEST_F(URLRequestFileDirTest, ListCompletionOnNoPending) {
base::ScopedTempDir directory;
// It is necessary to pass an existing directory to UrlRequest object,
// but it will be deleted for testing purpose after request is started.
ASSERT_TRUE(directory.CreateUniqueTempDir());
TestJobFactory factory(directory.GetPath());
context_.set_job_factory(&factory);
std::unique_ptr<URLRequest> request(context_.CreateRequest(
FilePathToFileURL(
directory.GetPath().AppendASCII("this_path_does_not_exist")),
DEFAULT_PRIORITY, &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
ASSERT_TRUE(directory.Delete());
// Since the DirectoryLister is running on the network thread, this
// will spin the message loop until the read error is returned to the
// URLRequestFileDirJob.
RunUntilIdle();
ASSERT_TRUE(delegate_.got_response_started());
int bytes_read = request->Read(buffer_.get(), kBufferSize);
// The URLRequestFileDirJobShould return the cached read error synchronously.
// If it's not returned synchronously, the code path this is intended to test
// was not executed.
EXPECT_THAT(bytes_read, IsError(ERR_FILE_NOT_FOUND));
}
// Test the case where reading the response completes synchronously.
TEST_F(URLRequestFileDirTest, DirectoryWithASingleFileSync) {
base::ScopedTempDir directory;
ASSERT_TRUE(directory.CreateUniqueTempDir());
base::FilePath path;
base::CreateTemporaryFileInDir(directory.GetPath(), &path);
TestJobFactory factory(directory.GetPath());
context_.set_job_factory(&factory);
std::unique_ptr<URLRequest> request(
context_.CreateRequest(FilePathToFileURL(path), DEFAULT_PRIORITY,
&delegate_, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
EXPECT_TRUE(request->is_pending());
// Since the DirectoryLister is running on the network thread, this will spin
// the message loop until the URLRequetsFileDirJob has received the
// entire directory listing and cached it.
RunUntilIdle();
// This will complete synchronously, since the URLRequetsFileDirJob had
// directory listing cached in memory.
int bytes_read = request->Read(buffer_.get(), kBufferSize);
ASSERT_GT(bytes_read, 0);
ASSERT_LE(bytes_read, kBufferSize);
std::string data(buffer_->data(), bytes_read);
EXPECT_TRUE(HasHeader(data, directory.GetPath()));
EXPECT_TRUE(HasParentDirLink(data));
ASSERT_EQ(1, GetEntryCount(data));
EXPECT_TRUE(HasEntry(data, path));
EXPECT_FALSE(HasParentDirEntry(data));
}
// Test the case where reading the response completes asynchronously.
TEST_F(URLRequestFileDirTest, DirectoryWithASingleFileAsync) {
base::ScopedTempDir directory;
ASSERT_TRUE(directory.CreateUniqueTempDir());
base::FilePath path;
base::CreateTemporaryFileInDir(directory.GetPath(), &path);
TestJobFactory factory(directory.GetPath());
context_.set_job_factory(&factory);
TestDelegate delegate;
std::unique_ptr<URLRequest> request(
context_.CreateRequest(FilePathToFileURL(path), DEFAULT_PRIORITY,
&delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
EXPECT_TRUE(request->is_pending());
base::RunLoop().Run();
ASSERT_GT(delegate.bytes_received(), 0);
ASSERT_LE(delegate.bytes_received(), kBufferSize);
EXPECT_TRUE(HasHeader(delegate.data_received(), directory.GetPath()));
EXPECT_TRUE(HasParentDirLink(delegate.data_received()));
ASSERT_EQ(1, GetEntryCount(delegate.data_received()));
EXPECT_TRUE(HasEntry(delegate.data_received(), path));
EXPECT_FALSE(HasParentDirEntry(delegate.data_received()));
}
TEST_F(URLRequestFileDirTest, DirectoryWithAFileAndSubdirectory) {
base::ScopedTempDir directory;
ASSERT_TRUE(directory.CreateUniqueTempDir());
base::FilePath sub_dir;
CreateTemporaryDirInDir(directory.GetPath(),
FILE_PATH_LITERAL("CreateNewSubDirectoryInDirectory"),
&sub_dir);
base::FilePath path;
base::CreateTemporaryFileInDir(directory.GetPath(), &path);
TestJobFactory factory(directory.GetPath());
context_.set_job_factory(&factory);
TestDelegate delegate;
std::unique_ptr<URLRequest> request(
context_.CreateRequest(FilePathToFileURL(path), DEFAULT_PRIORITY,
&delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
EXPECT_TRUE(request->is_pending());
base::RunLoop().Run();
ASSERT_GT(delegate.bytes_received(), 0);
ASSERT_LE(delegate.bytes_received(), kBufferSize);
EXPECT_TRUE(HasHeader(delegate.data_received(), directory.GetPath()));
EXPECT_TRUE(HasParentDirLink(delegate.data_received()));
ASSERT_EQ(2, GetEntryCount(delegate.data_received()));
EXPECT_TRUE(HasEntry(delegate.data_received(), sub_dir));
EXPECT_TRUE(HasEntry(delegate.data_received(), path));
EXPECT_FALSE(HasParentDirEntry(delegate.data_received()));
}
TEST_F(URLRequestFileDirTest, EmptyDirectory) {
base::ScopedTempDir directory;
ASSERT_TRUE(directory.CreateUniqueTempDir());
TestJobFactory factory(directory.GetPath());
context_.set_job_factory(&factory);
TestDelegate delegate;
std::unique_ptr<URLRequest> request(context_.CreateRequest(
FilePathToFileURL(directory.GetPath()), DEFAULT_PRIORITY, &delegate,
TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
EXPECT_TRUE(request->is_pending());
base::RunLoop().Run();
ASSERT_GT(delegate.bytes_received(), 0);
ASSERT_LE(delegate.bytes_received(), kBufferSize);
EXPECT_TRUE(HasHeader(delegate.data_received(), directory.GetPath()));
EXPECT_TRUE(HasParentDirLink(delegate.data_received()));
ASSERT_EQ(0, GetEntryCount(delegate.data_received()));
EXPECT_FALSE(HasParentDirEntry(delegate.data_received()));
}
// Android security policies prevent access to the root directory, so skip this
// test there.
#if !defined(OS_ANDROID)
TEST_F(URLRequestFileDirTest, RootDirectory) {
for (int slashes_to_test = 1; slashes_to_test < 4; ++slashes_to_test) {
base::FilePath::StringType root_dir_string;
#if defined(OS_WIN)
root_dir_string = L"C:";
#endif
root_dir_string.append(slashes_to_test, base::FilePath::kSeparators[0]);
base::FilePath root_dir(root_dir_string);
TestJobFactory factory(root_dir);
context_.set_job_factory(&factory);
TestDelegate delegate;
std::unique_ptr<URLRequest> request(
context_.CreateRequest(FilePathToFileURL(root_dir), DEFAULT_PRIORITY,
&delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
EXPECT_TRUE(request->is_pending());
base::RunLoop().Run();
ASSERT_GT(delegate.bytes_received(), 0);
ASSERT_LE(delegate.bytes_received(), kBufferSize);
EXPECT_TRUE(HasHeader(delegate.data_received(), root_dir));
EXPECT_FALSE(HasParentDirLink(delegate.data_received()));
EXPECT_GT(GetEntryCount(delegate.data_received()), 0);
EXPECT_FALSE(HasParentDirEntry(delegate.data_received()));
}
}
#endif // !defined(OS_ANDROID)
} // namespace
} // namespace net