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