• 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 <limits>
6 #include <memory>
7 #include <string>
8 #include <vector>
9 
10 #include "core/fpdfapi/parser/cpdf_linearized_header.h"
11 #include "core/fpdfapi/parser/cpdf_object.h"
12 #include "core/fpdfapi/parser/cpdf_parser.h"
13 #include "core/fpdfapi/parser/cpdf_syntax_parser.h"
14 #include "core/fxcrt/cfx_readonlymemorystream.h"
15 #include "core/fxcrt/fx_extension.h"
16 #include "core/fxcrt/fx_stream.h"
17 #include "core/fxcrt/retain_ptr.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "testing/utils/path_service.h"
20 #include "third_party/base/ptr_util.h"
21 
22 namespace {
23 
GetObjInfo(const CPDF_Parser & parser,uint32_t obj_num)24 CPDF_CrossRefTable::ObjectInfo GetObjInfo(const CPDF_Parser& parser,
25                                           uint32_t obj_num) {
26   const auto* info = parser.GetCrossRefTable()->GetObjectInfo(obj_num);
27   return info ? *info : CPDF_CrossRefTable::ObjectInfo();
28 }
29 
30 }  // namespace
31 
32 // A wrapper class to help test member functions of CPDF_Parser.
33 class CPDF_TestParser final : public CPDF_Parser {
34  public:
CPDF_TestParser()35   CPDF_TestParser() {}
~CPDF_TestParser()36   ~CPDF_TestParser() {}
37 
38   // Setup reading from a file and initial states.
InitTestFromFile(const char * path)39   bool InitTestFromFile(const char* path) {
40     RetainPtr<IFX_SeekableReadStream> pFileAccess =
41         IFX_SeekableReadStream::CreateFromFilename(path);
42     if (!pFileAccess)
43       return false;
44 
45     // For the test file, the header is set at the beginning.
46     m_pSyntax = pdfium::MakeUnique<CPDF_SyntaxParser>(pFileAccess);
47     return true;
48   }
49 
50   // Setup reading from a buffer and initial states.
InitTestFromBufferWithOffset(pdfium::span<const uint8_t> buffer,FX_FILESIZE header_offset)51   bool InitTestFromBufferWithOffset(pdfium::span<const uint8_t> buffer,
52                                     FX_FILESIZE header_offset) {
53     m_pSyntax = CPDF_SyntaxParser::CreateForTesting(
54         pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(buffer), header_offset);
55     return true;
56   }
57 
InitTestFromBuffer(pdfium::span<const uint8_t> buffer)58   bool InitTestFromBuffer(pdfium::span<const uint8_t> buffer) {
59     return InitTestFromBufferWithOffset(buffer, 0 /*header_offset*/);
60   }
61 
62  private:
63   // Add test cases here as private friend so that protected members in
64   // CPDF_Parser can be accessed by test cases.
65   // Need to access RebuildCrossRef.
66   FRIEND_TEST(cpdf_parser, RebuildCrossRefCorrectly);
67   FRIEND_TEST(cpdf_parser, RebuildCrossRefFailed);
68   // Need to access LoadCrossRefV4.
69   FRIEND_TEST(cpdf_parser, LoadCrossRefV4);
70 };
71 
TEST(cpdf_parser,RebuildCrossRefCorrectly)72 TEST(cpdf_parser, RebuildCrossRefCorrectly) {
73   CPDF_TestParser parser;
74   std::string test_file;
75   ASSERT_TRUE(PathService::GetTestFilePath("parser_rebuildxref_correct.pdf",
76                                            &test_file));
77   ASSERT_TRUE(parser.InitTestFromFile(test_file.c_str())) << test_file;
78 
79   ASSERT_TRUE(parser.RebuildCrossRef());
80   const FX_FILESIZE offsets[] = {0, 15, 61, 154, 296, 374, 450};
81   const uint16_t versions[] = {0, 0, 2, 4, 6, 8, 0};
82   for (size_t i = 0; i < FX_ArraySize(offsets); ++i)
83     EXPECT_EQ(offsets[i], GetObjInfo(parser, i).pos);
84   for (size_t i = 0; i < FX_ArraySize(versions); ++i)
85     EXPECT_EQ(versions[i], GetObjInfo(parser, i).gennum);
86 }
87 
TEST(cpdf_parser,RebuildCrossRefFailed)88 TEST(cpdf_parser, RebuildCrossRefFailed) {
89   CPDF_TestParser parser;
90   std::string test_file;
91   ASSERT_TRUE(PathService::GetTestFilePath(
92       "parser_rebuildxref_error_notrailer.pdf", &test_file));
93   ASSERT_TRUE(parser.InitTestFromFile(test_file.c_str())) << test_file;
94 
95   ASSERT_FALSE(parser.RebuildCrossRef());
96 }
97 
TEST(cpdf_parser,LoadCrossRefV4)98 TEST(cpdf_parser, LoadCrossRefV4) {
99   {
100     static const unsigned char kXrefTable[] =
101         "xref \n"
102         "0 6 \n"
103         "0000000003 65535 f \n"
104         "0000000017 00000 n \n"
105         "0000000081 00000 n \n"
106         "0000000000 00007 f \n"
107         "0000000331 00000 n \n"
108         "0000000409 00000 n \n"
109         "trail";  // Needed to end cross ref table reading.
110     CPDF_TestParser parser;
111     ASSERT_TRUE(parser.InitTestFromBuffer(kXrefTable));
112 
113     ASSERT_TRUE(parser.LoadCrossRefV4(0, false));
114     static const FX_FILESIZE kOffsets[] = {0, 17, 81, 0, 331, 409};
115     static const CPDF_TestParser::ObjectType kTypes[] = {
116         CPDF_TestParser::ObjectType::kFree,
117         CPDF_TestParser::ObjectType::kNotCompressed,
118         CPDF_TestParser::ObjectType::kNotCompressed,
119         CPDF_TestParser::ObjectType::kFree,
120         CPDF_TestParser::ObjectType::kNotCompressed,
121         CPDF_TestParser::ObjectType::kNotCompressed};
122     static_assert(FX_ArraySize(kOffsets) == FX_ArraySize(kTypes),
123                   "kOffsets / kTypes size mismatch");
124     for (size_t i = 0; i < FX_ArraySize(kOffsets); ++i) {
125       EXPECT_EQ(kOffsets[i], GetObjInfo(parser, i).pos);
126       EXPECT_EQ(kTypes[i], GetObjInfo(parser, i).type);
127     }
128   }
129   {
130     static const unsigned char kXrefTable[] =
131         "xref \n"
132         "0 1 \n"
133         "0000000000 65535 f \n"
134         "3 1 \n"
135         "0000025325 00000 n \n"
136         "8 2 \n"
137         "0000025518 00002 n \n"
138         "0000025635 00000 n \n"
139         "12 1 \n"
140         "0000025777 00000 n \n"
141         "trail";  // Needed to end cross ref table reading.
142     CPDF_TestParser parser;
143     ASSERT_TRUE(parser.InitTestFromBuffer(kXrefTable));
144 
145     ASSERT_TRUE(parser.LoadCrossRefV4(0, false));
146     static const FX_FILESIZE kOffsets[] = {0, 0,     0,     25325, 0, 0,    0,
147                                            0, 25518, 25635, 0,     0, 25777};
148     static const CPDF_TestParser::ObjectType kTypes[] = {
149         CPDF_TestParser::ObjectType::kFree,
150         CPDF_TestParser::ObjectType::kFree,
151         CPDF_TestParser::ObjectType::kFree,
152         CPDF_TestParser::ObjectType::kNotCompressed,
153         CPDF_TestParser::ObjectType::kFree,
154         CPDF_TestParser::ObjectType::kFree,
155         CPDF_TestParser::ObjectType::kFree,
156         CPDF_TestParser::ObjectType::kFree,
157         CPDF_TestParser::ObjectType::kNotCompressed,
158         CPDF_TestParser::ObjectType::kNotCompressed,
159         CPDF_TestParser::ObjectType::kFree,
160         CPDF_TestParser::ObjectType::kFree,
161         CPDF_TestParser::ObjectType::kNotCompressed};
162     static_assert(FX_ArraySize(kOffsets) == FX_ArraySize(kTypes),
163                   "kOffsets / kTypes size mismatch");
164     for (size_t i = 0; i < FX_ArraySize(kOffsets); ++i) {
165       EXPECT_EQ(kOffsets[i], GetObjInfo(parser, i).pos);
166       EXPECT_EQ(kTypes[i], GetObjInfo(parser, i).type);
167     }
168   }
169   {
170     static const unsigned char kXrefTable[] =
171         "xref \n"
172         "0 1 \n"
173         "0000000000 65535 f \n"
174         "3 1 \n"
175         "0000025325 00000 n \n"
176         "8 2 \n"
177         "0000000000 65535 f \n"
178         "0000025635 00000 n \n"
179         "12 1 \n"
180         "0000025777 00000 n \n"
181         "trail";  // Needed to end cross ref table reading.
182     CPDF_TestParser parser;
183     ASSERT_TRUE(parser.InitTestFromBuffer(kXrefTable));
184 
185     ASSERT_TRUE(parser.LoadCrossRefV4(0, false));
186     static const FX_FILESIZE kOffsets[] = {0, 0, 0,     25325, 0, 0,    0,
187                                            0, 0, 25635, 0,     0, 25777};
188     static const CPDF_TestParser::ObjectType kTypes[] = {
189         CPDF_TestParser::ObjectType::kFree,
190         CPDF_TestParser::ObjectType::kFree,
191         CPDF_TestParser::ObjectType::kFree,
192         CPDF_TestParser::ObjectType::kNotCompressed,
193         CPDF_TestParser::ObjectType::kFree,
194         CPDF_TestParser::ObjectType::kFree,
195         CPDF_TestParser::ObjectType::kFree,
196         CPDF_TestParser::ObjectType::kFree,
197         CPDF_TestParser::ObjectType::kFree,
198         CPDF_TestParser::ObjectType::kNotCompressed,
199         CPDF_TestParser::ObjectType::kFree,
200         CPDF_TestParser::ObjectType::kFree,
201         CPDF_TestParser::ObjectType::kNotCompressed};
202     static_assert(FX_ArraySize(kOffsets) == FX_ArraySize(kTypes),
203                   "kOffsets / kTypes size mismatch");
204     for (size_t i = 0; i < FX_ArraySize(kOffsets); ++i) {
205       EXPECT_EQ(kOffsets[i], GetObjInfo(parser, i).pos);
206       EXPECT_EQ(kTypes[i], GetObjInfo(parser, i).type);
207     }
208   }
209   {
210     static const unsigned char kXrefTable[] =
211         "xref \n"
212         "0 7 \n"
213         "0000000002 65535 f \n"
214         "0000000023 00000 n \n"
215         "0000000003 65535 f \n"
216         "0000000004 65535 f \n"
217         "0000000000 65535 f \n"
218         "0000000045 00000 n \n"
219         "0000000179 00000 n \n"
220         "trail";  // Needed to end cross ref table reading.
221     CPDF_TestParser parser;
222     ASSERT_TRUE(parser.InitTestFromBuffer(kXrefTable));
223 
224     ASSERT_TRUE(parser.LoadCrossRefV4(0, false));
225     static const FX_FILESIZE kOffsets[] = {0, 23, 0, 0, 0, 45, 179};
226     static const CPDF_TestParser::ObjectType kTypes[] = {
227         CPDF_TestParser::ObjectType::kFree,
228         CPDF_TestParser::ObjectType::kNotCompressed,
229         CPDF_TestParser::ObjectType::kFree,
230         CPDF_TestParser::ObjectType::kFree,
231         CPDF_TestParser::ObjectType::kFree,
232         CPDF_TestParser::ObjectType::kNotCompressed,
233         CPDF_TestParser::ObjectType::kNotCompressed};
234     static_assert(FX_ArraySize(kOffsets) == FX_ArraySize(kTypes),
235                   "kOffsets / kTypes size mismatch");
236     for (size_t i = 0; i < FX_ArraySize(kOffsets); ++i) {
237       EXPECT_EQ(kOffsets[i], GetObjInfo(parser, i).pos);
238       EXPECT_EQ(kTypes[i], GetObjInfo(parser, i).type);
239     }
240   }
241   {
242     // Regression test for https://crbug.com/945624 - Make sure the parser
243     // can correctly handle table sizes that are multiples of the read size,
244     // which is 1024.
245     std::string xref_table = "xref \n 0 2048 \n";
246     xref_table.reserve(41000);
247     for (int i = 0; i < 2048; ++i) {
248       char buffer[21];
249       snprintf(buffer, sizeof(buffer), "%010d 00000 n \n", i + 1);
250       xref_table += buffer;
251     }
252     xref_table += "trail";  // Needed to end cross ref table reading.
253     CPDF_TestParser parser;
254     ASSERT_TRUE(parser.InitTestFromBuffer(
255         pdfium::make_span(reinterpret_cast<const uint8_t*>(xref_table.c_str()),
256                           xref_table.size())));
257 
258     ASSERT_TRUE(parser.LoadCrossRefV4(0, false));
259     for (size_t i = 0; i < 2048; ++i) {
260       EXPECT_EQ(static_cast<int>(i) + 1, GetObjInfo(parser, i).pos);
261       EXPECT_EQ(CPDF_TestParser::ObjectType::kNotCompressed,
262                 GetObjInfo(parser, i).type);
263     }
264   }
265 }
266 
TEST(cpdf_parser,ParseStartXRef)267 TEST(cpdf_parser, ParseStartXRef) {
268   CPDF_TestParser parser;
269   std::string test_file;
270   ASSERT_TRUE(
271       PathService::GetTestFilePath("annotation_stamp_with_ap.pdf", &test_file));
272   ASSERT_TRUE(parser.InitTestFromFile(test_file.c_str())) << test_file;
273 
274   EXPECT_EQ(100940, parser.ParseStartXRef());
275   RetainPtr<CPDF_Object> cross_ref_v5_obj =
276       parser.ParseIndirectObjectAt(100940, 0);
277   ASSERT_TRUE(cross_ref_v5_obj);
278   EXPECT_EQ(75u, cross_ref_v5_obj->GetObjNum());
279 }
280 
TEST(cpdf_parser,ParseStartXRefWithHeaderOffset)281 TEST(cpdf_parser, ParseStartXRefWithHeaderOffset) {
282   static constexpr FX_FILESIZE kTestHeaderOffset = 765;
283   std::string test_file;
284   ASSERT_TRUE(
285       PathService::GetTestFilePath("annotation_stamp_with_ap.pdf", &test_file));
286   RetainPtr<IFX_SeekableReadStream> pFileAccess =
287       IFX_SeekableReadStream::CreateFromFilename(test_file.c_str());
288   ASSERT_TRUE(pFileAccess);
289 
290   std::vector<unsigned char> data(pFileAccess->GetSize() + kTestHeaderOffset);
291   ASSERT_TRUE(pFileAccess->ReadBlockAtOffset(&data.front() + kTestHeaderOffset,
292                                              0, pFileAccess->GetSize()));
293   CPDF_TestParser parser;
294   parser.InitTestFromBufferWithOffset(data, kTestHeaderOffset);
295 
296   EXPECT_EQ(100940, parser.ParseStartXRef());
297   RetainPtr<CPDF_Object> cross_ref_v5_obj =
298       parser.ParseIndirectObjectAt(100940, 0);
299   ASSERT_TRUE(cross_ref_v5_obj);
300   EXPECT_EQ(75u, cross_ref_v5_obj->GetObjNum());
301 }
302 
TEST(cpdf_parser,ParseLinearizedWithHeaderOffset)303 TEST(cpdf_parser, ParseLinearizedWithHeaderOffset) {
304   static constexpr FX_FILESIZE kTestHeaderOffset = 765;
305   std::string test_file;
306   ASSERT_TRUE(PathService::GetTestFilePath("linearized.pdf", &test_file));
307   RetainPtr<IFX_SeekableReadStream> pFileAccess =
308       IFX_SeekableReadStream::CreateFromFilename(test_file.c_str());
309   ASSERT_TRUE(pFileAccess);
310 
311   std::vector<unsigned char> data(pFileAccess->GetSize() + kTestHeaderOffset);
312   ASSERT_TRUE(pFileAccess->ReadBlockAtOffset(&data.front() + kTestHeaderOffset,
313                                              0, pFileAccess->GetSize()));
314   CPDF_TestParser parser;
315   parser.InitTestFromBufferWithOffset(data, kTestHeaderOffset);
316 
317   EXPECT_TRUE(parser.ParseLinearizedHeader());
318 }
319 
TEST(cpdf_parser,BadStartXrefShouldNotBuildCrossRefTable)320 TEST(cpdf_parser, BadStartXrefShouldNotBuildCrossRefTable) {
321   const unsigned char kData[] =
322       "%PDF1-7 0 obj <</Size 2 /W [0 0 0]\n>>\n"
323       "stream\n"
324       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
325       "endstream\n"
326       "endobj\n"
327       "startxref\n"
328       "6\n"
329       "%%EOF\n";
330   CPDF_TestParser parser;
331   ASSERT_TRUE(parser.InitTestFromBuffer(kData));
332   EXPECT_EQ(CPDF_Parser::FORMAT_ERROR, parser.StartParseInternal());
333   ASSERT_TRUE(parser.GetCrossRefTable());
334   EXPECT_EQ(0u, parser.GetCrossRefTable()->objects_info().size());
335 }
336