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 <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "build/build_config.h"
12 #include "core/fpdfapi/parser/cpdf_dictionary.h"
13 #include "core/fpdfapi/parser/cpdf_name.h"
14 #include "core/fpdfapi/parser/cpdf_number.h"
15 #include "core/fpdfapi/parser/cpdf_stream.h"
16 #include "core/fpdfapi/parser/cpdf_string.h"
17 #include "core/fxcrt/data_vector.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "testing/test_support.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 BUILDFLAG(IS_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 BUILDFLAG(IS_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 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_STREQ(test_data.expected, file_spec.GetFileName().c_str());
79 }
80 {
81 // Dictionary object.
82 static const pdfium::NullTermWstrFuncTestData test_data[] = {
83 #if BUILDFLAG(IS_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 BUILDFLAG(IS_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(std::size(test_data) == std::size(keywords), "size mismatch");
106 auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
107 CPDF_FileSpec file_spec(dict_obj);
108 EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
109 for (size_t i = 0; i < std::size(keywords); ++i) {
110 dict_obj->SetNewFor<CPDF_String>(keywords[i], test_data[i].input);
111 EXPECT_STREQ(test_data[i].expected, file_spec.GetFileName().c_str());
112 }
113
114 // With all the former fields and 'FS' field suggests 'URL' type.
115 dict_obj->SetNewFor<CPDF_String>("FS", "URL", false);
116 // Url string is not decoded.
117 EXPECT_STREQ(test_data[4].input, file_spec.GetFileName().c_str());
118 }
119 {
120 // Invalid object.
121 auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
122 CPDF_FileSpec file_spec(name_obj);
123 EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
124 }
125 {
126 // Invalid CPDF_Name objects in dictionary. See https://crbug.com/959183
127 auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
128 CPDF_FileSpec file_spec(dict_obj);
129 for (const char* key : {"Unix", "Mac", "DOS", "F", "UF"}) {
130 dict_obj->SetNewFor<CPDF_Name>(key, "http://evil.org");
131 EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
132 }
133 dict_obj->SetNewFor<CPDF_String>("FS", "URL", false);
134 EXPECT_TRUE(file_spec.GetFileName().IsEmpty());
135 }
136 }
137
TEST(cpdf_filespec,GetFileStream)138 TEST(cpdf_filespec, GetFileStream) {
139 {
140 // Invalid object.
141 auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
142 CPDF_FileSpec file_spec(name_obj);
143 EXPECT_FALSE(file_spec.GetFileStream());
144 }
145 {
146 // Dictionary object missing its embedded files dictionary.
147 auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
148 CPDF_FileSpec file_spec(dict_obj);
149 EXPECT_FALSE(file_spec.GetFileStream());
150 }
151 {
152 // Dictionary object with an empty embedded files dictionary.
153 auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
154 dict_obj->SetNewFor<CPDF_Dictionary>("EF");
155 CPDF_FileSpec file_spec(dict_obj);
156 EXPECT_FALSE(file_spec.GetFileStream());
157 }
158 {
159 // Dictionary object with a non-empty embedded files dictionary.
160 auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
161 dict_obj->SetNewFor<CPDF_Dictionary>("EF");
162 CPDF_FileSpec file_spec(dict_obj);
163
164 const wchar_t file_name[] = L"test.pdf";
165 const char* const keys[] = {"Unix", "Mac", "DOS", "F", "UF"};
166 const char* const streams[] = {"test1", "test2", "test3", "test4", "test5"};
167 static_assert(std::size(keys) == std::size(streams), "size mismatch");
168 RetainPtr<CPDF_Dictionary> file_dict = dict_obj->GetMutableDictFor("EF");
169
170 // Keys in reverse order of precedence to retrieve the file content stream.
171 for (size_t i = 0; i < std::size(keys); ++i) {
172 // Set the file name.
173 dict_obj->SetNewFor<CPDF_String>(keys[i], file_name);
174
175 // Set the file stream.
176 auto pDict = pdfium::MakeRetain<CPDF_Dictionary>();
177 size_t buf_len = strlen(streams[i]) + 1;
178 file_dict->SetNewFor<CPDF_Stream>(
179 keys[i], DataVector<uint8_t>(streams[i], streams[i] + buf_len),
180 std::move(pDict));
181
182 // Check that the file content stream is as expected.
183 EXPECT_STREQ(
184 streams[i],
185 file_spec.GetFileStream()->GetUnicodeText().ToUTF8().c_str());
186
187 if (i == 2) {
188 dict_obj->SetNewFor<CPDF_String>("FS", "URL", false);
189 EXPECT_FALSE(file_spec.GetFileStream());
190 }
191 }
192 }
193 }
194
TEST(cpdf_filespec,GetParamsDict)195 TEST(cpdf_filespec, GetParamsDict) {
196 {
197 // Invalid object.
198 auto name_obj = pdfium::MakeRetain<CPDF_Name>(nullptr, "test.pdf");
199 CPDF_FileSpec file_spec(name_obj);
200 EXPECT_FALSE(file_spec.GetParamsDict());
201 }
202 {
203 // Dictionary object.
204 auto dict_obj = pdfium::MakeRetain<CPDF_Dictionary>();
205 dict_obj->SetNewFor<CPDF_Dictionary>("EF");
206 dict_obj->SetNewFor<CPDF_String>("UF", L"test.pdf");
207 CPDF_FileSpec file_spec(dict_obj);
208 EXPECT_FALSE(file_spec.GetParamsDict());
209
210 // Add a file stream to the embedded files dictionary.
211 RetainPtr<CPDF_Dictionary> file_dict = dict_obj->GetMutableDictFor("EF");
212 auto pDict = pdfium::MakeRetain<CPDF_Dictionary>();
213 static constexpr char kHello[] = "hello";
214 file_dict->SetNewFor<CPDF_Stream>(
215 "UF", DataVector<uint8_t>(std::begin(kHello), std::end(kHello)),
216 std::move(pDict));
217
218 // Add a params dictionary to the file stream.
219 RetainPtr<CPDF_Stream> stream = file_dict->GetMutableStreamFor("UF");
220 RetainPtr<CPDF_Dictionary> stream_dict = stream->GetMutableDict();
221 stream_dict->SetNewFor<CPDF_Dictionary>("Params");
222 EXPECT_TRUE(file_spec.GetParamsDict());
223
224 // Add a parameter to the params dictionary.
225 RetainPtr<CPDF_Dictionary> params_dict =
226 stream_dict->GetMutableDictFor("Params");
227 params_dict->SetNewFor<CPDF_Number>("Size", 6);
228 EXPECT_EQ(6, file_spec.GetParamsDict()->GetIntegerFor("Size"));
229 }
230 }
231