blob: 70d1cd843ee7c591499e6caa993cda5d5b511f1c [file] [log] [blame]
// Copyright 2017 PDFium 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 "core/fpdfapi/parser/cpdf_read_validator.h"
#include <limits>
#include <utility>
#include <vector>
#include "core/fxcrt/cfx_readonlymemorystream.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/invalid_seekable_read_stream.h"
namespace {
constexpr uint32_t kTestDataSize = 64 * 1024 - 467;
std::pair<FX_FILESIZE, FX_FILESIZE> MakeRange(uint32_t start, uint32_t end) {
return std::pair<FX_FILESIZE, FX_FILESIZE>(start, end);
}
class MockFileAvail final : public CPDF_DataAvail::FileAvail {
public:
MockFileAvail() : available_range_(0, 0) {}
~MockFileAvail() override = default;
bool IsDataAvail(FX_FILESIZE offset, size_t size) override {
return available_range_.first <= offset &&
available_range_.second >= static_cast<FX_FILESIZE>(offset + size);
}
void SetAvailableRange(const std::pair<FX_FILESIZE, FX_FILESIZE>& range) {
available_range_ = range;
}
void SetAvailableRange(uint32_t start, uint32_t end) {
SetAvailableRange(MakeRange(start, end));
}
private:
std::pair<FX_FILESIZE, FX_FILESIZE> available_range_;
};
class MockDownloadHints final : public CPDF_DataAvail::DownloadHints {
public:
MockDownloadHints() : last_requested_range_(0, 0) {}
~MockDownloadHints() override = default;
void AddSegment(FX_FILESIZE offset, size_t size) override {
last_requested_range_.first = offset;
last_requested_range_.second = offset + size;
}
const std::pair<FX_FILESIZE, FX_FILESIZE>& GetLastRequstedRange() const {
return last_requested_range_;
}
void Reset() { last_requested_range_ = MakeRange(0, 0); }
private:
std::pair<FX_FILESIZE, FX_FILESIZE> last_requested_range_;
};
} // namespace
TEST(CPDF_ReadValidatorTest, UnavailableData) {
std::vector<uint8_t, FxAllocAllocator<uint8_t>> test_data(kTestDataSize);
auto file = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(test_data);
MockFileAvail file_avail;
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, &file_avail);
std::vector<uint8_t, FxAllocAllocator<uint8_t>> read_buffer(100);
EXPECT_FALSE(validator->ReadBlockAtOffset(read_buffer.data(), 5000,
read_buffer.size()));
EXPECT_FALSE(validator->read_error());
EXPECT_TRUE(validator->has_unavailable_data());
validator->ResetErrors();
file_avail.SetAvailableRange(5000, 5000 + read_buffer.size());
EXPECT_TRUE(validator->ReadBlockAtOffset(read_buffer.data(), 5000,
read_buffer.size()));
EXPECT_FALSE(validator->read_error());
EXPECT_FALSE(validator->has_unavailable_data());
}
TEST(CPDF_ReadValidatorTest, UnavailableDataWithHints) {
std::vector<uint8_t, FxAllocAllocator<uint8_t>> test_data(kTestDataSize);
auto file = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(test_data);
MockFileAvail file_avail;
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, &file_avail);
MockDownloadHints hints;
validator->SetDownloadHints(&hints);
std::vector<uint8_t, FxAllocAllocator<uint8_t>> read_buffer(100);
EXPECT_FALSE(validator->ReadBlockAtOffset(read_buffer.data(), 5000,
read_buffer.size()));
EXPECT_FALSE(validator->read_error());
EXPECT_TRUE(validator->has_unavailable_data());
// Requested range should be enlarged and aligned.
EXPECT_EQ(MakeRange(4608, 5120), hints.GetLastRequstedRange());
file_avail.SetAvailableRange(hints.GetLastRequstedRange());
hints.Reset();
validator->ResetErrors();
EXPECT_TRUE(validator->ReadBlockAtOffset(read_buffer.data(), 5000,
read_buffer.size()));
// No new request on already available data.
EXPECT_EQ(MakeRange(0, 0), hints.GetLastRequstedRange());
EXPECT_FALSE(validator->read_error());
EXPECT_FALSE(validator->has_unavailable_data());
validator->ResetErrors();
// Try read unavailable data at file end.
EXPECT_FALSE(validator->ReadBlockAtOffset(
read_buffer.data(), validator->GetSize() - read_buffer.size(),
read_buffer.size()));
// Should not enlarge request at file end.
EXPECT_EQ(validator->GetSize(), hints.GetLastRequstedRange().second);
EXPECT_FALSE(validator->read_error());
EXPECT_TRUE(validator->has_unavailable_data());
validator->SetDownloadHints(nullptr);
}
TEST(CPDF_ReadValidatorTest, ReadError) {
auto file = pdfium::MakeRetain<InvalidSeekableReadStream>(kTestDataSize);
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, nullptr);
static const uint32_t kBufferSize = 3 * 1000;
std::vector<uint8_t, FxAllocAllocator<uint8_t>> buffer(kBufferSize);
EXPECT_FALSE(validator->ReadBlockAtOffset(buffer.data(), 5000, 100));
EXPECT_TRUE(validator->read_error());
EXPECT_TRUE(validator->has_unavailable_data());
}
TEST(CPDF_ReadValidatorTest, IntOverflow) {
std::vector<uint8_t, FxAllocAllocator<uint8_t>> test_data(kTestDataSize);
auto file = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(test_data);
MockFileAvail file_avail;
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, &file_avail);
std::vector<uint8_t, FxAllocAllocator<uint8_t>> read_buffer(100);
// If we have int overflow, this is equal reading after file end. This is not
// read_error, and in this case we have not unavailable data. It is just error
// of input params.
EXPECT_FALSE(validator->ReadBlockAtOffset(
read_buffer.data(), std::numeric_limits<FX_FILESIZE>::max() - 1,
read_buffer.size()));
EXPECT_FALSE(validator->read_error());
EXPECT_FALSE(validator->has_unavailable_data());
}
TEST(CPDF_ReadValidatorTest, Session) {
std::vector<uint8_t, FxAllocAllocator<uint8_t>> test_data(kTestDataSize);
auto file = pdfium::MakeRetain<InvalidSeekableReadStream>(kTestDataSize);
MockFileAvail file_avail;
MockDownloadHints hints;
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, &file_avail);
validator->SetDownloadHints(&hints);
CPDF_ReadValidator::ScopedSession read_session(validator);
ASSERT_FALSE(validator->has_read_problems());
// Data is unavailable
validator->ReadBlockAtOffset(test_data.data(), 0, 100);
EXPECT_TRUE(validator->has_read_problems());
EXPECT_TRUE(validator->has_unavailable_data());
EXPECT_FALSE(validator->read_error());
{
CPDF_ReadValidator::ScopedSession read_subsession(validator);
// The read problems should be hidden.
EXPECT_FALSE(validator->has_read_problems());
file_avail.SetAvailableRange(0, 100);
// Read fail.
validator->ReadBlockAtOffset(test_data.data(), 0, 100);
EXPECT_TRUE(validator->has_read_problems());
EXPECT_TRUE(validator->has_unavailable_data());
EXPECT_TRUE(validator->read_error());
}
// The problems should be merged
EXPECT_TRUE(validator->has_read_problems());
EXPECT_TRUE(validator->has_unavailable_data());
EXPECT_TRUE(validator->read_error());
}
TEST(CPDF_ReadValidatorTest, SessionReset) {
std::vector<uint8_t, FxAllocAllocator<uint8_t>> test_data(kTestDataSize);
auto file = pdfium::MakeRetain<InvalidSeekableReadStream>(kTestDataSize);
MockFileAvail file_avail;
MockDownloadHints hints;
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, &file_avail);
validator->SetDownloadHints(&hints);
CPDF_ReadValidator::ScopedSession read_session(validator);
ASSERT_FALSE(validator->has_read_problems());
// Data is unavailable
validator->ReadBlockAtOffset(test_data.data(), 0, 100);
EXPECT_TRUE(validator->has_read_problems());
EXPECT_TRUE(validator->has_unavailable_data());
EXPECT_FALSE(validator->read_error());
{
CPDF_ReadValidator::ScopedSession read_subsession(validator);
// The read problems should be hidden.
EXPECT_FALSE(validator->has_read_problems());
file_avail.SetAvailableRange(0, 100);
// Read fail.
validator->ReadBlockAtOffset(test_data.data(), 0, 100);
EXPECT_TRUE(validator->has_read_problems());
EXPECT_TRUE(validator->has_unavailable_data());
EXPECT_TRUE(validator->read_error());
// Reset session.
validator->ResetErrors();
EXPECT_FALSE(validator->has_read_problems());
}
// The problems should be restored.
EXPECT_TRUE(validator->has_read_problems());
EXPECT_TRUE(validator->has_unavailable_data());
EXPECT_FALSE(validator->read_error());
}
TEST(CPDF_ReadValidatorTest, CheckDataRangeAndRequestIfUnavailable) {
std::vector<uint8_t, FxAllocAllocator<uint8_t>> test_data(kTestDataSize);
auto file = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(test_data);
MockFileAvail file_avail;
auto validator = pdfium::MakeRetain<CPDF_ReadValidator>(file, &file_avail);
MockDownloadHints hints;
validator->SetDownloadHints(&hints);
EXPECT_FALSE(validator->CheckDataRangeAndRequestIfUnavailable(5000, 100));
EXPECT_FALSE(validator->read_error());
EXPECT_TRUE(validator->has_unavailable_data());
// Requested range should be enlarged and aligned.
EXPECT_EQ(MakeRange(4608, 5632), hints.GetLastRequstedRange());
file_avail.SetAvailableRange(hints.GetLastRequstedRange());
hints.Reset();
validator->ResetErrors();
EXPECT_TRUE(validator->CheckDataRangeAndRequestIfUnavailable(5000, 100));
// No new request on already available data.
EXPECT_EQ(MakeRange(0, 0), hints.GetLastRequstedRange());
EXPECT_FALSE(validator->read_error());
EXPECT_FALSE(validator->has_unavailable_data());
std::vector<uint8_t, FxAllocAllocator<uint8_t>> read_buffer(100);
EXPECT_TRUE(validator->ReadBlockAtOffset(read_buffer.data(), 5000,
read_buffer.size()));
// No new request on already available data.
EXPECT_EQ(MakeRange(0, 0), hints.GetLastRequstedRange());
EXPECT_FALSE(validator->read_error());
EXPECT_FALSE(validator->has_unavailable_data());
validator->ResetErrors();
// Try request unavailable data at file end.
EXPECT_FALSE(validator->CheckDataRangeAndRequestIfUnavailable(
validator->GetSize() - 100, 100));
// Should not enlarge request at file end.
EXPECT_EQ(validator->GetSize(), hints.GetLastRequstedRange().second);
EXPECT_FALSE(validator->read_error());
EXPECT_TRUE(validator->has_unavailable_data());
validator->ResetErrors();
// Offset > file size should yield |true| and not cause a fetch.
EXPECT_TRUE(
validator->CheckDataRangeAndRequestIfUnavailable(kTestDataSize + 1, 1));
// No new request on already available data.
EXPECT_FALSE(validator->read_error());
EXPECT_FALSE(validator->has_unavailable_data());
validator->SetDownloadHints(nullptr);
}