Home | History | Annotate | Download | only in fpdfsdk
      1 // Copyright 2015 PDFium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <algorithm>
      6 #include <memory>
      7 #include <set>
      8 #include <string>
      9 #include <utility>
     10 #include <vector>
     11 
     12 #include "public/fpdfview.h"
     13 #include "testing/embedder_test.h"
     14 #include "testing/gtest/include/gtest/gtest.h"
     15 #include "testing/range_set.h"
     16 #include "testing/test_support.h"
     17 #include "testing/utils/path_service.h"
     18 
     19 namespace {
     20 
     21 class MockDownloadHints : public FX_DOWNLOADHINTS {
     22  public:
     23   static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {
     24   }
     25 
     26   MockDownloadHints() {
     27     FX_DOWNLOADHINTS::version = 1;
     28     FX_DOWNLOADHINTS::AddSegment = SAddSegment;
     29   }
     30 
     31   ~MockDownloadHints() {}
     32 };
     33 
     34 class TestAsyncLoader : public FX_DOWNLOADHINTS, FX_FILEAVAIL {
     35  public:
     36   explicit TestAsyncLoader(const std::string& file_name) {
     37     std::string file_path;
     38     if (!PathService::GetTestFilePath(file_name, &file_path))
     39       return;
     40     file_contents_ = GetFileContents(file_path.c_str(), &file_length_);
     41     if (!file_contents_)
     42       return;
     43 
     44     file_access_.m_FileLen = static_cast<unsigned long>(file_length_);
     45     file_access_.m_GetBlock = SGetBlock;
     46     file_access_.m_Param = this;
     47 
     48     FX_DOWNLOADHINTS::version = 1;
     49     FX_DOWNLOADHINTS::AddSegment = SAddSegment;
     50 
     51     FX_FILEAVAIL::version = 1;
     52     FX_FILEAVAIL::IsDataAvail = SIsDataAvail;
     53   }
     54 
     55   bool IsOpened() const { return !!file_contents_; }
     56 
     57   FPDF_FILEACCESS* file_access() { return &file_access_; }
     58   FX_DOWNLOADHINTS* hints() { return this; }
     59   FX_FILEAVAIL* file_avail() { return this; }
     60 
     61   const std::vector<std::pair<size_t, size_t>>& requested_segments() const {
     62     return requested_segments_;
     63   }
     64 
     65   size_t max_requested_bound() const { return max_requested_bound_; }
     66 
     67   void ClearRequestedSegments() {
     68     requested_segments_.clear();
     69     max_requested_bound_ = 0;
     70   }
     71 
     72   bool is_new_data_available() const { return is_new_data_available_; }
     73   void set_is_new_data_available(bool is_new_data_available) {
     74     is_new_data_available_ = is_new_data_available;
     75   }
     76 
     77   size_t max_already_available_bound() const {
     78     return available_ranges_.IsEmpty()
     79                ? 0
     80                : available_ranges_.ranges().rbegin()->second;
     81   }
     82 
     83   void FlushRequestedData() {
     84     for (const auto& it : requested_segments_) {
     85       SetDataAvailable(it.first, it.second);
     86     }
     87     ClearRequestedSegments();
     88   }
     89 
     90  private:
     91   void SetDataAvailable(size_t start, size_t size) {
     92     available_ranges_.Union(RangeSet::Range(start, start + size));
     93   }
     94 
     95   bool CheckDataAlreadyAvailable(size_t start, size_t size) const {
     96     return available_ranges_.Contains(RangeSet::Range(start, start + size));
     97   }
     98 
     99   int GetBlockImpl(unsigned long pos, unsigned char* pBuf, unsigned long size) {
    100     if (!IsDataAvailImpl(pos, size))
    101       return 0;
    102     const unsigned long end =
    103         std::min(static_cast<unsigned long>(file_length_), pos + size);
    104     if (end <= pos)
    105       return 0;
    106     memcpy(pBuf, file_contents_.get() + pos, end - pos);
    107     SetDataAvailable(pos, end - pos);
    108     return static_cast<int>(end - pos);
    109   }
    110 
    111   void AddSegmentImpl(size_t offset, size_t size) {
    112     requested_segments_.push_back(std::make_pair(offset, size));
    113     max_requested_bound_ = std::max(max_requested_bound_, offset + size);
    114   }
    115 
    116   bool IsDataAvailImpl(size_t offset, size_t size) {
    117     if (offset + size > file_length_)
    118       return false;
    119     if (is_new_data_available_) {
    120       SetDataAvailable(offset, size);
    121       return true;
    122     }
    123     return CheckDataAlreadyAvailable(offset, size);
    124   }
    125 
    126   static int SGetBlock(void* param,
    127                        unsigned long pos,
    128                        unsigned char* pBuf,
    129                        unsigned long size) {
    130     return static_cast<TestAsyncLoader*>(param)->GetBlockImpl(pos, pBuf, size);
    131   }
    132 
    133   static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {
    134     return static_cast<TestAsyncLoader*>(pThis)->AddSegmentImpl(offset, size);
    135   }
    136 
    137   static FPDF_BOOL SIsDataAvail(FX_FILEAVAIL* pThis,
    138                                 size_t offset,
    139                                 size_t size) {
    140     return static_cast<TestAsyncLoader*>(pThis)->IsDataAvailImpl(offset, size);
    141   }
    142 
    143   FPDF_FILEACCESS file_access_;
    144 
    145   std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
    146   size_t file_length_;
    147   std::vector<std::pair<size_t, size_t>> requested_segments_;
    148   size_t max_requested_bound_ = 0;
    149   bool is_new_data_available_ = true;
    150 
    151   RangeSet available_ranges_;
    152 };
    153 
    154 }  // namespace
    155 
    156 class FPDFDataAvailEmbeddertest : public EmbedderTest {};
    157 
    158 TEST_F(FPDFDataAvailEmbeddertest, TrailerUnterminated) {
    159   // Document must load without crashing but is too malformed to be available.
    160   EXPECT_FALSE(OpenDocument("trailer_unterminated.pdf"));
    161   MockDownloadHints hints;
    162   EXPECT_FALSE(FPDFAvail_IsDocAvail(avail_, &hints));
    163 }
    164 
    165 TEST_F(FPDFDataAvailEmbeddertest, TrailerAsHexstring) {
    166   // Document must load without crashing but is too malformed to be available.
    167   EXPECT_FALSE(OpenDocument("trailer_as_hexstring.pdf"));
    168   MockDownloadHints hints;
    169   EXPECT_FALSE(FPDFAvail_IsDocAvail(avail_, &hints));
    170 }
    171 
    172 TEST_F(FPDFDataAvailEmbeddertest, LoadUsingHintTables) {
    173   TestAsyncLoader loader("feature_linearized_loading.pdf");
    174   avail_ = FPDFAvail_Create(loader.file_avail(), loader.file_access());
    175   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsDocAvail(avail_, loader.hints()));
    176   document_ = FPDFAvail_GetDocument(avail_, nullptr);
    177   ASSERT_TRUE(document_);
    178   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsPageAvail(avail_, 1, loader.hints()));
    179 
    180   // No new data available, to prevent load "Pages" node.
    181   loader.set_is_new_data_available(false);
    182   FPDF_PAGE page = FPDF_LoadPage(document(), 1);
    183   EXPECT_TRUE(page);
    184   FPDF_ClosePage(page);
    185 }
    186 
    187 TEST_F(FPDFDataAvailEmbeddertest, CheckFormAvailIfLinearized) {
    188   TestAsyncLoader loader("feature_linearized_loading.pdf");
    189   avail_ = FPDFAvail_Create(loader.file_avail(), loader.file_access());
    190   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsDocAvail(avail_, loader.hints()));
    191   document_ = FPDFAvail_GetDocument(avail_, nullptr);
    192   ASSERT_TRUE(document_);
    193 
    194   // Prevent access to non requested data to coerce the parser to send new
    195   // request for non available (non requested before) data.
    196   loader.set_is_new_data_available(false);
    197   loader.ClearRequestedSegments();
    198 
    199   int status = PDF_FORM_NOTAVAIL;
    200   while (status == PDF_FORM_NOTAVAIL) {
    201     loader.FlushRequestedData();
    202     status = FPDFAvail_IsFormAvail(avail_, loader.hints());
    203   }
    204   EXPECT_NE(PDF_FORM_ERROR, status);
    205 }
    206 
    207 TEST_F(FPDFDataAvailEmbeddertest,
    208        DoNotLoadMainCrossRefForFirstPageIfLinearized) {
    209   TestAsyncLoader loader("feature_linearized_loading.pdf");
    210   avail_ = FPDFAvail_Create(loader.file_avail(), loader.file_access());
    211   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsDocAvail(avail_, loader.hints()));
    212   document_ = FPDFAvail_GetDocument(avail_, nullptr);
    213   ASSERT_TRUE(document_);
    214   const int first_page_num = FPDFAvail_GetFirstPageNum(document_);
    215 
    216   // The main cross ref table should not be processed.
    217   // (It is always at file end)
    218   EXPECT_GT(loader.file_access()->m_FileLen,
    219             loader.max_already_available_bound());
    220 
    221   // Prevent access to non requested data to coerce the parser to send new
    222   // request for non available (non requested before) data.
    223   loader.set_is_new_data_available(false);
    224   FPDFAvail_IsPageAvail(avail_, first_page_num, loader.hints());
    225 
    226   // The main cross ref table should not be requested.
    227   // (It is always at file end)
    228   EXPECT_GT(loader.file_access()->m_FileLen, loader.max_requested_bound());
    229 
    230   // Allow parse page.
    231   loader.set_is_new_data_available(true);
    232   ASSERT_EQ(PDF_DATA_AVAIL,
    233             FPDFAvail_IsPageAvail(avail_, first_page_num, loader.hints()));
    234 
    235   // The main cross ref table should not be processed.
    236   // (It is always at file end)
    237   EXPECT_GT(loader.file_access()->m_FileLen,
    238             loader.max_already_available_bound());
    239 
    240   // Prevent loading data, while page loading.
    241   loader.set_is_new_data_available(false);
    242   FPDF_PAGE page = FPDF_LoadPage(document(), first_page_num);
    243   EXPECT_TRUE(page);
    244   FPDF_ClosePage(page);
    245 }
    246 
    247 TEST_F(FPDFDataAvailEmbeddertest, LoadSecondPageIfLinearizedWithHints) {
    248   TestAsyncLoader loader("feature_linearized_loading.pdf");
    249   avail_ = FPDFAvail_Create(loader.file_avail(), loader.file_access());
    250   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsDocAvail(avail_, loader.hints()));
    251   document_ = FPDFAvail_GetDocument(avail_, nullptr);
    252   ASSERT_TRUE(document_);
    253 
    254   static constexpr uint32_t kSecondPageNum = 1;
    255 
    256   // Prevent access to non requested data to coerce the parser to send new
    257   // request for non available (non requested before) data.
    258   loader.set_is_new_data_available(false);
    259   loader.ClearRequestedSegments();
    260 
    261   int status = PDF_DATA_NOTAVAIL;
    262   while (status == PDF_DATA_NOTAVAIL) {
    263     loader.FlushRequestedData();
    264     status = FPDFAvail_IsPageAvail(avail_, kSecondPageNum, loader.hints());
    265   }
    266   EXPECT_EQ(PDF_DATA_AVAIL, status);
    267 
    268   // Prevent loading data, while page loading.
    269   loader.set_is_new_data_available(false);
    270   FPDF_PAGE page = FPDF_LoadPage(document(), kSecondPageNum);
    271   EXPECT_TRUE(page);
    272   FPDF_ClosePage(page);
    273 }
    274