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 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 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 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 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 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