| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #include "base/android/build_info.h" |
| #include "base/files/file.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "content/browser/font_unique_name_lookup/font_unique_name_lookup.h" |
| #include "third_party/blink/public/common/font_unique_name_lookup/font_table_matcher.h" |
| |
| #include <functional> |
| #include <memory> |
| |
| namespace { |
| |
| static const char* const kAndroidFontPaths[] = {"/system/fonts", |
| "/vendor/fonts"}; |
| |
| // Full font name, postscript name, filename. |
| static const char* const kRobotoCondensedBoldItalicNames[] = { |
| "Roboto Condensed Bold Italic", "RobotoCondensed-BoldItalic", |
| "RobotoCondensed-BoldItalic.ttf"}; |
| |
| std::vector<std::string> AndroidFontFilesList() { |
| std::vector<std::string> font_files; |
| for (const char* font_dir_path : kAndroidFontPaths) { |
| base::FileEnumerator files_enumerator( |
| base::MakeAbsoluteFilePath(base::FilePath(font_dir_path)), true, |
| base::FileEnumerator::FILES); |
| for (base::FilePath name = files_enumerator.Next(); !name.empty(); |
| name = files_enumerator.Next()) { |
| if (name.Extension() == ".ttf" || name.Extension() == ".ttc" || |
| name.Extension() == ".otf") { |
| font_files.push_back(name.value()); |
| } |
| } |
| } |
| return font_files; |
| } |
| |
| std::vector<std::string> SplitFontFilesList( |
| const std::vector<std::string> font_files, |
| bool return_second_half) { |
| CHECK_GT(font_files.size(), 2u); |
| auto start_copy = font_files.begin(); |
| auto end_copy = font_files.begin() + (font_files.size() / 2); |
| if (return_second_half) { |
| start_copy = end_copy; |
| end_copy = font_files.end(); |
| } |
| return std::vector<std::string>(start_copy, end_copy); |
| } |
| |
| enum class TruncateLength { TruncateToZero, TruncateHalf }; |
| |
| void TruncateFile(const base::FilePath& file_path, |
| TruncateLength truncate_length) { |
| base::File file_to_truncate( |
| file_path, base::File::FLAG_OPEN | base::File::Flags::FLAG_WRITE); |
| size_t truncate_to = truncate_length == TruncateLength::TruncateHalf |
| ? file_to_truncate.GetLength() / 2 |
| : 0; |
| file_to_truncate.SetLength(truncate_to); |
| } |
| |
| } // namespace |
| |
| namespace content { |
| |
| class FontUniqueNameLookupTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| font_unique_name_lookup_ = |
| std::make_unique<FontUniqueNameLookup>(temp_dir_.GetPath()); |
| } |
| |
| base::ScopedTempDir temp_dir_; |
| std::unique_ptr<FontUniqueNameLookup> font_unique_name_lookup_; |
| }; |
| |
| TEST_F(FontUniqueNameLookupTest, TestBuildLookup) { |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| base::ReadOnlySharedMemoryMapping mapping = |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map(); |
| blink::FontTableMatcher matcher(mapping); |
| ASSERT_GT(matcher.AvailableFonts(), 0u); |
| ASSERT_TRUE(font_unique_name_lookup_->PersistToFile()); |
| ASSERT_TRUE(font_unique_name_lookup_->LoadFromFile()); |
| blink::FontTableMatcher matcher_after_load( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| ASSERT_GT(matcher_after_load.AvailableFonts(), 0u); |
| } |
| |
| // http://crbug.com/928818 |
| #if defined(ADDRESS_SANITIZER) |
| #define MAYBE_TestHandleFiledRead DISABLED_TestHandleFailedRead |
| #else |
| #define MAYBE_TestHandleFiledRead TestHandleFailedRead |
| #endif |
| TEST_F(FontUniqueNameLookupTest, MAYBE_TestHandleFiledRead) { |
| base::DeleteFile(font_unique_name_lookup_->TableCacheFilePathForTesting(), |
| false); |
| ASSERT_FALSE(font_unique_name_lookup_->LoadFromFile()); |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| base::ReadOnlySharedMemoryMapping mapping = |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map(); |
| blink::FontTableMatcher matcher(mapping); |
| ASSERT_GT(matcher.AvailableFonts(), 0u); |
| ASSERT_TRUE(font_unique_name_lookup_->PersistToFile()); |
| ASSERT_TRUE(font_unique_name_lookup_->LoadFromFile()); |
| TruncateFile(font_unique_name_lookup_->TableCacheFilePathForTesting(), |
| TruncateLength::TruncateHalf); |
| ASSERT_FALSE(font_unique_name_lookup_->LoadFromFile()); |
| TruncateFile(font_unique_name_lookup_->TableCacheFilePathForTesting(), |
| TruncateLength::TruncateToZero); |
| ASSERT_FALSE(font_unique_name_lookup_->LoadFromFile()); |
| } |
| |
| TEST_F(FontUniqueNameLookupTest, TestMatchPostScriptName) { |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| blink::FontTableMatcher matcher( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| ASSERT_GT(matcher.AvailableFonts(), 0u); |
| auto match_result = matcher.MatchName(kRobotoCondensedBoldItalicNames[1]); |
| ASSERT_TRUE(match_result); |
| ASSERT_TRUE(EndsWith(match_result->font_path, |
| kRobotoCondensedBoldItalicNames[2], |
| base::CompareCase::SENSITIVE)); |
| base::File found_file(base::FilePath(match_result->font_path), |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(found_file.IsValid()); |
| ASSERT_EQ(match_result->ttc_index, 0u); |
| } |
| |
| TEST_F(FontUniqueNameLookupTest, TestMatchPostScriptNameTtc) { |
| if (base::android::BuildInfo::GetInstance()->sdk_int() < |
| base::android::SdkVersion::SDK_VERSION_NOUGAT) { |
| // Pre-Nougat Android does not contain any .ttc files as system fonts. |
| return; |
| } |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| blink::FontTableMatcher matcher( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| std::vector<std::string> ttc_postscript_names = { |
| "NotoSansCJKjp-Regular", "NotoSansCJKkr-Regular", |
| "NotoSansCJKsc-Regular", "NotoSansCJKtc-Regular", |
| "NotoSansMonoCJKjp-Regular", "NotoSansMonoCJKkr-Regular", |
| "NotoSansMonoCJKsc-Regular", "NotoSansMonoCJKtc-Regular", |
| }; |
| for (size_t i = 0; i < ttc_postscript_names.size(); ++i) { |
| auto match_result = matcher.MatchName(ttc_postscript_names[i]); |
| ASSERT_TRUE(match_result); |
| ASSERT_TRUE(EndsWith(match_result->font_path, "NotoSansCJK-Regular.ttc", |
| base::CompareCase::SENSITIVE)); |
| base::File found_file(base::FilePath(match_result->font_path), |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(found_file.IsValid()); |
| ASSERT_EQ(match_result->ttc_index, i); |
| } |
| } |
| |
| TEST_F(FontUniqueNameLookupTest, TestMatchFullFontName) { |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| blink::FontTableMatcher matcher( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| auto match_result = matcher.MatchName(kRobotoCondensedBoldItalicNames[0]); |
| ASSERT_TRUE(match_result); |
| ASSERT_TRUE(EndsWith(match_result->font_path, |
| kRobotoCondensedBoldItalicNames[2], |
| base::CompareCase::SENSITIVE)); |
| base::File found_file(base::FilePath(match_result->font_path), |
| base::File::FLAG_OPEN | base::File::Flags::FLAG_READ); |
| ASSERT_TRUE(found_file.IsValid()); |
| ASSERT_EQ(match_result->ttc_index, 0u); |
| } |
| |
| namespace { |
| size_t GetNumTables(base::File& font_file) { |
| font_file.Seek(base::File::FROM_BEGIN, 5); |
| uint8_t num_tables_bytes[2] = {}; |
| font_file.ReadAtCurrentPos(reinterpret_cast<char*>(num_tables_bytes), |
| base::size(num_tables_bytes)); |
| uint16_t num_tables = |
| static_cast<uint16_t>(num_tables_bytes[0] + (num_tables_bytes[1] << 8)); |
| return num_tables; |
| } |
| |
| const size_t kOffsetTableRecords = 13; |
| const size_t kSizeOneTableRecord = 16; |
| |
| } // namespace |
| |
| // Creates a temp directory and copies Android font files to this |
| // directory. Provides two methods to inject faults into the font files 1) |
| // ZeroOutTableRecords writes a sequence of 0 to where the font table offset |
| // should be stored in the font file. 2) ZeroAfterTableIndex writes 0 from after |
| // the table records until the end of the file. |
| class FontFileCorruptor { |
| public: |
| FontFileCorruptor() { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| CopyPlatformFilesToTempDir(); |
| } |
| |
| // Overwrite the list of table records with 0. |
| void ZeroOutTableRecords() { |
| ForEachCopiedFontFile([](base::File& font_file) { |
| // Read number of font tables, then zero out the table record structure. |
| // https://docs.microsoft.com/en-us/typography/opentype/spec/font-file |
| size_t num_tables = GetNumTables(font_file); |
| CHECK_GT(num_tables, 0u); |
| char garbage[kSizeOneTableRecord] = {0}; |
| for (size_t i = 0; i < num_tables; ++i) { |
| CHECK_EQ(static_cast<int>(kSizeOneTableRecord), |
| font_file.Write(kOffsetTableRecords + i * kSizeOneTableRecord, |
| garbage, base::size(garbage))); |
| } |
| }); |
| } |
| |
| // Overwrite the data in the font file with zeroes from after the table |
| // records until the end of the file. |
| void ZeroAfterTableIndex() { |
| ForEachCopiedFontFile([](base::File& font_file) { |
| size_t num_tables = GetNumTables(font_file); |
| CHECK_GT(num_tables, 0u); |
| const size_t offset_after_table_records = |
| kOffsetTableRecords + num_tables * kSizeOneTableRecord; |
| std::vector<char> zeroes; |
| zeroes.resize(font_file.GetLength() - offset_after_table_records); |
| std::fill(zeroes.begin(), zeroes.end(), 0); |
| CHECK_EQ(static_cast<int>(zeroes.size()), |
| font_file.Write(offset_after_table_records, zeroes.data(), |
| zeroes.size())); |
| }); |
| } |
| |
| // Get the list of filenames copied to the temporary directory. |
| std::vector<std::string> GetFontFilesList() { return copied_files_; } |
| |
| private: |
| void ForEachCopiedFontFile(std::function<void(base::File&)> manipulate_file) { |
| for (const auto& filename : copied_files_) { |
| base::File font_file(base::FilePath(filename), |
| base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_WRITE); |
| manipulate_file(font_file); |
| } |
| } |
| |
| void CopyPlatformFilesToTempDir() { |
| std::vector<std::string> platform_files = AndroidFontFilesList(); |
| for (auto& font_file : platform_files) { |
| base::FilePath source_path(font_file); |
| base::FilePath destination_path(temp_dir_.GetPath()); |
| destination_path = destination_path.Append(source_path.BaseName()); |
| if (base::CopyFile(source_path, destination_path)) |
| copied_files_.push_back(destination_path.value()); |
| } |
| } |
| base::ScopedTempDir temp_dir_; |
| std::vector<std::string> copied_files_; |
| }; |
| |
| class FaultInjectingFontUniqueNameLookupTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| font_unique_name_lookup_ = |
| std::make_unique<FontUniqueNameLookup>(temp_dir_.GetPath()); |
| font_unique_name_lookup_->SetFontFilePathsForTesting( |
| font_file_corruptor_.GetFontFilesList()); |
| } |
| |
| base::ScopedTempDir temp_dir_; |
| FontFileCorruptor font_file_corruptor_; |
| std::unique_ptr<FontUniqueNameLookup> font_unique_name_lookup_; |
| }; |
| |
| TEST_F(FaultInjectingFontUniqueNameLookupTest, TestZeroedTableContents) { |
| font_file_corruptor_.ZeroAfterTableIndex(); |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| blink::FontTableMatcher matcher_after_update( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| ASSERT_EQ(matcher_after_update.AvailableFonts(), 0u); |
| } |
| |
| TEST_F(FaultInjectingFontUniqueNameLookupTest, TestZeroedTableIndex) { |
| font_file_corruptor_.ZeroOutTableRecords(); |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| blink::FontTableMatcher matcher_after_update( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| ASSERT_EQ(matcher_after_update.AvailableFonts(), 0u); |
| } |
| |
| class FontUniqueNameLookupUpdateTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| ASSERT_TRUE(lookup_table_storage_dir.CreateUniqueTempDir()); |
| font_unique_name_lookup_ = std::make_unique<FontUniqueNameLookup>( |
| lookup_table_storage_dir.GetPath()); |
| font_unique_name_lookup_->SetFontFilePathsForTesting( |
| SplitFontFilesList(AndroidFontFilesList(), false)); |
| font_unique_name_lookup_->SetAndroidBuildFingerprintForTesting("A"); |
| } |
| |
| base::ScopedTempDir lookup_table_storage_dir; |
| std::unique_ptr<FontUniqueNameLookup> font_unique_name_lookup_; |
| }; |
| |
| TEST_F(FontUniqueNameLookupUpdateTest, CompareSets) { |
| ASSERT_TRUE(font_unique_name_lookup_->UpdateTable()); |
| blink::FontTableMatcher matcher_initial( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| ASSERT_GT(matcher_initial.AvailableFonts(), 0u); |
| font_unique_name_lookup_->SetFontFilePathsForTesting( |
| SplitFontFilesList(AndroidFontFilesList(), true)); |
| // Set the Android build fingerprint to something different from what it's set |
| // to in the test's SetUp method to trigger re-indexing. |
| font_unique_name_lookup_->SetAndroidBuildFingerprintForTesting("B"); |
| font_unique_name_lookup_->UpdateTableIfNeeded(); |
| blink::FontTableMatcher matcher_second_half( |
| font_unique_name_lookup_->DuplicateMemoryRegion().Map()); |
| ASSERT_GT(matcher_initial.AvailableFonts(), 0u); |
| ASSERT_TRUE(matcher_initial.FontListIsDisjointFrom(matcher_second_half)); |
| } |
| |
| } // namespace content |