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:
SAddSegment(FX_DOWNLOADHINTS * pThis,size_t offset,size_t size)23   static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {
24   }
25 
MockDownloadHints()26   MockDownloadHints() {
27     FX_DOWNLOADHINTS::version = 1;
28     FX_DOWNLOADHINTS::AddSegment = SAddSegment;
29   }
30 
~MockDownloadHints()31   ~MockDownloadHints() {}
32 };
33 
34 class TestAsyncLoader : public FX_DOWNLOADHINTS, FX_FILEAVAIL {
35  public:
TestAsyncLoader(const std::string & file_name)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 
IsOpened() const55   bool IsOpened() const { return !!file_contents_; }
56 
file_access()57   FPDF_FILEACCESS* file_access() { return &file_access_; }
hints()58   FX_DOWNLOADHINTS* hints() { return this; }
file_avail()59   FX_FILEAVAIL* file_avail() { return this; }
60 
requested_segments() const61   const std::vector<std::pair<size_t, size_t>>& requested_segments() const {
62     return requested_segments_;
63   }
64 
max_requested_bound() const65   size_t max_requested_bound() const { return max_requested_bound_; }
66 
ClearRequestedSegments()67   void ClearRequestedSegments() {
68     requested_segments_.clear();
69     max_requested_bound_ = 0;
70   }
71 
is_new_data_available() const72   bool is_new_data_available() const { return is_new_data_available_; }
set_is_new_data_available(bool 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 
max_already_available_bound() const77   size_t max_already_available_bound() const {
78     return available_ranges_.IsEmpty()
79                ? 0
80                : available_ranges_.ranges().rbegin()->second;
81   }
82 
FlushRequestedData()83   void FlushRequestedData() {
84     for (const auto& it : requested_segments_) {
85       SetDataAvailable(it.first, it.second);
86     }
87     ClearRequestedSegments();
88   }
89 
90  private:
SetDataAvailable(size_t start,size_t size)91   void SetDataAvailable(size_t start, size_t size) {
92     available_ranges_.Union(RangeSet::Range(start, start + size));
93   }
94 
CheckDataAlreadyAvailable(size_t start,size_t size) const95   bool CheckDataAlreadyAvailable(size_t start, size_t size) const {
96     return available_ranges_.Contains(RangeSet::Range(start, start + size));
97   }
98 
GetBlockImpl(unsigned long pos,unsigned char * pBuf,unsigned long size)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 
AddSegmentImpl(size_t offset,size_t size)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 
IsDataAvailImpl(size_t offset,size_t size)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 
SGetBlock(void * param,unsigned long pos,unsigned char * pBuf,unsigned long size)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 
SAddSegment(FX_DOWNLOADHINTS * pThis,size_t offset,size_t size)133   static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {
134     return static_cast<TestAsyncLoader*>(pThis)->AddSegmentImpl(offset, size);
135   }
136 
SIsDataAvail(FX_FILEAVAIL * pThis,size_t offset,size_t size)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 
TEST_F(FPDFDataAvailEmbeddertest,TrailerUnterminated)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 
TEST_F(FPDFDataAvailEmbeddertest,TrailerAsHexstring)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 
TEST_F(FPDFDataAvailEmbeddertest,LoadUsingHintTables)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 
TEST_F(FPDFDataAvailEmbeddertest,CheckFormAvailIfLinearized)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 
TEST_F(FPDFDataAvailEmbeddertest,DoNotLoadMainCrossRefForFirstPageIfLinearized)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 
TEST_F(FPDFDataAvailEmbeddertest,LoadSecondPageIfLinearizedWithHints)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