1*6777b538SAndroid Build Coastguard Worker // Copyright 2012 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker #include "base/nix/mime_util_xdg.h"
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker #include <arpa/inet.h>
8*6777b538SAndroid Build Coastguard Worker #include <memory>
9*6777b538SAndroid Build Coastguard Worker #include <utility>
10*6777b538SAndroid Build Coastguard Worker
11*6777b538SAndroid Build Coastguard Worker #include "base/check.h"
12*6777b538SAndroid Build Coastguard Worker #include "base/containers/stack.h"
13*6777b538SAndroid Build Coastguard Worker #include "base/environment.h"
14*6777b538SAndroid Build Coastguard Worker #include "base/files/file_path.h"
15*6777b538SAndroid Build Coastguard Worker #include "base/files/file_util.h"
16*6777b538SAndroid Build Coastguard Worker #include "base/logging.h"
17*6777b538SAndroid Build Coastguard Worker #include "base/nix/xdg_util.h"
18*6777b538SAndroid Build Coastguard Worker #include "base/no_destructor.h"
19*6777b538SAndroid Build Coastguard Worker #include "base/ranges/algorithm.h"
20*6777b538SAndroid Build Coastguard Worker #include "base/strings/string_util.h"
21*6777b538SAndroid Build Coastguard Worker #include "base/strings/utf_string_conversion_utils.h"
22*6777b538SAndroid Build Coastguard Worker #include "build/build_config.h"
23*6777b538SAndroid Build Coastguard Worker
24*6777b538SAndroid Build Coastguard Worker #if !BUILDFLAG(IS_CHROMEOS)
25*6777b538SAndroid Build Coastguard Worker #include "base/synchronization/lock.h"
26*6777b538SAndroid Build Coastguard Worker #endif
27*6777b538SAndroid Build Coastguard Worker
28*6777b538SAndroid Build Coastguard Worker namespace base::nix {
29*6777b538SAndroid Build Coastguard Worker namespace {
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker // Ridiculously large size for a /usr/share/mime/mime.cache file.
32*6777b538SAndroid Build Coastguard Worker // Default file is about 100KB, allow up to 10MB.
33*6777b538SAndroid Build Coastguard Worker constexpr size_t kMaxMimeTypesFileSize = 10 * 1024 * 1024;
34*6777b538SAndroid Build Coastguard Worker // Maximum number of nodes to allow in reverse suffix tree.
35*6777b538SAndroid Build Coastguard Worker // Default file has ~3K nodes, allow up to 30K.
36*6777b538SAndroid Build Coastguard Worker constexpr size_t kMaxNodes = 30000;
37*6777b538SAndroid Build Coastguard Worker // Maximum file extension size.
38*6777b538SAndroid Build Coastguard Worker constexpr size_t kMaxExtSize = 100;
39*6777b538SAndroid Build Coastguard Worker // Header size in mime.cache file.
40*6777b538SAndroid Build Coastguard Worker constexpr size_t kHeaderSize = 40;
41*6777b538SAndroid Build Coastguard Worker // Largest valid unicode code point is U+10ffff.
42*6777b538SAndroid Build Coastguard Worker constexpr uint32_t kMaxUnicode = 0x10ffff;
43*6777b538SAndroid Build Coastguard Worker // Default mime glob weight is 50, max is 100.
44*6777b538SAndroid Build Coastguard Worker constexpr uint8_t kDefaultGlobWeight = 50;
45*6777b538SAndroid Build Coastguard Worker
46*6777b538SAndroid Build Coastguard Worker // Path and last modified of mime.cache file.
47*6777b538SAndroid Build Coastguard Worker struct FileInfo {
48*6777b538SAndroid Build Coastguard Worker FilePath path;
49*6777b538SAndroid Build Coastguard Worker Time last_modified;
50*6777b538SAndroid Build Coastguard Worker };
51*6777b538SAndroid Build Coastguard Worker
52*6777b538SAndroid Build Coastguard Worker // Load all mime cache files on the system.
LoadAllMimeCacheFiles(MimeTypeMap & map,std::vector<FileInfo> & files)53*6777b538SAndroid Build Coastguard Worker void LoadAllMimeCacheFiles(MimeTypeMap& map, std::vector<FileInfo>& files) {
54*6777b538SAndroid Build Coastguard Worker std::unique_ptr<Environment> env(Environment::Create());
55*6777b538SAndroid Build Coastguard Worker File::Info info;
56*6777b538SAndroid Build Coastguard Worker for (const auto& path : GetXDGDataSearchLocations(env.get())) {
57*6777b538SAndroid Build Coastguard Worker FilePath mime_cache = path.Append("mime/mime.cache");
58*6777b538SAndroid Build Coastguard Worker if (GetFileInfo(mime_cache, &info) && ParseMimeTypes(mime_cache, map)) {
59*6777b538SAndroid Build Coastguard Worker files.emplace_back(mime_cache, info.last_modified);
60*6777b538SAndroid Build Coastguard Worker }
61*6777b538SAndroid Build Coastguard Worker }
62*6777b538SAndroid Build Coastguard Worker }
63*6777b538SAndroid Build Coastguard Worker
64*6777b538SAndroid Build Coastguard Worker // Read 4 bytes from string `buf` at `offset` as network order uint32_t.
65*6777b538SAndroid Build Coastguard Worker // Returns false if `offset > buf.size() - 4` or `offset` is not aligned to a
66*6777b538SAndroid Build Coastguard Worker // 4-byte word boundary, or `*result` is not between `min_result` and
67*6777b538SAndroid Build Coastguard Worker // `max_result`. `field_name` is used in error message.
ReadInt(const std::string & buf,uint32_t offset,const std::string & field_name,uint32_t min_result,size_t max_result,uint32_t * result)68*6777b538SAndroid Build Coastguard Worker bool ReadInt(const std::string& buf,
69*6777b538SAndroid Build Coastguard Worker uint32_t offset,
70*6777b538SAndroid Build Coastguard Worker const std::string& field_name,
71*6777b538SAndroid Build Coastguard Worker uint32_t min_result,
72*6777b538SAndroid Build Coastguard Worker size_t max_result,
73*6777b538SAndroid Build Coastguard Worker uint32_t* result) {
74*6777b538SAndroid Build Coastguard Worker if (offset > buf.size() - 4 || (offset & 0x3)) {
75*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Invalid offset=" << offset << " for " << field_name
76*6777b538SAndroid Build Coastguard Worker << ", string size=" << buf.size();
77*6777b538SAndroid Build Coastguard Worker return false;
78*6777b538SAndroid Build Coastguard Worker }
79*6777b538SAndroid Build Coastguard Worker *result = ntohl(*reinterpret_cast<const uint32_t*>(buf.c_str() + offset));
80*6777b538SAndroid Build Coastguard Worker if (*result < min_result || *result > max_result) {
81*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Invalid " << field_name << "=" << *result
82*6777b538SAndroid Build Coastguard Worker << " not between min_result=" << min_result
83*6777b538SAndroid Build Coastguard Worker << " and max_result=" << max_result;
84*6777b538SAndroid Build Coastguard Worker return false;
85*6777b538SAndroid Build Coastguard Worker }
86*6777b538SAndroid Build Coastguard Worker return true;
87*6777b538SAndroid Build Coastguard Worker }
88*6777b538SAndroid Build Coastguard Worker
89*6777b538SAndroid Build Coastguard Worker } // namespace
90*6777b538SAndroid Build Coastguard Worker
ParseMimeTypes(const FilePath & file_path,MimeTypeMap & out_mime_types)91*6777b538SAndroid Build Coastguard Worker bool ParseMimeTypes(const FilePath& file_path, MimeTypeMap& out_mime_types) {
92*6777b538SAndroid Build Coastguard Worker // File format from
93*6777b538SAndroid Build Coastguard Worker // https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-0.21.html#idm46070612075440
94*6777b538SAndroid Build Coastguard Worker // Header:
95*6777b538SAndroid Build Coastguard Worker // 2 CARD16 MAJOR_VERSION 1
96*6777b538SAndroid Build Coastguard Worker // 2 CARD16 MINOR_VERSION 2
97*6777b538SAndroid Build Coastguard Worker // 4 CARD32 ALIAS_LIST_OFFSET
98*6777b538SAndroid Build Coastguard Worker // 4 CARD32 PARENT_LIST_OFFSET
99*6777b538SAndroid Build Coastguard Worker // 4 CARD32 LITERAL_LIST_OFFSET
100*6777b538SAndroid Build Coastguard Worker // 4 CARD32 REVERSE_SUFFIX_TREE_OFFSET
101*6777b538SAndroid Build Coastguard Worker // ...
102*6777b538SAndroid Build Coastguard Worker // ReverseSuffixTree:
103*6777b538SAndroid Build Coastguard Worker // 4 CARD32 N_ROOTS
104*6777b538SAndroid Build Coastguard Worker // 4 CARD32 FIRST_ROOT_OFFSET
105*6777b538SAndroid Build Coastguard Worker // ReverseSuffixTreeNode:
106*6777b538SAndroid Build Coastguard Worker // 4 CARD32 CHARACTER
107*6777b538SAndroid Build Coastguard Worker // 4 CARD32 N_CHILDREN
108*6777b538SAndroid Build Coastguard Worker // 4 CARD32 FIRST_CHILD_OFFSET
109*6777b538SAndroid Build Coastguard Worker // ReverseSuffixTreeLeafNode:
110*6777b538SAndroid Build Coastguard Worker // 4 CARD32 0
111*6777b538SAndroid Build Coastguard Worker // 4 CARD32 MIME_TYPE_OFFSET
112*6777b538SAndroid Build Coastguard Worker // 4 CARD32 WEIGHT in lower 8 bits
113*6777b538SAndroid Build Coastguard Worker // FLAGS in rest:
114*6777b538SAndroid Build Coastguard Worker // 0x100 = case-sensitive
115*6777b538SAndroid Build Coastguard Worker
116*6777b538SAndroid Build Coastguard Worker std::string buf;
117*6777b538SAndroid Build Coastguard Worker if (!ReadFileToStringWithMaxSize(file_path, &buf, kMaxMimeTypesFileSize)) {
118*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Failed reading in mime.cache file: " << file_path;
119*6777b538SAndroid Build Coastguard Worker return false;
120*6777b538SAndroid Build Coastguard Worker }
121*6777b538SAndroid Build Coastguard Worker
122*6777b538SAndroid Build Coastguard Worker if (buf.size() < kHeaderSize) {
123*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Invalid mime.cache file size=" << buf.size();
124*6777b538SAndroid Build Coastguard Worker return false;
125*6777b538SAndroid Build Coastguard Worker }
126*6777b538SAndroid Build Coastguard Worker
127*6777b538SAndroid Build Coastguard Worker // Validate file[ALIAS_LIST_OFFSET - 1] is null to ensure that any
128*6777b538SAndroid Build Coastguard Worker // null-terminated strings dereferenced at addresses below ALIAS_LIST_OFFSET
129*6777b538SAndroid Build Coastguard Worker // will not overflow.
130*6777b538SAndroid Build Coastguard Worker uint32_t alias_list_offset = 0;
131*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, 4, "ALIAS_LIST_OFFSET", kHeaderSize, buf.size(),
132*6777b538SAndroid Build Coastguard Worker &alias_list_offset)) {
133*6777b538SAndroid Build Coastguard Worker return false;
134*6777b538SAndroid Build Coastguard Worker }
135*6777b538SAndroid Build Coastguard Worker if (buf[alias_list_offset - 1] != 0) {
136*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Invalid mime.cache file does not contain null prior to "
137*6777b538SAndroid Build Coastguard Worker "ALIAS_LIST_OFFSET="
138*6777b538SAndroid Build Coastguard Worker << alias_list_offset;
139*6777b538SAndroid Build Coastguard Worker return false;
140*6777b538SAndroid Build Coastguard Worker }
141*6777b538SAndroid Build Coastguard Worker
142*6777b538SAndroid Build Coastguard Worker // Parse ReverseSuffixTree. Read all nodes and place them on `stack`,
143*6777b538SAndroid Build Coastguard Worker // allowing max of kMaxNodes and max extension of kMaxExtSize.
144*6777b538SAndroid Build Coastguard Worker uint32_t tree_offset = 0;
145*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, 16, "REVERSE_SUFFIX_TREE_OFFSET", kHeaderSize, buf.size(),
146*6777b538SAndroid Build Coastguard Worker &tree_offset)) {
147*6777b538SAndroid Build Coastguard Worker return false;
148*6777b538SAndroid Build Coastguard Worker }
149*6777b538SAndroid Build Coastguard Worker
150*6777b538SAndroid Build Coastguard Worker struct Node {
151*6777b538SAndroid Build Coastguard Worker std::string ext;
152*6777b538SAndroid Build Coastguard Worker uint32_t n_children;
153*6777b538SAndroid Build Coastguard Worker uint32_t first_child_offset;
154*6777b538SAndroid Build Coastguard Worker };
155*6777b538SAndroid Build Coastguard Worker
156*6777b538SAndroid Build Coastguard Worker // Read root node and put it on the stack.
157*6777b538SAndroid Build Coastguard Worker Node root;
158*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, tree_offset, "N_ROOTS", 0, kMaxUnicode, &root.n_children)) {
159*6777b538SAndroid Build Coastguard Worker return false;
160*6777b538SAndroid Build Coastguard Worker }
161*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, tree_offset + 4, "FIRST_ROOT_OFFSET", tree_offset,
162*6777b538SAndroid Build Coastguard Worker buf.size(), &root.first_child_offset)) {
163*6777b538SAndroid Build Coastguard Worker return false;
164*6777b538SAndroid Build Coastguard Worker }
165*6777b538SAndroid Build Coastguard Worker stack<Node> stack;
166*6777b538SAndroid Build Coastguard Worker stack.push(std::move(root));
167*6777b538SAndroid Build Coastguard Worker
168*6777b538SAndroid Build Coastguard Worker uint32_t num_nodes = 0;
169*6777b538SAndroid Build Coastguard Worker while (!stack.empty()) {
170*6777b538SAndroid Build Coastguard Worker // Pop top node from the stack and process children.
171*6777b538SAndroid Build Coastguard Worker Node n = std::move(stack.top());
172*6777b538SAndroid Build Coastguard Worker stack.pop();
173*6777b538SAndroid Build Coastguard Worker uint32_t p = n.first_child_offset;
174*6777b538SAndroid Build Coastguard Worker for (uint32_t i = 0; i < n.n_children; i++) {
175*6777b538SAndroid Build Coastguard Worker uint32_t c = 0;
176*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, p, "CHARACTER", 0, kMaxUnicode, &c)) {
177*6777b538SAndroid Build Coastguard Worker return false;
178*6777b538SAndroid Build Coastguard Worker }
179*6777b538SAndroid Build Coastguard Worker p += 4;
180*6777b538SAndroid Build Coastguard Worker
181*6777b538SAndroid Build Coastguard Worker // Leaf node, add mime type if it is highest weight.
182*6777b538SAndroid Build Coastguard Worker if (c == 0) {
183*6777b538SAndroid Build Coastguard Worker uint32_t mime_type_offset = 0;
184*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, p, "mime type offset", kHeaderSize,
185*6777b538SAndroid Build Coastguard Worker alias_list_offset - 1, &mime_type_offset)) {
186*6777b538SAndroid Build Coastguard Worker return false;
187*6777b538SAndroid Build Coastguard Worker }
188*6777b538SAndroid Build Coastguard Worker p += 4;
189*6777b538SAndroid Build Coastguard Worker uint8_t weight = kDefaultGlobWeight;
190*6777b538SAndroid Build Coastguard Worker if ((p + 3) < buf.size()) {
191*6777b538SAndroid Build Coastguard Worker weight = static_cast<uint8_t>(buf[p + 3]);
192*6777b538SAndroid Build Coastguard Worker }
193*6777b538SAndroid Build Coastguard Worker p += 4;
194*6777b538SAndroid Build Coastguard Worker if (n.ext.size() > 0 && n.ext[0] == '.') {
195*6777b538SAndroid Build Coastguard Worker std::string ext = n.ext.substr(1);
196*6777b538SAndroid Build Coastguard Worker auto it = out_mime_types.find(ext);
197*6777b538SAndroid Build Coastguard Worker if (it == out_mime_types.end() || weight > it->second.weight) {
198*6777b538SAndroid Build Coastguard Worker out_mime_types[ext] = {std::string(buf.c_str() + mime_type_offset),
199*6777b538SAndroid Build Coastguard Worker weight};
200*6777b538SAndroid Build Coastguard Worker }
201*6777b538SAndroid Build Coastguard Worker }
202*6777b538SAndroid Build Coastguard Worker continue;
203*6777b538SAndroid Build Coastguard Worker }
204*6777b538SAndroid Build Coastguard Worker
205*6777b538SAndroid Build Coastguard Worker // Regular node, parse and add it to the stack.
206*6777b538SAndroid Build Coastguard Worker Node node;
207*6777b538SAndroid Build Coastguard Worker WriteUnicodeCharacter(static_cast<int>(c), &node.ext);
208*6777b538SAndroid Build Coastguard Worker node.ext += n.ext;
209*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, p, "N_CHILDREN", 0, kMaxUnicode, &node.n_children)) {
210*6777b538SAndroid Build Coastguard Worker return false;
211*6777b538SAndroid Build Coastguard Worker }
212*6777b538SAndroid Build Coastguard Worker p += 4;
213*6777b538SAndroid Build Coastguard Worker if (!ReadInt(buf, p, "FIRST_CHILD_OFFSET", tree_offset, buf.size(),
214*6777b538SAndroid Build Coastguard Worker &node.first_child_offset)) {
215*6777b538SAndroid Build Coastguard Worker return false;
216*6777b538SAndroid Build Coastguard Worker }
217*6777b538SAndroid Build Coastguard Worker p += 4;
218*6777b538SAndroid Build Coastguard Worker
219*6777b538SAndroid Build Coastguard Worker // Check limits.
220*6777b538SAndroid Build Coastguard Worker if (++num_nodes > kMaxNodes) {
221*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Exceeded maxium number of nodes=" << kMaxNodes;
222*6777b538SAndroid Build Coastguard Worker return false;
223*6777b538SAndroid Build Coastguard Worker }
224*6777b538SAndroid Build Coastguard Worker if (node.ext.size() > kMaxExtSize) {
225*6777b538SAndroid Build Coastguard Worker LOG(WARNING) << "Ignoring large extension exceeds size=" << kMaxExtSize
226*6777b538SAndroid Build Coastguard Worker << " ext=" << node.ext;
227*6777b538SAndroid Build Coastguard Worker continue;
228*6777b538SAndroid Build Coastguard Worker }
229*6777b538SAndroid Build Coastguard Worker
230*6777b538SAndroid Build Coastguard Worker stack.push(std::move(node));
231*6777b538SAndroid Build Coastguard Worker }
232*6777b538SAndroid Build Coastguard Worker }
233*6777b538SAndroid Build Coastguard Worker
234*6777b538SAndroid Build Coastguard Worker return true;
235*6777b538SAndroid Build Coastguard Worker }
236*6777b538SAndroid Build Coastguard Worker
GetFileMimeType(const FilePath & filepath)237*6777b538SAndroid Build Coastguard Worker std::string GetFileMimeType(const FilePath& filepath) {
238*6777b538SAndroid Build Coastguard Worker std::string ext = filepath.Extension();
239*6777b538SAndroid Build Coastguard Worker if (ext.empty()) {
240*6777b538SAndroid Build Coastguard Worker return std::string();
241*6777b538SAndroid Build Coastguard Worker }
242*6777b538SAndroid Build Coastguard Worker
243*6777b538SAndroid Build Coastguard Worker static NoDestructor<std::vector<FileInfo>> xdg_mime_files;
244*6777b538SAndroid Build Coastguard Worker
245*6777b538SAndroid Build Coastguard Worker static NoDestructor<MimeTypeMap> mime_type_map([] {
246*6777b538SAndroid Build Coastguard Worker MimeTypeMap map;
247*6777b538SAndroid Build Coastguard Worker LoadAllMimeCacheFiles(map, *xdg_mime_files);
248*6777b538SAndroid Build Coastguard Worker return map;
249*6777b538SAndroid Build Coastguard Worker }());
250*6777b538SAndroid Build Coastguard Worker
251*6777b538SAndroid Build Coastguard Worker // Files never change on ChromeOS, but for linux, match xdgmime behavior and
252*6777b538SAndroid Build Coastguard Worker // check every 5s and reload if any files have changed.
253*6777b538SAndroid Build Coastguard Worker #if !BUILDFLAG(IS_CHROMEOS)
254*6777b538SAndroid Build Coastguard Worker static Time last_check;
255*6777b538SAndroid Build Coastguard Worker // Lock is required since this may be called on any thread.
256*6777b538SAndroid Build Coastguard Worker static NoDestructor<Lock> lock;
257*6777b538SAndroid Build Coastguard Worker {
258*6777b538SAndroid Build Coastguard Worker AutoLock scoped_lock(*lock);
259*6777b538SAndroid Build Coastguard Worker
260*6777b538SAndroid Build Coastguard Worker Time now = Time::Now();
261*6777b538SAndroid Build Coastguard Worker if (last_check + Seconds(5) < now) {
262*6777b538SAndroid Build Coastguard Worker if (ranges::any_of(*xdg_mime_files, [](const FileInfo& file_info) {
263*6777b538SAndroid Build Coastguard Worker File::Info info;
264*6777b538SAndroid Build Coastguard Worker return !GetFileInfo(file_info.path, &info) ||
265*6777b538SAndroid Build Coastguard Worker info.last_modified != file_info.last_modified;
266*6777b538SAndroid Build Coastguard Worker })) {
267*6777b538SAndroid Build Coastguard Worker mime_type_map->clear();
268*6777b538SAndroid Build Coastguard Worker xdg_mime_files->clear();
269*6777b538SAndroid Build Coastguard Worker LoadAllMimeCacheFiles(*mime_type_map, *xdg_mime_files);
270*6777b538SAndroid Build Coastguard Worker }
271*6777b538SAndroid Build Coastguard Worker last_check = now;
272*6777b538SAndroid Build Coastguard Worker }
273*6777b538SAndroid Build Coastguard Worker }
274*6777b538SAndroid Build Coastguard Worker #endif
275*6777b538SAndroid Build Coastguard Worker
276*6777b538SAndroid Build Coastguard Worker auto it = mime_type_map->find(ext.substr(1));
277*6777b538SAndroid Build Coastguard Worker return it != mime_type_map->end() ? it->second.mime_type : std::string();
278*6777b538SAndroid Build Coastguard Worker }
279*6777b538SAndroid Build Coastguard Worker
280*6777b538SAndroid Build Coastguard Worker } // namespace base::nix
281