blob: c5dd462c8f8e548a6862ec04fcefa5e6d5aa76fe [file] [log] [blame]
// Copyright (c) 2012 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 "android_webview/browser/net/android_stream_reader_url_request_job.h"
#include <memory>
#include <utility>
#include "android_webview/browser/input_stream.h"
#include "android_webview/browser/net/aw_url_request_job_factory.h"
#include "android_webview/browser/net/input_stream_reader.h"
#include "base/format_macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/base/request_priority.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job_factory_impl.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 net::TestDelegate;
using net::TestJobInterceptor;
using net::TestNetworkDelegate;
using net::TestURLRequestContext;
using net::URLRequest;
using testing::DoAll;
using testing::Ge;
using testing::Gt;
using testing::InSequence;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::StrictMock;
using testing::Test;
using testing::WithArg;
using testing::WithArgs;
using testing::_;
namespace android_webview {
namespace {
// Some of the classes will DCHECK on a null InputStream (which is desirable).
// The workaround is to use this class. None of the methods need to be
// implemented as the mock InputStreamReader should never forward calls to the
// InputStream.
class NotImplInputStream : public InputStream {
public:
NotImplInputStream() {}
~NotImplInputStream() override {}
bool BytesAvailable(int* bytes_available) const override {
NOTIMPLEMENTED();
return false;
}
bool Skip(int64_t n, int64_t* bytes_skipped) override {
NOTIMPLEMENTED();
return false;
}
bool Read(net::IOBuffer* dest, int length, int* bytes_read) override {
NOTIMPLEMENTED();
return false;
}
};
// Required in order to create an instance of AndroidStreamReaderURLRequestJob.
class StreamReaderDelegate :
public AndroidStreamReaderURLRequestJob::Delegate {
public:
StreamReaderDelegate() {}
std::unique_ptr<InputStream> OpenInputStream(JNIEnv* env,
const GURL& url) override {
return base::MakeUnique<NotImplInputStream>();
}
void OnInputStreamOpenFailed(net::URLRequest* request,
bool* restart) override {
*restart = false;
}
bool GetMimeType(JNIEnv* env,
net::URLRequest* request,
android_webview::InputStream* stream,
std::string* mime_type) override {
return false;
}
bool GetCharset(JNIEnv* env,
net::URLRequest* request,
android_webview::InputStream* stream,
std::string* charset) override {
return false;
}
void AppendResponseHeaders(JNIEnv* env,
net::HttpResponseHeaders* headers) override {
// no-op
}
};
class NullStreamReaderDelegate : public StreamReaderDelegate {
public:
NullStreamReaderDelegate() {}
std::unique_ptr<InputStream> OpenInputStream(JNIEnv* env,
const GURL& url) override {
return nullptr;
}
};
class HeaderAlteringStreamReaderDelegate : public NullStreamReaderDelegate {
public:
HeaderAlteringStreamReaderDelegate() {}
void AppendResponseHeaders(JNIEnv* env,
net::HttpResponseHeaders* headers) override {
headers->ReplaceStatusLine(kStatusLine);
std::string headerLine(kCustomHeaderName);
headerLine.append(": ");
headerLine.append(kCustomHeaderValue);
headers->AddHeader(headerLine);
}
static const int kResponseCode;
static const char* kStatusLine;
static const char* kCustomHeaderName;
static const char* kCustomHeaderValue;
};
const int HeaderAlteringStreamReaderDelegate::kResponseCode = 401;
const char* HeaderAlteringStreamReaderDelegate::kStatusLine =
"HTTP/1.1 401 Gone";
const char* HeaderAlteringStreamReaderDelegate::kCustomHeaderName =
"X-Test-Header";
const char* HeaderAlteringStreamReaderDelegate::kCustomHeaderValue =
"TestHeaderValue";
class MockInputStreamReader : public InputStreamReader {
public:
MockInputStreamReader() : InputStreamReader(new NotImplInputStream()) {}
~MockInputStreamReader() {}
MOCK_METHOD1(Seek, int(const net::HttpByteRange& byte_range));
MOCK_METHOD2(ReadRawData, int(net::IOBuffer* buffer, int buffer_size));
};
class TestStreamReaderJob : public AndroidStreamReaderURLRequestJob {
public:
TestStreamReaderJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate,
std::unique_ptr<Delegate> delegate,
std::unique_ptr<InputStreamReader> stream_reader)
: AndroidStreamReaderURLRequestJob(request,
network_delegate,
std::move(delegate)),
stream_reader_(std::move(stream_reader)) {
task_runner_ = base::ThreadTaskRunnerHandle::Get();
}
~TestStreamReaderJob() override {}
std::unique_ptr<InputStreamReader> CreateStreamReader(
InputStream* stream) override {
return std::move(stream_reader_);
}
protected:
base::TaskRunner* GetWorkerThreadRunner() override {
return task_runner_.get();
}
std::unique_ptr<InputStreamReader> stream_reader_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
};
} // namespace
class AndroidStreamReaderURLRequestJobTest : public Test {
public:
AndroidStreamReaderURLRequestJobTest() {}
protected:
void SetUp() override {
context_.set_job_factory(&factory_);
context_.set_network_delegate(&network_delegate_);
req_ = context_.CreateRequest(GURL("content://foo"),
net::DEFAULT_PRIORITY,
&url_request_delegate_);
req_->set_method("GET");
}
void SetRange(net::URLRequest* req, int first_byte, int last_byte) {
net::HttpRequestHeaders headers;
headers.SetHeader(net::HttpRequestHeaders::kRange,
net::HttpByteRange::Bounded(
first_byte, last_byte).GetHeaderValue());
req->SetExtraRequestHeaders(headers);
}
void SetUpTestJob(std::unique_ptr<InputStreamReader> stream_reader) {
SetUpTestJob(std::move(stream_reader),
base::MakeUnique<StreamReaderDelegate>());
}
void SetUpTestJob(std::unique_ptr<InputStreamReader> stream_reader,
std::unique_ptr<AndroidStreamReaderURLRequestJob::Delegate>
stream_reader_delegate) {
std::unique_ptr<TestStreamReaderJob> test_stream_reader_job(
new TestStreamReaderJob(req_.get(), &network_delegate_,
std::move(stream_reader_delegate),
std::move(stream_reader)));
// The Interceptor is owned by the |factory_|.
std::unique_ptr<TestJobInterceptor> protocol_handler(
new TestJobInterceptor);
protocol_handler->set_main_intercept_job(std::move(test_stream_reader_job));
bool set_protocol =
factory_.SetProtocolHandler("content", std::move(protocol_handler));
DCHECK(set_protocol);
}
base::MessageLoopForIO loop_;
TestURLRequestContext context_;
android_webview::AwURLRequestJobFactory factory_;
TestDelegate url_request_delegate_;
TestNetworkDelegate network_delegate_;
std::unique_ptr<URLRequest> req_;
};
TEST_F(AndroidStreamReaderURLRequestJobTest, ReadEmptyStream) {
std::unique_ptr<StrictMock<MockInputStreamReader>> stream_reader(
new StrictMock<MockInputStreamReader>());
{
InSequence s;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(0));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Gt(0)))
.WillOnce(Return(0));
}
SetUpTestJob(std::move(stream_reader));
req_->Start();
// The TestDelegate will quit the message loop on request completion.
base::RunLoop().Run();
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(1, network_delegate_.completed_requests());
EXPECT_EQ(0, network_delegate_.error_count());
EXPECT_EQ(200, req_->GetResponseCode());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, ReadWithNullStream) {
SetUpTestJob(nullptr, base::MakeUnique<NullStreamReaderDelegate>());
req_->Start();
// The TestDelegate will quit the message loop on request completion.
base::RunLoop().Run();
// The request_failed() method is named confusingly but all it checks is
// whether the request got as far as calling NotifyHeadersComplete.
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(1, network_delegate_.completed_requests());
// A null input stream shouldn't result in an error. See crbug.com/180950.
EXPECT_EQ(0, network_delegate_.error_count());
EXPECT_EQ(404, req_->GetResponseCode());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, ModifyHeadersAndStatus) {
SetUpTestJob(nullptr, base::MakeUnique<HeaderAlteringStreamReaderDelegate>());
req_->Start();
// The TestDelegate will quit the message loop on request completion.
base::RunLoop().Run();
// The request_failed() method is named confusingly but all it checks is
// whether the request got as far as calling NotifyHeadersComplete.
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(1, network_delegate_.completed_requests());
// A null input stream shouldn't result in an error. See crbug.com/180950.
EXPECT_EQ(0, network_delegate_.error_count());
EXPECT_EQ(HeaderAlteringStreamReaderDelegate::kResponseCode,
req_->GetResponseCode());
EXPECT_EQ(HeaderAlteringStreamReaderDelegate::kStatusLine,
req_->response_headers()->GetStatusLine());
EXPECT_TRUE(req_->response_headers()->HasHeader(
HeaderAlteringStreamReaderDelegate::kCustomHeaderName));
std::string header_value;
EXPECT_TRUE(req_->response_headers()->EnumerateHeader(
NULL, HeaderAlteringStreamReaderDelegate::kCustomHeaderName,
&header_value));
EXPECT_EQ(HeaderAlteringStreamReaderDelegate::kCustomHeaderValue,
header_value);
}
TEST_F(AndroidStreamReaderURLRequestJobTest, ReadPartOfStream) {
const int bytes_available = 128;
const int offset = 32;
const int bytes_to_read = bytes_available - offset;
std::unique_ptr<StrictMock<MockInputStreamReader>> stream_reader(
new StrictMock<MockInputStreamReader>());
{
InSequence s;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(bytes_available));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(bytes_to_read/2));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(bytes_to_read/2));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(0));
}
SetUpTestJob(std::move(stream_reader));
SetRange(req_.get(), offset, bytes_available);
req_->Start();
base::RunLoop().Run();
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received());
EXPECT_EQ(1, network_delegate_.completed_requests());
EXPECT_EQ(0, network_delegate_.error_count());
}
TEST_F(AndroidStreamReaderURLRequestJobTest,
ReadStreamWithMoreAvailableThanActual) {
const int bytes_available_reported = 190;
const int bytes_available = 128;
const int offset = 0;
const int bytes_to_read = bytes_available - offset;
std::unique_ptr<StrictMock<MockInputStreamReader>> stream_reader(
new StrictMock<MockInputStreamReader>());
{
InSequence s;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(bytes_available_reported));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(bytes_available));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(0));
}
SetUpTestJob(std::move(stream_reader));
SetRange(req_.get(), offset, bytes_available_reported);
req_->Start();
base::RunLoop().Run();
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received());
EXPECT_EQ(1, network_delegate_.completed_requests());
EXPECT_EQ(0, network_delegate_.error_count());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWaySeek) {
const int offset = 20;
const int bytes_available = 128;
base::RunLoop loop;
std::unique_ptr<StrictMock<MockInputStreamReader>> stream_reader(
new StrictMock<MockInputStreamReader>());
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(DoAll(InvokeWithoutArgs(&loop, &base::RunLoop::Quit),
Return(bytes_available)));
ON_CALL(*stream_reader, ReadRawData(_, _))
.WillByDefault(Return(0));
SetUpTestJob(std::move(stream_reader));
SetRange(req_.get(), offset, bytes_available);
req_->Start();
loop.Run();
EXPECT_EQ(0, network_delegate_.completed_requests());
req_->Cancel();
EXPECT_EQ(1, network_delegate_.completed_requests());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWayRead) {
const int offset = 20;
const int bytes_available = 128;
base::RunLoop loop;
std::unique_ptr<StrictMock<MockInputStreamReader>> stream_reader(
new StrictMock<MockInputStreamReader>());
net::CompletionCallback read_completion_callback;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(bytes_available));
EXPECT_CALL(*stream_reader, ReadRawData(_, _))
.WillOnce(DoAll(InvokeWithoutArgs(&loop, &base::RunLoop::Quit),
Return(bytes_available)));
SetUpTestJob(std::move(stream_reader));
SetRange(req_.get(), offset, bytes_available);
req_->Start();
loop.Run();
EXPECT_EQ(0, network_delegate_.completed_requests());
req_->Cancel();
EXPECT_EQ(1, network_delegate_.completed_requests());
}
} // namespace android_webview