blob: 20841538527c05182eb70f8784e007bab7bf7895 [file] [log] [blame]
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -07001// 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"
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -070015#include "testing/range_set.h"
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -070016#include "testing/test_support.h"
17#include "testing/utils/path_service.h"
18
19namespace {
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -070020
21class 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
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -070034class 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 {
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -070078 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();
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -070088 }
89
90 private:
91 void SetDataAvailable(size_t start, size_t size) {
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -070092 available_ranges_.Union(RangeSet::Range(start, start + size));
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -070093 }
94
95 bool CheckDataAlreadyAvailable(size_t start, size_t size) const {
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -070096 return available_ranges_.Contains(RangeSet::Range(start, start + size));
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -070097 }
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
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700151 RangeSet available_ranges_;
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700152};
153
154} // namespace
155
156class FPDFDataAvailEmbeddertest : public EmbedderTest {};
157
158TEST_F(FPDFDataAvailEmbeddertest, TrailerUnterminated) {
159 // Document must load without crashing but is too malformed to be available.
160 EXPECT_FALSE(OpenDocument("trailer_unterminated.pdf"));
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700161 MockDownloadHints hints;
162 EXPECT_FALSE(FPDFAvail_IsDocAvail(avail_, &hints));
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700163}
164
165TEST_F(FPDFDataAvailEmbeddertest, TrailerAsHexstring) {
166 // Document must load without crashing but is too malformed to be available.
167 EXPECT_FALSE(OpenDocument("trailer_as_hexstring.pdf"));
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700168 MockDownloadHints hints;
169 EXPECT_FALSE(FPDFAvail_IsDocAvail(avail_, &hints));
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700170}
171
172TEST_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);
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700182 FPDF_PAGE page = FPDF_LoadPage(document(), 1);
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700183 EXPECT_TRUE(page);
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700184 FPDF_ClosePage(page);
185}
186
187TEST_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);
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700205}
206
207TEST_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);
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700242 FPDF_PAGE page = FPDF_LoadPage(document(), first_page_num);
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700243 EXPECT_TRUE(page);
Philip P. Moltmannd904c1e2018-03-19 09:26:45 -0700244 FPDF_ClosePage(page);
245}
246
247TEST_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);
Philip P. Moltmann4d3acf42017-03-20 11:05:52 -0700273}