summaryrefslogtreecommitdiffstats
path: root/content/browser
diff options
context:
space:
mode:
Diffstat (limited to 'content/browser')
-rw-r--r--content/browser/android/content_protocol_handler_impl.cc43
-rw-r--r--content/browser/android/content_protocol_handler_impl.h45
-rw-r--r--content/browser/android/url_request_content_job.cc228
-rw-r--r--content/browser/android/url_request_content_job.h103
-rw-r--r--content/browser/android/url_request_content_job_unittest.cc198
5 files changed, 617 insertions, 0 deletions
diff --git a/content/browser/android/content_protocol_handler_impl.cc b/content/browser/android/content_protocol_handler_impl.cc
new file mode 100644
index 0000000..f5e5d20
--- /dev/null
+++ b/content/browser/android/content_protocol_handler_impl.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 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/content_protocol_handler_impl.h"
+
+#include "base/task_runner.h"
+#include "content/browser/android/url_request_content_job.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+
+namespace content {
+
+// static
+ContentProtocolHandler* ContentProtocolHandler::Create(
+ const scoped_refptr<base::TaskRunner>& content_task_runner) {
+ return new ContentProtocolHandlerImpl(content_task_runner);
+}
+
+ContentProtocolHandlerImpl::ContentProtocolHandlerImpl(
+ const scoped_refptr<base::TaskRunner>& content_task_runner)
+ : content_task_runner_(content_task_runner) {}
+
+ContentProtocolHandlerImpl::~ContentProtocolHandlerImpl() {}
+
+net::URLRequestJob* ContentProtocolHandlerImpl::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ if (!network_delegate) {
+ return new net::URLRequestErrorJob(
+ request, network_delegate, net::ERR_ACCESS_DENIED);
+ }
+ return new URLRequestContentJob(
+ request, network_delegate, base::FilePath(request->url().spec()),
+ content_task_runner_);
+}
+
+bool ContentProtocolHandlerImpl::IsSafeRedirectTarget(
+ const GURL& location) const {
+ return false;
+}
+
+} // namespace content
diff --git a/content/browser/android/content_protocol_handler_impl.h b/content/browser/android/content_protocol_handler_impl.h
new file mode 100644
index 0000000..f593a84
--- /dev/null
+++ b/content/browser/android/content_protocol_handler_impl.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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.
+
+#ifndef CONTENT_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/android/content_protocol_handler.h"
+
+class GURL;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+class NetworkDelegate;
+class URLRequestJob;
+}
+
+namespace content {
+
+// Implements a ProtocolHandler for content scheme jobs. If |network_delegate_|
+// is NULL, then all file requests will fail with ERR_ACCESS_DENIED.
+class ContentProtocolHandlerImpl : public ContentProtocolHandler {
+ public:
+ explicit ContentProtocolHandlerImpl(
+ const scoped_refptr<base::TaskRunner>& content_task_runner);
+ ~ContentProtocolHandlerImpl() override;
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+ bool IsSafeRedirectTarget(const GURL& location) const override;
+
+ private:
+ const scoped_refptr<base::TaskRunner> content_task_runner_;
+ DISALLOW_COPY_AND_ASSIGN(ContentProtocolHandlerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_PROTOCOL_HANDLER_IMPL_H_
diff --git a/content/browser/android/url_request_content_job.cc b/content/browser/android/url_request_content_job.cc
new file mode 100644
index 0000000..0ba13e0
--- /dev/null
+++ b/content/browser/android/url_request_content_job.cc
@@ -0,0 +1,228 @@
+// Copyright (c) 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 "base/android/content_uri_utils.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/task_runner.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_error_job.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// TODO(qinmin): Refactor this class to reuse the common code in
+// url_request_file_job.cc.
+URLRequestContentJob::ContentMetaInfo::ContentMetaInfo()
+ : content_exists(false),
+ content_size(0) {
+}
+
+URLRequestContentJob::URLRequestContentJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& content_path,
+ const scoped_refptr<base::TaskRunner>& content_task_runner)
+ : net::URLRequestJob(request, network_delegate),
+ content_path_(content_path),
+ stream_(new net::FileStream(content_task_runner)),
+ content_task_runner_(content_task_runner),
+ remaining_bytes_(0),
+ io_pending_(false),
+ weak_ptr_factory_(this) {}
+
+void URLRequestContentJob::Start() {
+ ContentMetaInfo* meta_info = new ContentMetaInfo();
+ content_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&URLRequestContentJob::FetchMetaInfo, content_path_,
+ base::Unretained(meta_info)),
+ base::Bind(&URLRequestContentJob::DidFetchMetaInfo,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(meta_info)));
+}
+
+void URLRequestContentJob::Kill() {
+ stream_.reset();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ net::URLRequestJob::Kill();
+}
+
+bool URLRequestContentJob::ReadRawData(net::IOBuffer* dest,
+ int dest_size,
+ int* bytes_read) {
+ DCHECK_GT(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ // If we should copy zero bytes because |remaining_bytes_| is zero, short
+ // circuit here.
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ int rv = stream_->Read(dest,
+ dest_size,
+ base::Bind(&URLRequestContentJob::DidRead,
+ weak_ptr_factory_.GetWeakPtr(),
+ make_scoped_refptr(dest)));
+ if (rv >= 0) {
+ // Data is immediately available.
+ *bytes_read = rv;
+ remaining_bytes_ -= rv;
+ DCHECK_GE(remaining_bytes_, 0);
+ return true;
+ }
+
+ // Otherwise, a read error occured. We may just need to wait...
+ if (rv == net::ERR_IO_PENDING) {
+ io_pending_ = true;
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+bool URLRequestContentJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ return false;
+}
+
+bool URLRequestContentJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(request_);
+ if (!meta_info_.mime_type.empty()) {
+ *mime_type = meta_info_.mime_type;
+ return true;
+ }
+ return false;
+}
+
+void URLRequestContentJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header))
+ return;
+
+ // We only care about "Range" header here.
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests.
+ NotifyDone(net::URLRequestStatus(
+ net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ }
+ }
+}
+
+URLRequestContentJob::~URLRequestContentJob() {}
+
+void URLRequestContentJob::FetchMetaInfo(const base::FilePath& content_path,
+ ContentMetaInfo* meta_info) {
+ base::File::Info file_info;
+ meta_info->content_exists = base::GetFileInfo(content_path, &file_info);
+ if (meta_info->content_exists) {
+ meta_info->content_size = file_info.size;
+ meta_info->mime_type = base::GetContentUriMimeType(content_path);
+ }
+}
+
+void URLRequestContentJob::DidFetchMetaInfo(const ContentMetaInfo* meta_info) {
+ meta_info_ = *meta_info;
+
+ if (!meta_info_.content_exists) {
+ DidOpen(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ int flags = base::File::FLAG_OPEN |
+ base::File::FLAG_READ |
+ base::File::FLAG_ASYNC;
+ int rv = stream_->Open(content_path_, flags,
+ base::Bind(&URLRequestContentJob::DidOpen,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != net::ERR_IO_PENDING)
+ DidOpen(rv);
+}
+
+void URLRequestContentJob::DidOpen(int result) {
+ if (result != net::OK) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
+ return;
+ }
+
+ if (!byte_range_.ComputeBounds(meta_info_.content_size)) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) {
+ int rv = stream_->Seek(base::File::FROM_BEGIN,
+ byte_range_.first_byte_position(),
+ base::Bind(&URLRequestContentJob::DidSeek,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != net::ERR_IO_PENDING) {
+ // stream_->Seek() failed, so pass an intentionally erroneous value
+ // into DidSeek().
+ DidSeek(-1);
+ }
+ } else {
+ // We didn't need to call stream_->Seek() at all, so we pass to DidSeek()
+ // the value that would mean seek success. This way we skip the code
+ // handling seek failure.
+ DidSeek(byte_range_.first_byte_position());
+ }
+}
+
+void URLRequestContentJob::DidSeek(int64 result) {
+ if (result != byte_range_.first_byte_position()) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ set_expected_content_size(remaining_bytes_);
+ NotifyHeadersComplete();
+}
+
+void URLRequestContentJob::DidRead(
+ scoped_refptr<net::IOBuffer> buf, int result) {
+ if (result > 0) {
+ SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+ }
+
+ DCHECK(io_pending_);
+ io_pending_ = false;
+
+ if (result == 0) {
+ NotifyDone(net::URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
+ }
+
+ NotifyReadComplete(result);
+}
+
+} // namespace content
diff --git a/content/browser/android/url_request_content_job.h b/content/browser/android/url_request_content_job.h
new file mode 100644
index 0000000..5d3d933
--- /dev/null
+++ b/content/browser/android/url_request_content_job.h
@@ -0,0 +1,103 @@
+// Copyright (c) 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.
+
+#ifndef CONTENT_BROWSER_ANDROID_URL_REQUEST_CONTENT_JOB_H_
+#define CONTENT_BROWSER_ANDROID_URL_REQUEST_CONTENT_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace file_util {
+struct FileInfo;
+}
+
+namespace net {
+class FileStream;
+}
+
+namespace content {
+
+// A request job that handles reading content URIs
+class CONTENT_EXPORT URLRequestContentJob : public net::URLRequestJob {
+ public:
+ URLRequestContentJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& content_path,
+ const scoped_refptr<base::TaskRunner>& content_task_runner);
+
+ // net::URLRequestJob:
+ void Start() override;
+ void Kill() override;
+ bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override;
+ bool IsRedirectResponse(GURL* location, int* http_status_code) override;
+ bool GetMimeType(std::string* mime_type) const override;
+ void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override;
+
+ protected:
+ ~URLRequestContentJob() override;
+
+ private:
+ // Meta information about the content URI. It's used as a member in the
+ // URLRequestContentJob and also passed between threads because disk access is
+ // necessary to obtain it.
+ struct ContentMetaInfo {
+ ContentMetaInfo();
+ // Flag showing whether the content URI exists.
+ bool content_exists;
+ // Size of the content URI.
+ int64 content_size;
+ // Mime type associated with the content URI.
+ std::string mime_type;
+ };
+
+ // Fetches content URI info on a background thread.
+ static void FetchMetaInfo(const base::FilePath& content_path,
+ ContentMetaInfo* meta_info);
+
+ // Callback after fetching content URI info on a background thread.
+ void DidFetchMetaInfo(const ContentMetaInfo* meta_info);
+
+ // Callback after opening content URI on a background thread.
+ void DidOpen(int result);
+
+ // Callback after seeking to the beginning of |byte_range_| in the content URI
+ // on a background thread.
+ void DidSeek(int64 result);
+
+ // Callback after data is asynchronously read from the content URI into |buf|.
+ void DidRead(scoped_refptr<net::IOBuffer> buf, int result);
+
+ // The full path of the content URI.
+ base::FilePath content_path_;
+
+ scoped_ptr<net::FileStream> stream_;
+ ContentMetaInfo meta_info_;
+ const scoped_refptr<base::TaskRunner> content_task_runner_;
+
+ net::HttpByteRange byte_range_;
+ int64 remaining_bytes_;
+
+ bool io_pending_;
+
+ base::WeakPtrFactory<URLRequestContentJob> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContentJob);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_URL_REQUEST_CONTENT_JOB_H_
diff --git a/content/browser/android/url_request_content_job_unittest.cc b/content/browser/android/url_request_content_job_unittest.cc
new file mode 100644
index 0000000..e4b5f9e
--- /dev/null
+++ b/content/browser/android/url_request_content_job_unittest.cc
@@ -0,0 +1,198 @@
+// 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 <algorithm>
+
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_file_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 content {
+
+namespace {
+
+// A URLRequestJobFactory that will return URLRequestContentJobWithCallbacks
+// instances for content:// scheme URLs.
+class CallbacksJobFactory : public net::URLRequestJobFactory {
+ public:
+ class JobObserver {
+ public:
+ virtual void OnJobCreated(URLRequestContentJob* job) = 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_,
+ const_cast<base::MessageLoop*>(&message_loop_)->task_runner());
+ 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 == "content";
+ }
+
+ bool IsHandledURL(const GURL& url) const override {
+ return IsHandledProtocol(url.scheme());
+ }
+
+ bool IsSafeRedirectTarget(const GURL& location) const override {
+ return false;
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::FilePath path_;
+ JobObserver* observer_;
+};
+
+class JobObserverImpl : public CallbacksJobFactory::JobObserver {
+ public:
+ void OnJobCreated(URLRequestContentJob* job) override {
+ jobs_.push_back(job);
+ }
+
+ typedef std::vector<scoped_refptr<URLRequestContentJob> > 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;
+
+ 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);
+
+ 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 file_size;
+ EXPECT_TRUE(base::GetFileSize(path, &file_size));
+ EXPECT_LT(0, file_size);
+ CallbacksJobFactory factory(path, &observer_);
+ context_.set_job_factory(&factory);
+
+ scoped_ptr<net::URLRequest> request(context_.CreateRequest(
+ GURL(path.value()), net::DEFAULT_PRIORITY, &delegate_, NULL));
+ 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, (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(observer_.jobs().size(), 1u);
+ EXPECT_EQ(delegate_.bytes_received(), expected_length);
+}
+
+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