| // 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 "content/browser/android/url_request_content_job.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/files/file_util.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_file_util.h" |
| #include "base/threading/thread_task_runner_handle.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 content { |
| |
| namespace { |
| |
| // A URLRequestJobFactory that will return URLRequestContentJobWithCallbacks |
| // instances for content:// scheme URLs. |
| class CallbacksJobFactory : public net::URLRequestJobFactory { |
| public: |
| class JobObserver { |
| public: |
| virtual ~JobObserver() {} |
| virtual void OnJobCreated() = 0; |
| }; |
| |
| CallbacksJobFactory(const base::FilePath& path, JobObserver* observer) |
| : path_(path), observer_(observer) { |
| } |
| |
| ~CallbacksJobFactory() override {} |
| |
| net::URLRequestJob* MaybeCreateJobWithProtocolHandler( |
| const std::string& scheme, |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const override { |
| URLRequestContentJob* job = |
| new URLRequestContentJob( |
| request, |
| network_delegate, |
| path_, |
| base::ThreadTaskRunnerHandle::Get()); |
| observer_->OnJobCreated(); |
| 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 == "content"; |
| } |
| |
| bool IsSafeRedirectTarget(const GURL& location) const override { |
| return false; |
| } |
| |
| private: |
| base::FilePath path_; |
| JobObserver* observer_; |
| }; |
| |
| class JobObserverImpl : public CallbacksJobFactory::JobObserver { |
| public: |
| JobObserverImpl() : num_jobs_created_(0) {} |
| ~JobObserverImpl() override {} |
| |
| void OnJobCreated() override { ++num_jobs_created_; } |
| |
| int num_jobs_created() const { return num_jobs_created_; } |
| |
| private: |
| int num_jobs_created_; |
| }; |
| |
| // A simple holder for start/end used in http range requests. |
| struct Range { |
| int start; |
| int end; |
| |
| explicit Range(int start, int end) { |
| this->start = start; |
| this->end = end; |
| } |
| }; |
| |
| // A superclass for tests of the OnSeekComplete / OnReadComplete functions of |
| // URLRequestContentJob. |
| class URLRequestContentJobTest : public testing::Test { |
| public: |
| URLRequestContentJobTest(); |
| |
| protected: |
| // This inserts an image file into the android MediaStore and retrieves the |
| // content URI. Then creates and runs a URLRequestContentJob to get the |
| // contents out of it. If a Range is provided, this function will add the |
| // appropriate Range http header to the request and verify the bytes |
| // retrieved. |
| void RunRequest(const Range* range); |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| JobObserverImpl observer_; |
| net::TestURLRequestContext context_; |
| net::TestDelegate delegate_; |
| }; |
| |
| URLRequestContentJobTest::URLRequestContentJobTest() {} |
| |
| void URLRequestContentJobTest::RunRequest(const Range* range) { |
| base::FilePath test_dir; |
| PathService::Get(base::DIR_SOURCE_ROOT, &test_dir); |
| test_dir = test_dir.AppendASCII("content"); |
| test_dir = test_dir.AppendASCII("test"); |
| test_dir = test_dir.AppendASCII("data"); |
| test_dir = test_dir.AppendASCII("android"); |
| ASSERT_TRUE(base::PathExists(test_dir)); |
| base::FilePath image_file = test_dir.Append(FILE_PATH_LITERAL("red.png")); |
| |
| // Insert the image into MediaStore. MediaStore will do some conversions, and |
| // return the content URI. |
| base::FilePath path = base::InsertImageIntoMediaStore(image_file); |
| EXPECT_TRUE(path.IsContentUri()); |
| EXPECT_TRUE(base::PathExists(path)); |
| int64_t file_size; |
| EXPECT_TRUE(base::GetFileSize(path, &file_size)); |
| EXPECT_LT(0, file_size); |
| CallbacksJobFactory factory(path, &observer_); |
| context_.set_job_factory(&factory); |
| |
| std::unique_ptr<net::URLRequest> request(context_.CreateRequest( |
| GURL(path.value()), net::DEFAULT_PRIORITY, &delegate_)); |
| int expected_length = file_size; |
| if (range) { |
| ASSERT_GE(range->start, 0); |
| ASSERT_GE(range->end, 0); |
| ASSERT_LE(range->start, range->end); |
| std::string range_value = |
| base::StringPrintf("bytes=%d-%d", range->start, range->end); |
| request->SetExtraRequestHeaderByName( |
| net::HttpRequestHeaders::kRange, range_value, true /*overwrite*/); |
| if (range->start <= file_size) { |
| expected_length = std::min(range->end, static_cast<int>(file_size - 1)) - |
| range->start + 1; |
| } else { |
| expected_length = 0; |
| } |
| } |
| request->Start(); |
| |
| base::RunLoop loop; |
| loop.Run(); |
| |
| EXPECT_FALSE(delegate_.request_failed()); |
| ASSERT_EQ(1, observer_.num_jobs_created()); |
| EXPECT_EQ(expected_length, delegate_.bytes_received()); |
| } |
| |
| TEST_F(URLRequestContentJobTest, ContentURIWithoutRange) { |
| RunRequest(NULL); |
| } |
| |
| TEST_F(URLRequestContentJobTest, ContentURIWithSmallRange) { |
| Range range(1, 10); |
| RunRequest(&range); |
| } |
| |
| TEST_F(URLRequestContentJobTest, ContentURIWithLargeRange) { |
| Range range(1, 100000); |
| RunRequest(&range); |
| } |
| |
| TEST_F(URLRequestContentJobTest, ContentURIWithZeroRange) { |
| Range range(0, 0); |
| RunRequest(&range); |
| } |
| |
| } // namespace |
| |
| } // namespace content |