// Copyright 2016 The PDFium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "core/fpdfdoc/cpdf_filespec.h" #include #include #include #include #include "build/build_config.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_indirect_object_holder.h" #include "core/fpdfapi/parser/cpdf_name.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_reference.h" #include "core/fpdfapi/parser/cpdf_stream.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/test_support.h" TEST(CPDFFileSpecTest, EncodeDecodeFileName) { static const std::vector test_data = { // Empty src string. {L"", L""}, // only file name. {L"test.pdf", L"test.pdf"}, #if BUILDFLAG(IS_WIN) // With drive identifier. {L"r:\\pdfdocs\\spec.pdf", L"/r/pdfdocs/spec.pdf"}, // Relative path. {L"My Document\\test.pdf", L"My Document/test.pdf"}, // Absolute path without drive identifier. {L"\\pdfdocs\\spec.pdf", L"//pdfdocs/spec.pdf"}, // Absolute path with double backslashes. {L"\\\\pdfdocs\\spec.pdf", L"/pdfdocs/spec.pdf"}, // Network resource name. It is not supported yet. // {L"pclib/eng:\\pdfdocs\\spec.pdf", L"/pclib/eng/pdfdocs/spec.pdf"}, #elif BUILDFLAG(IS_APPLE) // Absolute path with colon separator. {L"Mac HD:PDFDocs:spec.pdf", L"/Mac HD/PDFDocs/spec.pdf"}, // Relative path with colon separator. {L"PDFDocs:spec.pdf", L"PDFDocs/spec.pdf"}, #else // Relative path. {L"./docs/test.pdf", L"./docs/test.pdf"}, // Relative path with parent dir. {L"../test_docs/test.pdf", L"../test_docs/test.pdf"}, // Absolute path. {L"/usr/local/home/test.pdf", L"/usr/local/home/test.pdf"}, #endif }; for (const auto& data : test_data) { EXPECT_EQ(data.expected, CPDF_FileSpec::EncodeFileName(data.input)); // DecodeFileName is the reverse procedure of EncodeFileName. EXPECT_EQ(data.input, CPDF_FileSpec::DecodeFileName(data.expected)); } } TEST(CPDFFileSpecTest, GetFileName) { { // String object. static const pdfium::NullTermWstrFuncTestData test_data = { #if BUILDFLAG(IS_WIN) L"/C/docs/test.pdf", L"C:\\docs\\test.pdf" #elif BUILDFLAG(IS_APPLE) L"/Mac HD/docs/test.pdf", L"Mac HD:docs:test.pdf" #else L"/docs/test.pdf", L"/docs/test.pdf" #endif }; auto str_obj = pdfium::MakeRetain(nullptr, test_data.input); CPDF_FileSpec file_spec(str_obj); EXPECT_EQ(test_data.expected, file_spec.GetFileName()); } { // Dictionary object. static constexpr std::array test_data = {{ #if BUILDFLAG(IS_WIN) {L"/C/docs/test.pdf", L"C:\\docs\\test.pdf"}, {L"/D/docs/test.pdf", L"D:\\docs\\test.pdf"}, {L"/E/docs/test.pdf", L"E:\\docs\\test.pdf"}, {L"/F/docs/test.pdf", L"F:\\docs\\test.pdf"}, {L"/G/docs/test.pdf", L"G:\\docs\\test.pdf"}, #elif BUILDFLAG(IS_APPLE) {L"/Mac HD/docs1/test.pdf", L"Mac HD:docs1:test.pdf"}, {L"/Mac HD/docs2/test.pdf", L"Mac HD:docs2:test.pdf"}, {L"/Mac HD/docs3/test.pdf", L"Mac HD:docs3:test.pdf"}, {L"/Mac HD/docs4/test.pdf", L"Mac HD:docs4:test.pdf"}, {L"/Mac HD/docs5/test.pdf", L"Mac HD:docs5:test.pdf"}, #else {L"/docs/a/test.pdf", L"/docs/a/test.pdf"}, {L"/docs/b/test.pdf", L"/docs/b/test.pdf"}, {L"/docs/c/test.pdf", L"/docs/c/test.pdf"}, {L"/docs/d/test.pdf", L"/docs/d/test.pdf"}, {L"/docs/e/test.pdf", L"/docs/e/test.pdf"}, #endif }}; // Keyword fields in reverse order of precedence to retrieve the file name. constexpr std::array keywords = { {"Unix", "Mac", "DOS", "F", "UF"}}; auto dict_obj = pdfium::MakeRetain(); CPDF_FileSpec file_spec(dict_obj); EXPECT_TRUE(file_spec.GetFileName().IsEmpty()); for (size_t i = 0; i < std::size(keywords); ++i) { dict_obj->SetNewFor(keywords[i], test_data[i].input); EXPECT_EQ(test_data[i].expected, file_spec.GetFileName()); } // With all the former fields and 'FS' field suggests 'URL' type. dict_obj->SetNewFor("FS", "URL"); // Url string is not decoded. EXPECT_EQ(test_data[4].input, file_spec.GetFileName()); } { // Invalid object. auto name_obj = pdfium::MakeRetain(nullptr, "test.pdf"); CPDF_FileSpec file_spec(name_obj); EXPECT_TRUE(file_spec.GetFileName().IsEmpty()); } { // Invalid CPDF_Name objects in dictionary. See https://crbug.com/959183 auto dict_obj = pdfium::MakeRetain(); CPDF_FileSpec file_spec(dict_obj); for (const char* key : {"Unix", "Mac", "DOS", "F", "UF"}) { dict_obj->SetNewFor(key, "http://evil.org"); EXPECT_TRUE(file_spec.GetFileName().IsEmpty()); } dict_obj->SetNewFor("FS", "URL"); EXPECT_TRUE(file_spec.GetFileName().IsEmpty()); } } TEST(CPDFFileSpecTest, GetFileStream) { { // Invalid object. auto name_obj = pdfium::MakeRetain(nullptr, "test.pdf"); CPDF_FileSpec file_spec(name_obj); EXPECT_FALSE(file_spec.GetFileStream()); } { // Dictionary object missing its embedded files dictionary. auto dict_obj = pdfium::MakeRetain(); CPDF_FileSpec file_spec(dict_obj); EXPECT_FALSE(file_spec.GetFileStream()); } { // Dictionary object with an empty embedded files dictionary. auto dict_obj = pdfium::MakeRetain(); dict_obj->SetNewFor("EF"); CPDF_FileSpec file_spec(dict_obj); EXPECT_FALSE(file_spec.GetFileStream()); } { CPDF_IndirectObjectHolder object_holder; // Dictionary object with a non-empty embedded files dictionary. auto dict_obj = pdfium::MakeRetain(); dict_obj->SetNewFor("EF"); CPDF_FileSpec file_spec(dict_obj); const wchar_t file_name[] = L"test.pdf"; constexpr std::array keys = { {"Unix", "Mac", "DOS", "F", "UF"}}; constexpr std::array streams = { {"test1", "test2", "test3", "test4", "test5"}}; static_assert(std::size(keys) == std::size(streams), "size mismatch"); RetainPtr file_dict = dict_obj->GetMutableDictFor("EF"); // Keys in reverse order of precedence to retrieve the file content stream. for (size_t i = 0; i < std::size(keys); ++i) { // Set the file name. dict_obj->SetNewFor(keys[i], file_name); // Set the file stream. auto stream_object = object_holder.NewIndirect( ByteStringView(streams[i]).unsigned_span()); ASSERT_TRUE(stream_object); const uint32_t stream_object_number = stream_object->GetObjNum(); ASSERT_GT(stream_object_number, 0u); file_dict->SetNewFor(keys[i], &object_holder, stream_object_number); // Check that the file content stream is as expected. EXPECT_EQ(streams[i], file_spec.GetFileStream()->GetUnicodeText().ToUTF8()); if (i == 2) { dict_obj->SetNewFor("FS", "URL"); EXPECT_FALSE(file_spec.GetFileStream()); } } } } TEST(CPDFFileSpecTest, GetParamsDict) { { // Invalid object. auto name_obj = pdfium::MakeRetain(nullptr, "test.pdf"); CPDF_FileSpec file_spec(name_obj); EXPECT_FALSE(file_spec.GetParamsDict()); } { CPDF_IndirectObjectHolder object_holder; // Dictionary object. auto dict_obj = pdfium::MakeRetain(); dict_obj->SetNewFor("EF"); dict_obj->SetNewFor("UF", L"test.pdf"); CPDF_FileSpec file_spec(dict_obj); EXPECT_FALSE(file_spec.GetParamsDict()); // Add a file stream to the embedded files dictionary. RetainPtr file_dict = dict_obj->GetMutableDictFor("EF"); static constexpr char kHello[] = "hello"; auto stream_object = object_holder.NewIndirect(pdfium::as_byte_span(kHello)); ASSERT_TRUE(stream_object); const uint32_t stream_object_number = stream_object->GetObjNum(); ASSERT_GT(stream_object_number, 0u); file_dict->SetNewFor("UF", &object_holder, stream_object_number); // Add a params dictionary to the file stream. RetainPtr stream = file_dict->GetMutableStreamFor("UF"); RetainPtr stream_dict = stream->GetMutableDict(); stream_dict->SetNewFor("Params"); EXPECT_TRUE(file_spec.GetParamsDict()); // Add a parameter to the params dictionary. RetainPtr params_dict = stream_dict->GetMutableDictFor("Params"); params_dict->SetNewFor("Size", 6); EXPECT_EQ(6, file_spec.GetParamsDict()->GetIntegerFor("Size")); } }