1 // Copyright 2016 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 <memory>
6 #include <utility>
7 #include <vector>
8 
9 #include "build/build_config.h"
10 #include "core/fpdfapi/parser/cpdf_dictionary.h"
11 #include "core/fpdfapi/parser/cpdf_name.h"
12 #include "core/fpdfapi/parser/cpdf_number.h"
13 #include "core/fpdfapi/parser/cpdf_stream.h"
14 #include "core/fpdfapi/parser/cpdf_string.h"
15 #include "core/fpdfdoc/cpdf_filespec.h"
16 #include "core/fxcrt/fx_memory_wrappers.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "testing/test_support.h"
19 #include "third_party/base/stl_util.h"
20 
TEST(cpdf_filespec,EncodeDecodeFileName)21 TEST(cpdf_filespec, EncodeDecodeFileName) {
22   static const std::vector<pdfium::NullTermWstrFuncTestData> test_data = {
23     // Empty src string.
24     {L"", L""},
25     // only file name.
26     {L"test.pdf", L"test.pdf"},
27 #if defined(OS_WIN)
28     // With drive identifier.
29     {L"r:\\pdfdocs\\spec.pdf", L"/r/pdfdocs/spec.pdf"},
30     // Relative path.
31     {L"My Document\\test.pdf", L"My Document/test.pdf"},
32     // Absolute path without drive identifier.
33     {L"\\pdfdocs\\spec.pdf", L"//pdfdocs/spec.pdf"},
34     // Absolute path with double backslashes.
35     {L"\\\\pdfdocs\\spec.pdf", L"/pdfdocs/spec.pdf"},
36 // Network resource name. It is not supported yet.
37 // {L"pclib/eng:\\pdfdocs\\spec.pdf", L"/pclib/eng/pdfdocs/spec.pdf"},
38 #elif defined(OS_APPLE)
39     // Absolute path with colon separator.
40     {L"Mac HD:PDFDocs:spec.pdf", L"/Mac HD/PDFDocs/spec.pdf"},
41     // Relative path with colon separator.
42     {L"PDFDocs:spec.pdf", L"PDFDocs/spec.pdf"},
43 #else
44     // Relative path.
45     {L"./docs/test.pdf", L"./docs/test.pdf"},
46     // Relative path with parent dir.
47     {L"../test_docs/test.pdf", L"../test_docs/test.pdf"},
48     // Absolute path.
49     {L"/usr/local/home/test.pdf", L"/usr/local/home/test.pdf"},
50 #endif
51   };
52   for (const auto& data : test_data) {
53     EXPECT_STREQ(data.expected,
54                  CPDF_FileSpec::EncodeFileName(data.input).c_str());
55     // DecodeFileName is the reverse procedure of EncodeFileName.
56     EXPECT_STREQ(data.input,
57                  CPDF_FileSpec::DecodeFileName(data.expected).c_str());
58   }
59 }
60 
TEST(cpdf_filespec,GetFileName)61 TEST(cpdf_filespec, GetFileName) {
62   {
63     // String object.
64     static const pdfium::NullTermWstrFuncTestData test_data = {
65 #if defined(OS_WIN)
66       L"/C/docs/test.pdf",
67       L"C:\\docs\\test.pdf"
68 #elif defined(OS_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.Get());
78     EXPECT_STREQ(test_data.expected, file_spec.GetFileName().c_str());
79   }
80   {
81     // Dictionary object.
82     static const pdfium::NullTermWstrFuncTestData test_data[] = {
83 #if defined(OS_WIN)
84       {L"/C/docs/test.pdf", L"C:\\docs\\test.pdf"},
85       {L"/D/docs/test.pdf", L"D:\\docs\\test.pdf"},
86       {L"/E/docs/test.pdf", L"E:\\docs\\test.pdf"},
87       {L"/F/docs/test.pdf", L"F:\\docs\\test.pdf"},
88       {L"/G/docs/test.pdf", L"G:\\docs\\test.pdf"},
89 #elif defined(OS_APPLE)
90       {L"/Mac HD/docs1/test.pdf", L"Mac HD:docs1:test.pdf"},
91       {L"/Mac HD/docs2/test.pdf", L"Mac HD:docs2:test.pdf"},
92       {L"/Mac HD/docs3/test.pdf", L"Mac HD:docs3:test.pdf"},
93       {L"/Mac HD/docs4/test.pdf", L"Mac HD:docs4:test.pdf"},
94       {L"/Mac HD/docs5/test.pdf", L"Mac HD:docs5:test.pdf"},
95 #else
96       {L"/docs/a/test.pdf", L"/docs/a/test.pdf"},
97       {L"/docs/b/test.pdf", L"/docs/b/test.pdf"},
98       {L"/docs/c/test.pdf", L"/docs/c/test.pdf"},
99       {L"/docs/d/test.pdf", L"/docs/d/test.pdf"},
100       {L"/docs/e/test.pdf", L"/docs/e/test.pdf"},
101 #endif
102     };
103     // Keyword fields in reverse order of precedence to retrieve the file name.
104     const char* const keywords[] = {"Unix", "Mac", "DOS", "F", "UF"};
105     static_assert(pdfium::size(test_data) == pdfium::size(keywords),
106                   "size mismatch");
107     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
108     CPDF_FileSpec file_spec(dict_obj.Get());
109     EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
110     for (size_t i = 0; i < pdfium::size(keywords); ++i) {
111       dict_obj->SetNewFor<CPDF_String>(keywords[i], test_data[i].input);
112       EXPECT_STREQ(test_data[i].expected, file_spec.GetFileName().c_str());
113     }
114 
115     // With all the former fields and 'FS' field suggests 'URL' type.
116     dict_obj->SetNewFor<CPDF_String>("FS", "URL", false);
117     // Url string is not decoded.
118     EXPECT_STREQ(test_data[4].input, file_spec.GetFileName().c_str());
119   }
120   {
121     // Invalid object.
122     auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
123     CPDF_FileSpec file_spec(name_obj.Get());
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.Get());
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", false);
135     EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
136   }
137 }
138 
TEST(cpdf_filespec,SetFileName)139 TEST(cpdf_filespec, SetFileName) {
140   static const pdfium::NullTermWstrFuncTestData test_data = {
141 #if defined(OS_WIN)
142     L"C:\\docs\\test.pdf",
143     L"/C/docs/test.pdf"
144 #elif defined(OS_APPLE)
145     L"Mac HD:docs:test.pdf",
146     L"/Mac HD/docs/test.pdf"
147 #else
148     L"/docs/test.pdf",
149     L"/docs/test.pdf"
150 #endif
151   };
152   // String object.
153   auto str_obj = pdfium::MakeRetain<CPDF_String>(nullptr, L"babababa");
154   CPDF_FileSpec file_spec1(str_obj.Get());
155   file_spec1.SetFileName(test_data.input);
156   // Check internal object value.
157   EXPECT_STREQ(test_data.expected, str_obj->GetUnicodeText().c_str());
158   // Check we can get the file name back.
159   EXPECT_STREQ(test_data.input, file_spec1.GetFileName().c_str());
160 
161   // Dictionary object.
162   auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
163   CPDF_FileSpec file_spec2(dict_obj.Get());
164   file_spec2.SetFileName(test_data.input);
165   // Check internal object value.
166   EXPECT_STREQ(test_data.expected, dict_obj->GetUnicodeTextFor("F").c_str());
167   EXPECT_STREQ(test_data.expected, dict_obj->GetUnicodeTextFor("UF").c_str());
168   // Check we can get the file name back.
169   EXPECT_STREQ(test_data.input, file_spec2.GetFileName().c_str());
170 }
171 
TEST(cpdf_filespec,GetFileStream)172 TEST(cpdf_filespec, GetFileStream) {
173   {
174     // Invalid object.
175     auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
176     CPDF_FileSpec file_spec(name_obj.Get());
177     EXPECT_FALSE(file_spec.GetFileStream());
178   }
179   {
180     // Dictionary object missing its embedded files dictionary.
181     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
182     CPDF_FileSpec file_spec(dict_obj.Get());
183     EXPECT_FALSE(file_spec.GetFileStream());
184   }
185   {
186     // Dictionary object with an empty embedded files dictionary.
187     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
188     dict_obj->SetNewFor<CPDF_Dictionary>("EF");
189     CPDF_FileSpec file_spec(dict_obj.Get());
190     EXPECT_FALSE(file_spec.GetFileStream());
191   }
192   {
193     // Dictionary object with a non-empty embedded files dictionary.
194     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
195     dict_obj->SetNewFor<CPDF_Dictionary>("EF");
196     CPDF_FileSpec file_spec(dict_obj.Get());
197 
198     const wchar_t file_name[] = L"test.pdf";
199     const char* const keys[] = {"Unix", "Mac", "DOS", "F", "UF"};
200     const char* const streams[] = {"test1", "test2", "test3", "test4", "test5"};
201     static_assert(pdfium::size(keys) == pdfium::size(streams), "size mismatch");
202     CPDF_Dictionary* file_dict =
203         file_spec.GetObj()->AsDictionary()->GetDictFor("EF");
204 
205     // Keys in reverse order of precedence to retrieve the file content stream.
206     for (size_t i = 0; i < pdfium::size(keys); ++i) {
207       // Set the file name.
208       dict_obj->SetNewFor<CPDF_String>(keys[i], file_name);
209 
210       // Set the file stream.
211       auto pDict = pdfium::MakeRetain<CPDF_Dictionary>();
212       size_t buf_len = strlen(streams[i]) + 1;
213       std::unique_ptr<uint8_t, FxFreeDeleter> buf(
214           FX_AllocUninit(uint8_t, buf_len));
215       memcpy(buf.get(), streams[i], buf_len);
216       file_dict->SetNewFor<CPDF_Stream>(keys[i], std::move(buf), buf_len,
217                                         std::move(pDict));
218 
219       // Check that the file content stream is as expected.
220       EXPECT_STREQ(
221           streams[i],
222           file_spec.GetFileStream()->GetUnicodeText().ToUTF8().c_str());
223 
224       if (i == 2) {
225         dict_obj->SetNewFor<CPDF_String>("FS", "URL", false);
226         EXPECT_FALSE(file_spec.GetFileStream());
227       }
228     }
229   }
230 }
231 
TEST(cpdf_filespec,GetParamsDict)232 TEST(cpdf_filespec, GetParamsDict) {
233   {
234     // Invalid object.
235     auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
236     CPDF_FileSpec file_spec(name_obj.Get());
237     EXPECT_FALSE(file_spec.GetParamsDict());
238   }
239   {
240     // Dictionary object.
241     auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
242     dict_obj->SetNewFor<CPDF_Dictionary>("EF");
243     dict_obj->SetNewFor<CPDF_String>("UF", L"test.pdf");
244     CPDF_FileSpec file_spec(dict_obj.Get());
245     EXPECT_FALSE(file_spec.GetParamsDict());
246 
247     // Add a file stream to the embedded files dictionary.
248     CPDF_Dictionary* file_dict =
249         file_spec.GetObj()->AsDictionary()->GetDictFor("EF");
250     auto pDict = pdfium::MakeRetain<CPDF_Dictionary>();
251     std::unique_ptr<uint8_t, FxFreeDeleter> buf(FX_AllocUninit(uint8_t, 6));
252     memcpy(buf.get(), "hello", 6);
253     file_dict->SetNewFor<CPDF_Stream>("UF", std::move(buf), 6,
254                                       std::move(pDict));
255 
256     // Add a params dictionary to the file stream.
257     CPDF_Stream* stream = file_dict->GetStreamFor("UF");
258     CPDF_Dictionary* stream_dict = stream->GetDict();
259     stream_dict->SetNewFor<CPDF_Dictionary>("Params");
260     EXPECT_TRUE(file_spec.GetParamsDict());
261 
262     // Add a parameter to the params dictionary.
263     CPDF_Dictionary* params_dict = stream_dict->GetDictFor("Params");
264     params_dict->SetNewFor<CPDF_Number>("Size", 6);
265     EXPECT_EQ(6, file_spec.GetParamsDict()->GetIntegerFor("Size"));
266   }
267 }
268