blob: efb23d6bd0de361e348b3e2d902bd9126584071a [file] [log] [blame]
// Copyright 2021 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 "components/pdf/browser/plugin_response_writer.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/pdf/browser/mock_url_loader_client.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace pdf {
namespace {
using ::testing::HasSubstr;
using ::testing::NiceMock;
class BodyDrainer {
public:
explicit BodyDrainer(mojo::ScopedDataPipeConsumerHandle body)
: drainer_(&drainer_client_, std::move(body)) {}
const std::string& content() const { return drainer_client_.content; }
void WaitComplete() {
if (drainer_client_.is_complete)
return;
base::RunLoop run_loop;
ASSERT_FALSE(drainer_client_.quit_closure);
drainer_client_.quit_closure = run_loop.QuitClosure();
run_loop.Run();
EXPECT_TRUE(drainer_client_.is_complete);
}
private:
struct DrainerClient : public mojo::DataPipeDrainer::Client {
void OnDataAvailable(const void* data, size_t num_bytes) override {
content.append(reinterpret_cast<const char*>(data), num_bytes);
}
void OnDataComplete() override {
is_complete = true;
if (quit_closure)
std::move(quit_closure).Run();
}
std::string content;
bool is_complete = false;
base::OnceClosure quit_closure;
};
DrainerClient drainer_client_;
mojo::DataPipeDrainer drainer_;
};
class PluginResponseWriterTest : public testing::Test {
protected:
PluginResponseWriterTest() {
ON_CALL(mock_client_, OnStartLoadingResponseBody)
.WillByDefault([this](mojo::ScopedDataPipeConsumerHandle body) {
body_drainer_ = std::make_unique<BodyDrainer>(std::move(body));
});
}
std::unique_ptr<PluginResponseWriter> NewPluginResponseWriter(
const GURL& source_url,
const GURL& original_url) {
return std::make_unique<PluginResponseWriter>(
source_url, original_url, client_receiver_.BindNewPipeAndPassRemote());
}
base::test::TaskEnvironment task_environment_;
NiceMock<MockURLLoaderClient> mock_client_;
mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{
&mock_client_};
std::unique_ptr<BodyDrainer> body_drainer_;
};
} // namespace
TEST_F(PluginResponseWriterTest, Start) {
auto response_writer =
NewPluginResponseWriter(GURL("chrome-extension://id/stream-url"),
GURL("https://example.test/fake.pdf"));
base::RunLoop run_loop;
{
// Note that `URLLoaderClient` operations are received on a separate
// sequence, and only are ordered with respect to each other.
testing::InSequence in_sequence;
EXPECT_CALL(mock_client_, OnReceiveResponse)
.WillOnce([](network::mojom::URLResponseHeadPtr head) {
EXPECT_EQ("text/html", head->mime_type);
});
EXPECT_CALL(mock_client_, OnStartLoadingResponseBody);
EXPECT_CALL(mock_client_, OnComplete)
.WillOnce(
[&run_loop](const network::URLLoaderCompletionStatus& status) {
EXPECT_EQ(net::OK, status.error_code);
run_loop.Quit();
});
}
// Note that `done_callback` is not ordered with respect to the
// `URLLoaderClient` operations.
base::MockCallback<base::OnceClosure> done_callback;
EXPECT_CALL(done_callback, Run);
response_writer->Start(done_callback.Get());
run_loop.Run();
// Waiting for `URLLoaderClient::OnComplete()` ensures `body_drainer_` is set,
// but the data pipe may still have unread data.
ASSERT_TRUE(body_drainer_);
body_drainer_->WaitComplete();
EXPECT_THAT(body_drainer_->content(),
HasSubstr("src=\"chrome-extension://id/stream-url\""));
EXPECT_THAT(body_drainer_->content(),
HasSubstr("original-url=\"https://example.test/fake.pdf\""));
EXPECT_THAT(body_drainer_->content(), HasSubstr("'chrome-extension://id/'"));
}
TEST_F(PluginResponseWriterTest, StartWithUnescapedUrls) {
auto response_writer =
NewPluginResponseWriter(GURL("chrome-extension://id/stream-url\""),
GURL("https://example.test/\"fake.pdf"));
base::RunLoop run_loop;
EXPECT_CALL(mock_client_, OnComplete).WillOnce([&run_loop]() {
run_loop.Quit();
});
response_writer->Start(base::DoNothing());
run_loop.Run();
// Waiting for `URLLoaderClient::OnComplete()` ensures `body_drainer_` is set,
// but the data pipe may still have unread data.
ASSERT_TRUE(body_drainer_);
body_drainer_->WaitComplete();
EXPECT_THAT(body_drainer_->content(),
HasSubstr("src=\"chrome-extension://id/stream-url%22\""));
EXPECT_THAT(body_drainer_->content(),
HasSubstr("original-url=\"https://example.test/%22fake.pdf\""));
EXPECT_THAT(body_drainer_->content(), HasSubstr("'chrome-extension://id/'"));
}
TEST_F(PluginResponseWriterTest, StartForPrintPreview) {
auto response_writer =
NewPluginResponseWriter(GURL("chrome://print/1/0/print.pdf"),
GURL("chrome://print/1/0/print.pdf"));
base::RunLoop run_loop;
EXPECT_CALL(mock_client_, OnComplete).WillOnce([&run_loop]() {
run_loop.Quit();
});
response_writer->Start(base::DoNothing());
run_loop.Run();
// Waiting for `URLLoaderClient::OnComplete()` ensures `body_drainer_` is set,
// but the data pipe may still have unread data.
ASSERT_TRUE(body_drainer_);
body_drainer_->WaitComplete();
EXPECT_THAT(body_drainer_->content(),
HasSubstr("src=\"chrome://print/1/0/print.pdf\""));
EXPECT_THAT(body_drainer_->content(),
HasSubstr("original-url=\"chrome://print/1/0/print.pdf\""));
EXPECT_THAT(body_drainer_->content(), HasSubstr("'chrome://print/'"));
}
} // namespace pdf