• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/test_support.h"
16 #include "testing/utils/path_service.h"
17 
18 namespace {
19 class TestAsyncLoader : public FX_DOWNLOADHINTS, FX_FILEAVAIL {
20  public:
TestAsyncLoader(const std::string & file_name)21   explicit TestAsyncLoader(const std::string& file_name) {
22     std::string file_path;
23     if (!PathService::GetTestFilePath(file_name, &file_path))
24       return;
25     file_contents_ = GetFileContents(file_path.c_str(), &file_length_);
26     if (!file_contents_)
27       return;
28 
29     file_access_.m_FileLen = static_cast<unsigned long>(file_length_);
30     file_access_.m_GetBlock = SGetBlock;
31     file_access_.m_Param = this;
32 
33     FX_DOWNLOADHINTS::version = 1;
34     FX_DOWNLOADHINTS::AddSegment = SAddSegment;
35 
36     FX_FILEAVAIL::version = 1;
37     FX_FILEAVAIL::IsDataAvail = SIsDataAvail;
38   }
39 
IsOpened() const40   bool IsOpened() const { return !!file_contents_; }
41 
file_access()42   FPDF_FILEACCESS* file_access() { return &file_access_; }
hints()43   FX_DOWNLOADHINTS* hints() { return this; }
file_avail()44   FX_FILEAVAIL* file_avail() { return this; }
45 
requested_segments() const46   const std::vector<std::pair<size_t, size_t>>& requested_segments() const {
47     return requested_segments_;
48   }
49 
max_requested_bound() const50   size_t max_requested_bound() const { return max_requested_bound_; }
51 
ClearRequestedSegments()52   void ClearRequestedSegments() {
53     requested_segments_.clear();
54     max_requested_bound_ = 0;
55   }
56 
is_new_data_available() const57   bool is_new_data_available() const { return is_new_data_available_; }
set_is_new_data_available(bool is_new_data_available)58   void set_is_new_data_available(bool is_new_data_available) {
59     is_new_data_available_ = is_new_data_available;
60   }
61 
max_already_available_bound() const62   size_t max_already_available_bound() const {
63     return available_ranges_.empty() ? 0 : available_ranges_.rbegin()->second;
64   }
65 
66  private:
SetDataAvailable(size_t start,size_t size)67   void SetDataAvailable(size_t start, size_t size) {
68     if (size == 0)
69       return;
70     const auto range = std::make_pair(start, start + size);
71     if (available_ranges_.empty()) {
72       available_ranges_.insert(range);
73       return;
74     }
75     auto start_it = available_ranges_.upper_bound(range);
76     if (start_it != available_ranges_.begin())
77       --start_it;  // start now points to the key equal or lower than offset.
78     if (start_it->second < range.first)
79       ++start_it;  // start element is entirely before current range, skip it.
80 
81     auto end_it = available_ranges_.upper_bound(
82         std::make_pair(range.second, range.second));
83     if (start_it == end_it) {  // No ranges to merge.
84       available_ranges_.insert(range);
85       return;
86     }
87 
88     --end_it;
89 
90     size_t new_start = std::min<size_t>(start_it->first, range.first);
91     size_t new_end = std::max(end_it->second, range.second);
92 
93     available_ranges_.erase(start_it, ++end_it);
94     available_ranges_.insert(std::make_pair(new_start, new_end));
95   }
96 
CheckDataAlreadyAvailable(size_t start,size_t size) const97   bool CheckDataAlreadyAvailable(size_t start, size_t size) const {
98     if (size == 0)
99       return false;
100     const auto range = std::make_pair(start, start + size);
101     auto it = available_ranges_.upper_bound(range);
102     if (it == available_ranges_.begin())
103       return false;  // No ranges includes range.start().
104 
105     --it;  // Now it starts equal or before range.start().
106     return it->second >= range.second;
107   }
108 
GetBlockImpl(unsigned long pos,unsigned char * pBuf,unsigned long size)109   int GetBlockImpl(unsigned long pos, unsigned char* pBuf, unsigned long size) {
110     if (!IsDataAvailImpl(pos, size))
111       return 0;
112     const unsigned long end =
113         std::min(static_cast<unsigned long>(file_length_), pos + size);
114     if (end <= pos)
115       return 0;
116     memcpy(pBuf, file_contents_.get() + pos, end - pos);
117     SetDataAvailable(pos, end - pos);
118     return static_cast<int>(end - pos);
119   }
120 
AddSegmentImpl(size_t offset,size_t size)121   void AddSegmentImpl(size_t offset, size_t size) {
122     requested_segments_.push_back(std::make_pair(offset, size));
123     max_requested_bound_ = std::max(max_requested_bound_, offset + size);
124   }
125 
IsDataAvailImpl(size_t offset,size_t size)126   bool IsDataAvailImpl(size_t offset, size_t size) {
127     if (offset + size > file_length_)
128       return false;
129     if (is_new_data_available_) {
130       SetDataAvailable(offset, size);
131       return true;
132     }
133     return CheckDataAlreadyAvailable(offset, size);
134   }
135 
SGetBlock(void * param,unsigned long pos,unsigned char * pBuf,unsigned long size)136   static int SGetBlock(void* param,
137                        unsigned long pos,
138                        unsigned char* pBuf,
139                        unsigned long size) {
140     return static_cast<TestAsyncLoader*>(param)->GetBlockImpl(pos, pBuf, size);
141   }
142 
SAddSegment(FX_DOWNLOADHINTS * pThis,size_t offset,size_t size)143   static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {
144     return static_cast<TestAsyncLoader*>(pThis)->AddSegmentImpl(offset, size);
145   }
146 
SIsDataAvail(FX_FILEAVAIL * pThis,size_t offset,size_t size)147   static FPDF_BOOL SIsDataAvail(FX_FILEAVAIL* pThis,
148                                 size_t offset,
149                                 size_t size) {
150     return static_cast<TestAsyncLoader*>(pThis)->IsDataAvailImpl(offset, size);
151   }
152 
153   FPDF_FILEACCESS file_access_;
154 
155   std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
156   size_t file_length_;
157   std::vector<std::pair<size_t, size_t>> requested_segments_;
158   size_t max_requested_bound_ = 0;
159   bool is_new_data_available_ = true;
160 
161   using Range = std::pair<size_t, size_t>;
162   struct range_compare {
operator ()__anon25cedee30111::TestAsyncLoader::range_compare163     bool operator()(const Range& lval, const Range& rval) const {
164       return lval.first < rval.first;
165     }
166   };
167   using RangesContainer = std::set<Range, range_compare>;
168   RangesContainer available_ranges_;
169 };
170 
171 }  // namespace
172 
173 class FPDFDataAvailEmbeddertest : public EmbedderTest {};
174 
TEST_F(FPDFDataAvailEmbeddertest,TrailerUnterminated)175 TEST_F(FPDFDataAvailEmbeddertest, TrailerUnterminated) {
176   // Document must load without crashing but is too malformed to be available.
177   EXPECT_FALSE(OpenDocument("trailer_unterminated.pdf"));
178   EXPECT_FALSE(FPDFAvail_IsDocAvail(avail_, &hints_));
179 }
180 
TEST_F(FPDFDataAvailEmbeddertest,TrailerAsHexstring)181 TEST_F(FPDFDataAvailEmbeddertest, TrailerAsHexstring) {
182   // Document must load without crashing but is too malformed to be available.
183   EXPECT_FALSE(OpenDocument("trailer_as_hexstring.pdf"));
184   EXPECT_FALSE(FPDFAvail_IsDocAvail(avail_, &hints_));
185 }
186 
TEST_F(FPDFDataAvailEmbeddertest,LoadUsingHintTables)187 TEST_F(FPDFDataAvailEmbeddertest, LoadUsingHintTables) {
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   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsPageAvail(avail_, 1, loader.hints()));
194 
195   // No new data available, to prevent load "Pages" node.
196   loader.set_is_new_data_available(false);
197   FPDF_PAGE page = LoadPage(1);
198   EXPECT_TRUE(page);
199   UnloadPage(page);
200 }
201 
TEST_F(FPDFDataAvailEmbeddertest,DoNotLoadMainCrossRefForFirstPageIfLinearized)202 TEST_F(FPDFDataAvailEmbeddertest,
203        DoNotLoadMainCrossRefForFirstPageIfLinearized) {
204   TestAsyncLoader loader("feature_linearized_loading.pdf");
205   avail_ = FPDFAvail_Create(loader.file_avail(), loader.file_access());
206   ASSERT_EQ(PDF_DATA_AVAIL, FPDFAvail_IsDocAvail(avail_, loader.hints()));
207   document_ = FPDFAvail_GetDocument(avail_, nullptr);
208   ASSERT_TRUE(document_);
209   const int first_page_num = FPDFAvail_GetFirstPageNum(document_);
210 
211   // The main cross ref table should not be processed.
212   // (It is always at file end)
213   EXPECT_GT(loader.file_access()->m_FileLen,
214             loader.max_already_available_bound());
215 
216   // Prevent access to non requested data to coerce the parser to send new
217   // request for non available (non requested before) data.
218   loader.set_is_new_data_available(false);
219   FPDFAvail_IsPageAvail(avail_, first_page_num, loader.hints());
220 
221   // The main cross ref table should not be requested.
222   // (It is always at file end)
223   EXPECT_GT(loader.file_access()->m_FileLen, loader.max_requested_bound());
224 
225   // Allow parse page.
226   loader.set_is_new_data_available(true);
227   ASSERT_EQ(PDF_DATA_AVAIL,
228             FPDFAvail_IsPageAvail(avail_, first_page_num, loader.hints()));
229 
230   // The main cross ref table should not be processed.
231   // (It is always at file end)
232   EXPECT_GT(loader.file_access()->m_FileLen,
233             loader.max_already_available_bound());
234 
235   // Prevent loading data, while page loading.
236   loader.set_is_new_data_available(false);
237   FPDF_PAGE page = LoadPage(first_page_num);
238   EXPECT_TRUE(page);
239   UnloadPage(page);
240 }
241