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