| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/devtools/request_body_collector.h" |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "services/network/public/cpp/resource_request_body.h" |
| #include "services/network/test/test_data_pipe_getter.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| class RequestBodyCollectorTest : public ::testing::Test { |
| protected: |
| using Result = std::vector<RequestBodyCollector::BodyEntry>; |
| |
| RequestBodyCollectorTest() = default; |
| ~RequestBodyCollectorTest() override = default; |
| |
| base::OnceCallback<void(Result result)> MakeCallback() { |
| return base::BindOnce(&RequestBodyCollectorTest::SetResult, |
| base::Unretained(this)); |
| } |
| |
| static std::unique_ptr<network::TestDataPipeGetter> AddPipeGetterWithData( |
| network::ResourceRequestBody& body, |
| std::string data) { |
| mojo::PendingRemote<network::mojom::DataPipeGetter> pipe_getter_remote; |
| auto pipe_getter = std::make_unique<network::TestDataPipeGetter>( |
| std::move(data), pipe_getter_remote.InitWithNewPipeAndPassReceiver()); |
| body.AppendDataPipe(std::move(pipe_getter_remote)); |
| return pipe_getter; |
| } |
| |
| void WaitCompletion() { |
| base::RunLoop run_loop; |
| quit_loop_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| Result result_; |
| std::vector<std::string_view> text_result_; |
| |
| private: |
| void SetResult(Result result) { |
| result_ = std::move(result); |
| std::transform( |
| result_.begin(), result_.end(), std::back_inserter(text_result_), |
| [](const RequestBodyCollector::BodyEntry& entry) { |
| return entry.has_value() |
| ? std::string_view( |
| reinterpret_cast<const char*>(entry->data()), |
| entry->size()) |
| : entry.error(); |
| }); |
| if (quit_loop_callback_) { |
| std::move(quit_loop_callback_).Run(); |
| } |
| } |
| |
| BrowserTaskEnvironment task_environment_; |
| base::OnceClosure quit_loop_callback_; |
| }; |
| |
| static constexpr char kBytes[] = "don't panic!"; |
| |
| TEST_F(RequestBodyCollectorTest, Empty) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| EXPECT_THAT(collector, testing::IsNull()); |
| EXPECT_THAT(result_, testing::IsEmpty()); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, OnlyBytes) { |
| auto body = network::ResourceRequestBody::CreateFromCopyOfBytes( |
| base::byte_span_from_cstring(kBytes)); |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| EXPECT_THAT(collector, testing::IsNull()); |
| EXPECT_THAT(text_result_, testing::ElementsAre(kBytes)); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, UnsupportedType) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| body->AppendFileRange(base::FilePath(FILE_PATH_LITERAL("/etc/passwd")), 0, 42, |
| base::Time::Now()); |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| EXPECT_THAT(collector, testing::IsNull()); |
| EXPECT_THAT(text_result_, testing::ElementsAre("Unsupported entry")); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, Pipe) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| auto pipe_getter = AddPipeGetterWithData(*body, kBytes); |
| |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| |
| EXPECT_THAT(collector, testing::NotNull()); |
| WaitCompletion(); |
| EXPECT_THAT(text_result_, testing::ElementsAre(kBytes)); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, PipeInsufficientData) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| auto pipe_getter = AddPipeGetterWithData(*body, kBytes); |
| |
| pipe_getter->set_pipe_closed_early(true); |
| |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| |
| EXPECT_THAT(collector, testing::NotNull()); |
| WaitCompletion(); |
| EXPECT_THAT(text_result_, testing::ElementsAre("Unexpected end of data")); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, PipeError) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| auto pipe_getter = AddPipeGetterWithData(*body, kBytes); |
| |
| pipe_getter->set_start_error(13); |
| |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| |
| EXPECT_THAT(collector, testing::NotNull()); |
| WaitCompletion(); |
| EXPECT_THAT(text_result_, testing::ElementsAre("Error reading blob")); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, PipeDisconnect) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| |
| mojo::PendingRemote<network::mojom::DataPipeGetter> pipe_getter_remote; |
| mojo::PendingReceiver<network::mojom::DataPipeGetter> pipe_getter_receiver( |
| pipe_getter_remote.InitWithNewPipeAndPassReceiver()); |
| |
| body->AppendDataPipe(std::move(pipe_getter_remote)); |
| |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| EXPECT_THAT(collector, testing::NotNull()); |
| |
| pipe_getter_receiver.reset(); |
| |
| WaitCompletion(); |
| EXPECT_THAT(text_result_, testing::ElementsAre("Error reading blob")); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, EarlyTermination) { |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| |
| mojo::PendingRemote<network::mojom::DataPipeGetter> pipe_getter_remote; |
| mojo::PendingReceiver<network::mojom::DataPipeGetter> pipe_getter_receiver( |
| pipe_getter_remote.InitWithNewPipeAndPassReceiver()); |
| |
| body->AppendDataPipe(std::move(pipe_getter_remote)); |
| |
| auto collector = RequestBodyCollector::Collect( |
| *body, base::BindOnce([](Result) { FAIL() << "Should not be called"; })); |
| collector.reset(); |
| |
| EXPECT_THAT(text_result_, testing::IsEmpty()); |
| } |
| |
| TEST_F(RequestBodyCollectorTest, AllTogertherNow) { |
| static constexpr char kDelimiter[] = "-------------------"; |
| |
| auto body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| body->AppendCopyOfBytes(base::byte_span_from_cstring(kBytes)); |
| |
| auto pipe1 = AddPipeGetterWithData(*body, "Pipe bytes 1"); |
| body->AppendCopyOfBytes(base::byte_span_from_cstring(kDelimiter)); |
| auto pipe2 = AddPipeGetterWithData(*body, "Pipe bytes 2"); |
| body->AppendCopyOfBytes(base::byte_span_from_cstring(kDelimiter)); |
| auto pipe3 = AddPipeGetterWithData(*body, ""); |
| pipe3->set_pipe_closed_early(true); |
| body->AppendCopyOfBytes(base::byte_span_from_cstring(kDelimiter)); |
| auto pipe4 = AddPipeGetterWithData(*body, ""); |
| body->AppendCopyOfBytes(base::byte_span_from_cstring(kDelimiter)); |
| |
| auto collector = RequestBodyCollector::Collect(*body, MakeCallback()); |
| EXPECT_THAT(collector, testing::NotNull()); |
| pipe4.reset(); |
| |
| WaitCompletion(); |
| EXPECT_THAT( |
| text_result_, |
| testing::ElementsAre(kBytes, "Pipe bytes 1", kDelimiter, "Pipe bytes 2", |
| kDelimiter, "Unexpected end of data", kDelimiter, |
| "Error reading blob", kDelimiter)); |
| } |
| |
| } // namespace |
| } // namespace content |