// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/nix/mime_util_xdg.h" #include #include #include #include "base/base64.h" #include "base/check.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "testing/gtest/include/gtest/gtest.h" namespace base::nix { namespace { // Test mime.cache files are generated using a process such as: // mkdir -p /tmp/mimetest/packages // cat <> /tmp/mimetest/packages/application-x-foobar.xml // // // // // // // // // // // // EOF // update-mime-database /tmp/mimetest // base64 -w72 /tmp/mimetest/mime.cache // See https://wiki.archlinux.org/title/XDG_MIME_Applications constexpr char kTestMimeCacheB64[] = "AAEAAgAAAHQAAAB4AAAAfAAAAIwAAAHMAAAB0AAAAdwAAAHgAAAB5AAAAehhcHBsaWNhdGlv" "bi9wZGYAeC9zbWlsZQB4L2lnbm9yZQAAAAB0ZXh0L3BsYWluAAB4L2ZvbwAAAH4AAAB4L25v" "LWRvdAAAAAAAAAAAAAAAAAAAAAEAAABkAAAAaAAAADIAAAAFAAAAlAAAAGMAAAABAAAA0AAA" "AGYAAAABAAAA3AAAAG8AAAABAAAA6AAAAHQAAAABAAAA9AAB+SkAAAABAAABAAAAAG8AAAAB" "AAABDAAAAGQAAAABAAABGAAAAG8AAAABAAABJAAAAHgAAAABAAABMAAB9kIAAAABAAABPAAA" "AGQAAAABAAABSAAAAHAAAAABAAABVAAAAGYAAAABAAABYAAAAHQAAAABAAABbAAAAC4AAAAB" "AAABeAAAAC4AAAABAAABhAAAAC4AAAABAAABkAAAAC4AAAADAAABnAAAAC4AAAABAAABwAAA" "AAAAAAA8AAAAMgAAAAAAAABQAAAAMgAAAAAAAAAsAAAAMgAAAAAAAABEAAAAPAAAAAAAAABc" "AAAAUAAAAAAAAABQAAAAMgAAAAAAAABQAAAAMgAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAA" "AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; class ParseMimeTypesTest : public ::testing::Test { public: ParseMimeTypesTest() { CHECK(temp_dir_.CreateUniqueTempDir()); mime_types_path_ = temp_dir_.GetPath().Append("mime.types"); } ParseMimeTypesTest(const ParseMimeTypesTest&) = delete; ParseMimeTypesTest& operator=(const ParseMimeTypesTest&) = delete; ~ParseMimeTypesTest() override = default; // Ensures that parsing fails when mime.cache file is modified such that // `buf[pos] = c`. void InvalidIf(std::vector& buf, size_t pos, uint8_t c) { ASSERT_LT(pos, buf.size()); uint8_t old_c = buf[pos]; buf[pos] = c; ASSERT_TRUE(base::WriteFile(TempFile(), buf)); MimeTypeMap map; EXPECT_FALSE(ParseMimeTypes(TempFile(), map)); buf[pos] = old_c; } const FilePath& TempFile() const { return mime_types_path_; } private: ScopedTempDir temp_dir_; FilePath mime_types_path_; }; } // namespace bool operator==(const WeightedMime& lhs, const WeightedMime& rhs) { return lhs.mime_type == rhs.mime_type && lhs.weight == rhs.weight; } TEST_F(ParseMimeTypesTest, NonExistentFileFails) { MimeTypeMap map; EXPECT_FALSE(ParseMimeTypes(FilePath("/invalid/filepath/foo"), map)); } TEST_F(ParseMimeTypesTest, ValidResult) { MimeTypeMap map; auto buf = Base64Decode(kTestMimeCacheB64); ASSERT_TRUE(buf.has_value()); ASSERT_TRUE(WriteFile(TempFile(), *buf)); EXPECT_TRUE(ParseMimeTypes(TempFile(), map)); const MimeTypeMap kExpected = { {"pdf", {"application/pdf", 50}}, {"txt", {"text/plain", 50}}, {"doc", {"text/plain", 50}}, {"foo", {"x/foo", 80}}, {"🙂🤩", {"x/smile", 50}}, }; EXPECT_EQ(map, kExpected); } TEST_F(ParseMimeTypesTest, Empty) { MimeTypeMap map; ASSERT_TRUE(WriteFile(TempFile(), "")); EXPECT_FALSE(ParseMimeTypes(TempFile(), map)); } // xxd /tmp/mimetest/mime.cache // 00000000: 0001 0002 0000 0074 0000 0078 0000 007c .......t...x...| // 00000010: 0000 008c 0000 01cc 0000 01d0 0000 01dc ................ // 00000020: 0000 01e0 0000 01e4 0000 01e8 6170 706c ............appl // 00000030: 6963 6174 696f 6e2f 7064 6600 782f 736d ication/pdf.x/sm // 00000040: 696c 6500 782f 6967 6e6f 7265 0000 0000 ile.x/ignore.... // 00000050: 7465 7874 2f70 6c61 696e 0000 782f 666f text/plain..x/fo // 00000060: 6f00 0000 7e00 0000 782f 6e6f 2d64 6f74 o...~...x/no-dot // 00000070: 0000 0000 0000 0000 0000 0000 0000 0001 ................ // 00000080: 0000 0064 0000 0068 0000 0032 0000 0005 ...d...h...2.... // 00000090: 0000 0094 0000 0063 0000 0001 0000 00d0 .......c........ // 000000a0: 0000 0066 0000 0001 0000 00dc 0000 006f ...f...........o // 000000b0: 0000 0001 0000 00e8 0000 0074 0000 0001 ...........t.... // 000000c0: 0000 00f4 0001 f929 0000 0001 0000 0100 .......)........ // 000000d0: 0000 006f 0000 0001 0000 010c 0000 0064 ...o...........d // 000000e0: 0000 0001 0000 0118 0000 006f 0000 0001 ...........o.... // 000000f0: 0000 0124 0000 0078 0000 0001 0000 0130 ...$...x.......0 // 00000100: 0001 f642 0000 0001 0000 013c 0000 0064 ...B.......<...d // 00000110: 0000 0001 0000 0148 0000 0070 0000 0001 .......H...p.... // 00000120: 0000 0154 0000 0066 0000 0001 0000 0160 ...T...f.......` // 00000130: 0000 0074 0000 0001 0000 016c 0000 002e ...t.......l.... // 00000140: 0000 0001 0000 0178 0000 002e 0000 0001 .......x........ // 00000150: 0000 0184 0000 002e 0000 0001 0000 0190 ................ // 00000160: 0000 002e 0000 0003 0000 019c 0000 002e ................ // 00000170: 0000 0001 0000 01c0 0000 0000 0000 003c ...............< // 00000180: 0000 0032 0000 0000 0000 0050 0000 0032 ...2.......P...2 // 00000190: 0000 0000 0000 002c 0000 0032 0000 0000 .......,...2.... // 000001a0: 0000 0044 0000 003c 0000 0000 0000 005c ...D...<.......\ // 000001b0: 0000 0050 0000 0000 0000 0050 0000 0032 ...P.......P...2 // 000001c0: 0000 0000 0000 0050 0000 0032 0000 0000 .......P...2.... // 000001d0: 0000 0000 0000 0000 0000 01dc 0000 0000 ................ // 000001e0: 0000 0000 0000 0000 0000 0006 0000 0000 ................ // 000001f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ // 00000200: 0000 0000 TEST_F(ParseMimeTypesTest, Invalid) { auto buf = Base64Decode(kTestMimeCacheB64); ASSERT_TRUE(buf.has_value()); // ALIAS_LIST_OFFSET is uint32 at byte 4 = 0x74. // Alias list offset inside header. InvalidIf(*buf, 7, 0xa); // Alias list offset larger than file size. InvalidIf(*buf, 6, 0xff); // Not null beore alias list. InvalidIf(*buf, 0x74 - 1, 'X'); // Misaligned offset for REVERSE_SUFFIX_TREE_OFFSET. InvalidIf(*buf, 0x13, 0x7a); // N_ROOTS > kMaxUnicode (0x10ffff). InvalidIf(*buf, 0x8d, 0x20); InvalidIf(*buf, 0xd5, 0x20); // Node C > kMaxUnicode (0x10ffff). InvalidIf(*buf, 0x95, 0x20); // Node N_CHILDREN > kMaxUnicode (0x10ffff). InvalidIf(*buf, 0x99, 0x20); // Node FIRST_CHILD_OFFSET below tree offset. InvalidIf(*buf, 0x9f, 0x10); InvalidIf(*buf, 0xdb, 0x20); // Node FIRST_CHILD_OFFSET beyond file size. InvalidIf(*buf, 0x9e, 0x20); InvalidIf(*buf, 0xda, 0x20); // Mime type offset below header. InvalidIf(*buf, 0x18b, 0x10); // Mime type offset above alias list. InvalidIf(*buf, 0x18b, 0x74); } } // namespace base::nix