1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
base / nix / mime_util_xdg_unittest.cc [blame]
// 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 <map>
#include <string>
#include <vector>
#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 <<EOF >> /tmp/mimetest/packages/application-x-foobar.xml
// <?xml version="1.0" encoding="UTF-8"?>
// <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
// <mime-type type="x/no-dot"><glob pattern="~"/></mime-type>
// <mime-type type="application/pdf"><glob pattern="*.pdf"/></mime-type>
// <mime-type type="text/plain"><glob pattern="*.txt"/></mime-type>
// <mime-type type="text/plain"><glob pattern="*.doc"/></mime-type>
// <mime-type type="x/ignore"><glob pattern="*.foo" weight="60"/></mime-type>
// <mime-type type="x/foo"><glob pattern="*.foo" weight="80"/></mime-type>
// <mime-type type="text/plain"><glob pattern="*.foo"/></mime-type>
// <mime-type type="x/smile"><glob pattern="*.🙂🤩"/></mime-type>
// </mime-info>
// 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<uint8_t>& 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