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