• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The PDFium Authors
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 "core/fpdfdoc/cpdf_filespec.h"
6 
7 #include <array>
8 #include <memory>
9 #include <utility>
10 #include <vector>
11 
12 #include "build/build_config.h"
13 #include "core/fpdfapi/parser/cpdf_dictionary.h"
14 #include "core/fpdfapi/parser/cpdf_indirect_object_holder.h"
15 #include "core/fpdfapi/parser/cpdf_name.h"
16 #include "core/fpdfapi/parser/cpdf_number.h"
17 #include "core/fpdfapi/parser/cpdf_reference.h"
18 #include "core/fpdfapi/parser/cpdf_stream.h"
19 #include "core/fpdfapi/parser/cpdf_string.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "testing/test_support.h"
22 
TEST(CPDFFileSpecTest,EncodeDecodeFileName)23 TEST(CPDFFileSpecTest, EncodeDecodeFileName) {
24   static const std::vector<pdfium::NullTermWstrFuncTestData> test_data = {
25     // Empty src string.
26     {L"", L""},
27     // only file name.
28     {L"test.pdf", L"test.pdf"},
29 #if BUILDFLAG(IS_WIN)
30     // With drive identifier.
31     {L"r:\\pdfdocs\\spec.pdf", L"/r/pdfdocs/spec.pdf"},
32     // Relative path.
33     {L"My Document\\test.pdf", L"My Document/test.pdf"},
34     // Absolute path without drive identifier.
35     {L"\\pdfdocs\\spec.pdf", L"//pdfdocs/spec.pdf"},
36     // Absolute path with double backslashes.
37     {L"\\\\pdfdocs\\spec.pdf", L"/pdfdocs/spec.pdf"},
38 // Network resource name. It is not supported yet.
39 // {L"pclib/eng:\\pdfdocs\\spec.pdf", L"/pclib/eng/pdfdocs/spec.pdf"},
40 #elif BUILDFLAG(IS_APPLE)
41     // Absolute path with colon separator.
42     {L"Mac HD:PDFDocs:spec.pdf", L"/Mac HD/PDFDocs/spec.pdf"},
43     // Relative path with colon separator.
44     {L"PDFDocs:spec.pdf", L"PDFDocs/spec.pdf"},
45 #else
46     // Relative path.
47     {L"./docs/test.pdf", L"./docs/test.pdf"},
48     // Relative path with parent dir.
49     {L"../test_docs/test.pdf", L"../test_docs/test.pdf"},
50     // Absolute path.
51     {L"/usr/local/home/test.pdf", L"/usr/local/home/test.pdf"},
52 #endif
53   };
54   for (const auto& data : test_data) {
55     EXPECT_EQ(data.expected, CPDF_FileSpec::EncodeFileName(data.input));
56     // DecodeFileName is the reverse procedure of EncodeFileName.
57     EXPECT_EQ(data.input, CPDF_FileSpec::DecodeFileName(data.expected));
58   }
59 }
60 
TEST(CPDFFileSpecTest,GetFileName)61 TEST(CPDFFileSpecTest, GetFileName) {
62   {
63     // String object.
64     static const pdfium::NullTermWstrFuncTestData test_data = {
65 #if BUILDFLAG(IS_WIN)
66       L"/C/docs/test.pdf",
67       L"C:\\docs\\test.pdf"
68 #elif BUILDFLAG(IS_APPLE)
69       L"/Mac HD/docs/test.pdf",
70       L"Mac HD:docs:test.pdf"
71 #else
72       L"/docs/test.pdf",
73       L"/docs/test.pdf"
74 #endif
75     };
76     auto str_obj = pdfium::MakeRetain<CPDF_String>(nullptr, test_data.input);
77     CPDF_FileSpec file_spec(str_obj);
78     EXPECT_EQ(test_data.expected, file_spec.GetFileName());
79   }
80   {
81     // Dictionary object.
82     static constexpr std::array<pdfium::NullTermWstrFuncTestData, 5> test_data =
83         {{
84 #if BUILDFLAG(IS_WIN)
85             {L"/C/docs/test.pdf", L"C:\\docs\\test.pdf"},
86             {L"/D/docs/test.pdf", L"D:\\docs\\test.pdf"},
87             {L"/E/docs/test.pdf", L"E:\\docs\\test.pdf"},
88             {L"/F/docs/test.pdf", L"F:\\docs\\test.pdf"},
89             {L"/G/docs/test.pdf", L"G:\\docs\\test.pdf"},
90 #elif BUILDFLAG(IS_APPLE)
91             {L"/Mac HD/docs1/test.pdf", L"Mac HD:docs1:test.pdf"},
92             {L"/Mac HD/docs2/test.pdf", L"Mac HD:docs2:test.pdf"},
93             {L"/Mac HD/docs3/test.pdf", L"Mac HD:docs3:test.pdf"},
94             {L"/Mac HD/docs4/test.pdf", L"Mac HD:docs4:test.pdf"},
95             {L"/Mac HD/docs5/test.pdf", L"Mac HD:docs5:test.pdf"},
96 #else
97             {L"/docs/a/test.pdf", L"/docs/a/test.pdf"},
98             {L"/docs/b/test.pdf", L"/docs/b/test.pdf"},
99             {L"/docs/c/test.pdf", L"/docs/c/test.pdf"},
100             {L"/docs/d/test.pdf", L"/docs/d/test.pdf"},
101             {L"/docs/e/test.pdf", L"/docs/e/test.pdf"},
102 #endif
103         }};
104     // Keyword fields in reverse order of precedence to retrieve the file name.
105     constexpr std::array<const char*, 5> keywords = {
106         {"Unix", "Mac", "DOS", "F", "UF"}};
107     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
108     CPDF_FileSpec file_spec(dict_obj);
109     EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
110     for (size_t i = 0; i < std::size(keywords); ++i) {
111       dict_obj->SetNewFor<CPDF_String>(keywords[i], test_data[i].input);
112       EXPECT_EQ(test_data[i].expected, file_spec.GetFileName());
113     }
114 
115     // With all the former fields and 'FS' field suggests 'URL' type.
116     dict_obj->SetNewFor<CPDF_String>("FS", "URL");
117     // Url string is not decoded.
118     EXPECT_EQ(test_data[4].input, file_spec.GetFileName());
119   }
120   {
121     // Invalid object.
122     auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
123     CPDF_FileSpec file_spec(name_obj);
124     EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
125   }
126   {
127     // Invalid CPDF_Name objects in dictionary. See https://crbug.com/959183
128     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
129     CPDF_FileSpec file_spec(dict_obj);
130     for (const char* key : {"Unix", "Mac", "DOS", "F", "UF"}) {
131       dict_obj->SetNewFor<CPDF_Name>(key, "http://evil.org");
132       EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
133     }
134     dict_obj->SetNewFor<CPDF_String>("FS", "URL");
135     EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
136   }
137 }
138 
TEST(CPDFFileSpecTest,GetFileStream)139 TEST(CPDFFileSpecTest, GetFileStream) {
140   {
141     // Invalid object.
142     auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
143     CPDF_FileSpec file_spec(name_obj);
144     EXPECT_FALSE(file_spec.GetFileStream());
145   }
146   {
147     // Dictionary object missing its embedded files dictionary.
148     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
149     CPDF_FileSpec file_spec(dict_obj);
150     EXPECT_FALSE(file_spec.GetFileStream());
151   }
152   {
153     // Dictionary object with an empty embedded files dictionary.
154     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
155     dict_obj->SetNewFor<CPDF_Dictionary>("EF");
156     CPDF_FileSpec file_spec(dict_obj);
157     EXPECT_FALSE(file_spec.GetFileStream());
158   }
159   {
160     CPDF_IndirectObjectHolder object_holder;
161     // Dictionary object with a non-empty embedded files dictionary.
162     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
163     dict_obj->SetNewFor<CPDF_Dictionary>("EF");
164     CPDF_FileSpec file_spec(dict_obj);
165 
166     const wchar_t file_name[] = L"test.pdf";
167     constexpr std::array<const char*, 5> keys = {
168         {"Unix", "Mac", "DOS", "F", "UF"}};
169     constexpr std::array<const char*, 5> streams = {
170         {"test1", "test2", "test3", "test4", "test5"}};
171     static_assert(std::size(keys) == std::size(streams), "size mismatch");
172     RetainPtr<CPDF_Dictionary> file_dict = dict_obj->GetMutableDictFor("EF");
173 
174     // Keys in reverse order of precedence to retrieve the file content stream.
175     for (size_t i = 0; i < std::size(keys); ++i) {
176       // Set the file name.
177       dict_obj->SetNewFor<CPDF_String>(keys[i], file_name);
178 
179       // Set the file stream.
180       auto stream_object = object_holder.NewIndirect<CPDF_Stream>(
181           ByteStringView(streams[i]).unsigned_span());
182       ASSERT_TRUE(stream_object);
183       const uint32_t stream_object_number = stream_object->GetObjNum();
184       ASSERT_GT(stream_object_number, 0u);
185       file_dict->SetNewFor<CPDF_Reference>(keys[i], &object_holder,
186                                            stream_object_number);
187 
188       // Check that the file content stream is as expected.
189       EXPECT_EQ(streams[i],
190                 file_spec.GetFileStream()->GetUnicodeText().ToUTF8());
191 
192       if (i == 2) {
193         dict_obj->SetNewFor<CPDF_String>("FS", "URL");
194         EXPECT_FALSE(file_spec.GetFileStream());
195       }
196     }
197   }
198 }
199 
TEST(CPDFFileSpecTest,GetParamsDict)200 TEST(CPDFFileSpecTest, GetParamsDict) {
201   {
202     // Invalid object.
203     auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
204     CPDF_FileSpec file_spec(name_obj);
205     EXPECT_FALSE(file_spec.GetParamsDict());
206   }
207   {
208     CPDF_IndirectObjectHolder object_holder;
209 
210     // Dictionary object.
211     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
212     dict_obj->SetNewFor<CPDF_Dictionary>("EF");
213     dict_obj->SetNewFor<CPDF_String>("UF", L"test.pdf");
214     CPDF_FileSpec file_spec(dict_obj);
215     EXPECT_FALSE(file_spec.GetParamsDict());
216 
217     // Add a file stream to the embedded files dictionary.
218     RetainPtr<CPDF_Dictionary> file_dict = dict_obj->GetMutableDictFor("EF");
219     static constexpr char kHello[] = "hello";
220     auto stream_object =
221         object_holder.NewIndirect<CPDF_Stream>(pdfium::as_byte_span(kHello));
222     ASSERT_TRUE(stream_object);
223     const uint32_t stream_object_number = stream_object->GetObjNum();
224     ASSERT_GT(stream_object_number, 0u);
225     file_dict->SetNewFor<CPDF_Reference>("UF", &object_holder,
226                                          stream_object_number);
227 
228     // Add a params dictionary to the file stream.
229     RetainPtr<CPDF_Stream> stream = file_dict->GetMutableStreamFor("UF");
230     RetainPtr<CPDF_Dictionary> stream_dict = stream->GetMutableDict();
231     stream_dict->SetNewFor<CPDF_Dictionary>("Params");
232     EXPECT_TRUE(file_spec.GetParamsDict());
233 
234     // Add a parameter to the params dictionary.
235     RetainPtr<CPDF_Dictionary> params_dict =
236         stream_dict->GetMutableDictFor("Params");
237     params_dict->SetNewFor<CPDF_Number>("Size", 6);
238     EXPECT_EQ(6, file_spec.GetParamsDict()->GetIntegerFor("Size"));
239   }
240 }
241