| // Copyright 2014 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_job.h" |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "net/base/filename_util.h" |
| #include "net/base/net_util.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // A URLRequestFileJob for testing OnSeekComplete / OnReadComplete callbacks. |
| class URLRequestFileJobWithCallbacks : public URLRequestFileJob { |
| public: |
| URLRequestFileJobWithCallbacks( |
| URLRequest* request, |
| NetworkDelegate* network_delegate, |
| const base::FilePath& file_path, |
| const scoped_refptr<base::TaskRunner>& file_task_runner) |
| : URLRequestFileJob(request, |
| network_delegate, |
| file_path, |
| file_task_runner), |
| seek_position_(0) { |
| } |
| |
| int64 seek_position() { return seek_position_; } |
| const std::vector<std::string>& data_chunks() { return data_chunks_; } |
| |
| protected: |
| ~URLRequestFileJobWithCallbacks() override {} |
| |
| void OnSeekComplete(int64 result) override { |
| ASSERT_EQ(seek_position_, 0); |
| seek_position_ = result; |
| } |
| |
| void OnReadComplete(IOBuffer* buf, int result) override { |
| data_chunks_.push_back(std::string(buf->data(), result)); |
| } |
| |
| int64 seek_position_; |
| std::vector<std::string> data_chunks_; |
| }; |
| |
| // A URLRequestJobFactory that will return URLRequestFileJobWithCallbacks |
| // instances for file:// scheme URLs. |
| class CallbacksJobFactory : public URLRequestJobFactory { |
| public: |
| class JobObserver { |
| public: |
| virtual void OnJobCreated(URLRequestFileJobWithCallbacks* job) = 0; |
| }; |
| |
| CallbacksJobFactory(const base::FilePath& path, JobObserver* observer) |
| : path_(path), observer_(observer) { |
| } |
| |
| ~CallbacksJobFactory() override {} |
| |
| URLRequestJob* MaybeCreateJobWithProtocolHandler( |
| const std::string& scheme, |
| URLRequest* request, |
| NetworkDelegate* network_delegate) const override { |
| URLRequestFileJobWithCallbacks* job = new URLRequestFileJobWithCallbacks( |
| request, |
| network_delegate, |
| path_, |
| base::MessageLoop::current()->message_loop_proxy()); |
| observer_->OnJobCreated(job); |
| return job; |
| } |
| |
| net::URLRequestJob* MaybeInterceptRedirect( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate, |
| const GURL& location) const override { |
| return nullptr; |
| } |
| |
| net::URLRequestJob* MaybeInterceptResponse( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const override { |
| return nullptr; |
| } |
| |
| bool IsHandledProtocol(const std::string& scheme) const override { |
| return scheme == "file"; |
| } |
| |
| bool IsHandledURL(const GURL& url) const override { |
| return IsHandledProtocol(url.scheme()); |
| } |
| |
| bool IsSafeRedirectTarget(const GURL& location) const override { |
| return false; |
| } |
| |
| private: |
| base::FilePath path_; |
| JobObserver* observer_; |
| }; |
| |
| // Helper function to create a file in |directory| filled with |
| // |content|. Returns true on succes and fills in |path| with the full path to |
| // the file. |
| bool CreateTempFileWithContent(const std::string& content, |
| const base::ScopedTempDir& directory, |
| base::FilePath* path) { |
| if (!directory.IsValid()) |
| return false; |
| |
| if (!base::CreateTemporaryFileInDir(directory.path(), path)) |
| return false; |
| |
| return base::WriteFile(*path, content.c_str(), content.length()); |
| } |
| |
| class JobObserverImpl : public CallbacksJobFactory::JobObserver { |
| public: |
| void OnJobCreated(URLRequestFileJobWithCallbacks* job) override { |
| jobs_.push_back(job); |
| } |
| |
| typedef std::vector<scoped_refptr<URLRequestFileJobWithCallbacks> > JobList; |
| |
| const JobList& jobs() { return jobs_; } |
| |
| protected: |
| JobList jobs_; |
| }; |
| |
| // A simple holder for start/end used in http range requests. |
| struct Range { |
| int start; |
| int end; |
| |
| Range() { |
| start = 0; |
| end = 0; |
| } |
| |
| Range(int start, int end) { |
| this->start = start; |
| this->end = end; |
| } |
| }; |
| |
| // A superclass for tests of the OnSeekComplete / OnReadComplete functions of |
| // URLRequestFileJob. |
| class URLRequestFileJobEventsTest : public testing::Test { |
| public: |
| URLRequestFileJobEventsTest(); |
| |
| protected: |
| // This creates a file with |content| as the contents, and then creates and |
| // runs a URLRequestFileJobWithCallbacks job to get the contents out of it, |
| // and makes sure that the callbacks observed the correct bytes. If a Range |
| // is provided, this function will add the appropriate Range http header to |
| // the request and verify that only the bytes in that range (inclusive) were |
| // observed. |
| void RunRequest(const std::string& content, const Range* range); |
| |
| JobObserverImpl observer_; |
| TestURLRequestContext context_; |
| TestDelegate delegate_; |
| }; |
| |
| URLRequestFileJobEventsTest::URLRequestFileJobEventsTest() {} |
| |
| void URLRequestFileJobEventsTest::RunRequest(const std::string& content, |
| const Range* range) { |
| base::ScopedTempDir directory; |
| ASSERT_TRUE(directory.CreateUniqueTempDir()); |
| base::FilePath path; |
| ASSERT_TRUE(CreateTempFileWithContent(content, directory, &path)); |
| CallbacksJobFactory factory(path, &observer_); |
| context_.set_job_factory(&factory); |
| |
| scoped_ptr<URLRequest> request(context_.CreateRequest( |
| FilePathToFileURL(path), DEFAULT_PRIORITY, &delegate_, NULL)); |
| if (range) { |
| ASSERT_GE(range->start, 0); |
| ASSERT_GE(range->end, 0); |
| ASSERT_LE(range->start, range->end); |
| ASSERT_LT(static_cast<unsigned int>(range->end), content.length()); |
| std::string range_value = |
| base::StringPrintf("bytes=%d-%d", range->start, range->end); |
| request->SetExtraRequestHeaderByName( |
| HttpRequestHeaders::kRange, range_value, true /*overwrite*/); |
| } |
| request->Start(); |
| |
| base::RunLoop loop; |
| loop.Run(); |
| |
| EXPECT_FALSE(delegate_.request_failed()); |
| int expected_length = |
| range ? (range->end - range->start + 1) : content.length(); |
| EXPECT_EQ(delegate_.bytes_received(), expected_length); |
| |
| std::string expected_content; |
| if (range) { |
| expected_content.insert(0, content, range->start, expected_length); |
| } else { |
| expected_content = content; |
| } |
| EXPECT_TRUE(delegate_.data_received() == expected_content); |
| |
| ASSERT_EQ(observer_.jobs().size(), 1u); |
| ASSERT_EQ(observer_.jobs().at(0)->seek_position(), range ? range->start : 0); |
| |
| std::string observed_content; |
| const std::vector<std::string>& chunks = |
| observer_.jobs().at(0)->data_chunks(); |
| for (std::vector<std::string>::const_iterator i = chunks.begin(); |
| i != chunks.end(); |
| ++i) { |
| observed_content.append(*i); |
| } |
| EXPECT_EQ(expected_content, observed_content); |
| } |
| |
| // Helper function to make a character array filled with |size| bytes of |
| // test content. |
| std::string MakeContentOfSize(int size) { |
| EXPECT_GE(size, 0); |
| std::string result; |
| result.reserve(size); |
| for (int i = 0; i < size; i++) { |
| result.append(1, static_cast<char>(i % 256)); |
| } |
| return result; |
| } |
| |
| TEST_F(URLRequestFileJobEventsTest, TinyFile) { |
| RunRequest(std::string("hello world"), NULL); |
| } |
| |
| TEST_F(URLRequestFileJobEventsTest, SmallFile) { |
| RunRequest(MakeContentOfSize(17 * 1024), NULL); |
| } |
| |
| TEST_F(URLRequestFileJobEventsTest, BigFile) { |
| RunRequest(MakeContentOfSize(3 * 1024 * 1024), NULL); |
| } |
| |
| TEST_F(URLRequestFileJobEventsTest, Range) { |
| // Use a 15KB content file and read a range chosen somewhat arbitrarily but |
| // not aligned on any likely page boundaries. |
| int size = 15 * 1024; |
| Range range(1701, (6 * 1024) + 3); |
| RunRequest(MakeContentOfSize(size), &range); |
| } |
| |
| } // namespace |
| |
| } // namespace net |