1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "extensions/browser/api/file_handlers/mime_util.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/strings/string_piece.h"
11 #include "base/task/post_task.h"
12 #include "base/task/thread_pool.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "build/build_config.h"
15 #include "build/chromeos_buildflags.h"
16 #include "content/public/browser/browser_context.h"
17 #include "net/base/filename_util.h"
18 #include "net/base/mime_sniffer.h"
19 #include "net/base/mime_util.h"
20 #include "storage/browser/file_system/file_system_url.h"
21
22 #if BUILDFLAG(IS_CHROMEOS_ASH)
23 #include "extensions/browser/api/extensions_api_client.h"
24 #include "extensions/browser/api/file_handlers/non_native_file_system_delegate.h"
25 #endif
26
27 namespace extensions {
28 namespace app_file_handler_util {
29
30 const char kMimeTypeApplicationOctetStream[] = "application/octet-stream";
31 const char kMimeTypeInodeDirectory[] = "inode/directory";
32
33 namespace {
34
35 // Detects MIME type by reading initial bytes from the file. If found, then
36 // writes the MIME type to |result|.
SniffMimeType(const base::FilePath & local_path,std::string * result)37 void SniffMimeType(const base::FilePath& local_path, std::string* result) {
38 std::vector<char> content(net::kMaxBytesToSniff);
39
40 const int bytes_read =
41 base::ReadFile(local_path, &content[0], static_cast<int>(content.size()));
42
43 if (bytes_read >= 0) {
44 net::SniffMimeType(base::StringPiece(&content[0], bytes_read),
45 net::FilePathToFileURL(local_path),
46 std::string(), // type_hint (passes no hint)
47 net::ForceSniffFileUrlsForHtml::kDisabled, result);
48 if (*result == "text/plain") {
49 // text/plain misidentifies AMR files, which look like scripts because
50 // they start with "#!". Use SniffMimeTypeFromLocalData to try and get a
51 // better match.
52 // TODO(amistry): Potentially add other types (i.e. SVG).
53 std::string secondary_result;
54 net::SniffMimeTypeFromLocalData(
55 base::StringPiece(&content[0], bytes_read), &secondary_result);
56 if (!secondary_result.empty())
57 *result = secondary_result;
58 }
59 } else if (base::DirectoryExists(local_path)) {
60 // XDG defines directories to have mime type inode/directory.
61 // https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html#idm45070737701600
62 *result = kMimeTypeInodeDirectory;
63 }
64 }
65
66 #if BUILDFLAG(IS_CHROMEOS_ASH)
67 // Converts a result passed as a scoped pointer to a dereferenced value passed
68 // to |callback|.
OnGetMimeTypeFromFileForNonNativeLocalPathCompleted(std::unique_ptr<std::string> mime_type,base::OnceCallback<void (const std::string &)> callback)69 void OnGetMimeTypeFromFileForNonNativeLocalPathCompleted(
70 std::unique_ptr<std::string> mime_type,
71 base::OnceCallback<void(const std::string&)> callback) {
72 std::move(callback).Run(*mime_type);
73 }
74
75 // Called when fetching MIME type for a non-native local path is completed.
76 // If |success| is false, then tries to guess the MIME type by looking at the
77 // file name.
OnGetMimeTypeFromMetadataForNonNativeLocalPathCompleted(const base::FilePath & local_path,base::OnceCallback<void (const std::string &)> callback,const base::Optional<std::string> & mime_type)78 void OnGetMimeTypeFromMetadataForNonNativeLocalPathCompleted(
79 const base::FilePath& local_path,
80 base::OnceCallback<void(const std::string&)> callback,
81 const base::Optional<std::string>& mime_type) {
82 if (mime_type) {
83 std::move(callback).Run(mime_type.value());
84 return;
85 }
86
87 // MIME type not available with metadata, hence try to guess it from the
88 // file's extension.
89 std::unique_ptr<std::string> mime_type_from_extension(new std::string);
90 std::string* const mime_type_from_extension_ptr =
91 mime_type_from_extension.get();
92 base::ThreadPool::PostTaskAndReply(
93 FROM_HERE, {base::MayBlock()},
94 base::BindOnce(base::IgnoreResult(&net::GetMimeTypeFromFile), local_path,
95 mime_type_from_extension_ptr),
96 base::BindOnce(&OnGetMimeTypeFromFileForNonNativeLocalPathCompleted,
97 std::move(mime_type_from_extension), std::move(callback)));
98 }
99 #endif
100
101 // Called when sniffing for MIME type in the native local file is completed.
OnSniffMimeTypeForNativeLocalPathCompleted(std::unique_ptr<std::string> mime_type,base::OnceCallback<void (const std::string &)> callback)102 void OnSniffMimeTypeForNativeLocalPathCompleted(
103 std::unique_ptr<std::string> mime_type,
104 base::OnceCallback<void(const std::string&)> callback) {
105 // Do not return application/zip as sniffed result. If the file has .zip
106 // extension, it should be already returned as application/zip. If the file
107 // does not have .zip extension and couldn't find mime type from the
108 // extension, it might be unknown internally zipped file.
109 if (*mime_type == "application/zip") {
110 std::move(callback).Run(kMimeTypeApplicationOctetStream);
111 return;
112 }
113
114 std::move(callback).Run(*mime_type);
115 }
116
117 } // namespace
118
119 // Handles response of net::GetMimeTypeFromFile for native file systems. If
120 // MIME type is available, then forwards it to |callback|. Otherwise, fallbacks
121 // to sniffing.
OnGetMimeTypeFromFileForNativeLocalPathCompleted(const base::FilePath & local_path,std::unique_ptr<std::string> mime_type,base::OnceCallback<void (const std::string &)> callback)122 void OnGetMimeTypeFromFileForNativeLocalPathCompleted(
123 const base::FilePath& local_path,
124 std::unique_ptr<std::string> mime_type,
125 base::OnceCallback<void(const std::string&)> callback) {
126 if (!mime_type->empty()) {
127 std::move(callback).Run(*mime_type);
128 return;
129 }
130
131 std::unique_ptr<std::string> sniffed_mime_type(
132 new std::string(kMimeTypeApplicationOctetStream));
133 std::string* const sniffed_mime_type_ptr = sniffed_mime_type.get();
134 base::ThreadPool::PostTaskAndReply(
135 FROM_HERE, {base::MayBlock()},
136 base::BindOnce(&SniffMimeType, local_path, sniffed_mime_type_ptr),
137 base::BindOnce(&OnSniffMimeTypeForNativeLocalPathCompleted,
138 std::move(sniffed_mime_type), std::move(callback)));
139 }
140
141 // Fetches MIME type for a local path and returns it with a |callback|.
GetMimeTypeForLocalPath(content::BrowserContext * context,const base::FilePath & local_path,base::OnceCallback<void (const std::string &)> callback)142 void GetMimeTypeForLocalPath(
143 content::BrowserContext* context,
144 const base::FilePath& local_path,
145 base::OnceCallback<void(const std::string&)> callback) {
146 #if BUILDFLAG(IS_CHROMEOS_ASH)
147 NonNativeFileSystemDelegate* delegate =
148 ExtensionsAPIClient::Get()->GetNonNativeFileSystemDelegate();
149 if (delegate && delegate->HasNonNativeMimeTypeProvider(context, local_path)) {
150 // For non-native files, try to get the MIME type from metadata. If not
151 // available, then try to guess from the extension. Never sniff (because
152 // it can be very slow).
153 delegate->GetNonNativeLocalPathMimeType(
154 context, local_path,
155 base::BindOnce(&OnGetMimeTypeFromMetadataForNonNativeLocalPathCompleted,
156 local_path, std::move(callback)));
157 return;
158 }
159 #endif
160
161 // For native local files, try to guess the mime from the extension. If
162 // not available, then try to sniff if.
163 std::unique_ptr<std::string> mime_type_from_extension(new std::string);
164 std::string* const mime_type_from_extension_ptr =
165 mime_type_from_extension.get();
166 base::ThreadPool::PostTaskAndReply(
167 FROM_HERE, {base::MayBlock()},
168 base::BindOnce(base::IgnoreResult(&net::GetMimeTypeFromFile), local_path,
169 mime_type_from_extension_ptr),
170 base::BindOnce(&OnGetMimeTypeFromFileForNativeLocalPathCompleted,
171 local_path, std::move(mime_type_from_extension),
172 std::move(callback)));
173 }
174
MimeTypeCollector(content::BrowserContext * context)175 MimeTypeCollector::MimeTypeCollector(content::BrowserContext* context)
176 : context_(context), left_(0) {}
177
~MimeTypeCollector()178 MimeTypeCollector::~MimeTypeCollector() {}
179
CollectForURLs(const std::vector<storage::FileSystemURL> & urls,CompletionCallback callback)180 void MimeTypeCollector::CollectForURLs(
181 const std::vector<storage::FileSystemURL>& urls,
182 CompletionCallback callback) {
183 std::vector<base::FilePath> local_paths;
184 for (size_t i = 0; i < urls.size(); ++i) {
185 local_paths.push_back(urls[i].path());
186 }
187
188 CollectForLocalPaths(local_paths, std::move(callback));
189 }
190
CollectForLocalPaths(const std::vector<base::FilePath> & local_paths,CompletionCallback callback)191 void MimeTypeCollector::CollectForLocalPaths(
192 const std::vector<base::FilePath>& local_paths,
193 CompletionCallback callback) {
194 DCHECK(!callback.is_null());
195 callback_ = std::move(callback);
196
197 DCHECK(!result_.get());
198 result_.reset(new std::vector<std::string>(local_paths.size()));
199 left_ = local_paths.size();
200
201 if (!left_) {
202 // Nothing to process.
203 base::ThreadTaskRunnerHandle::Get()->PostTask(
204 FROM_HERE, base::BindOnce(std::move(callback_), std::move(result_)));
205 callback_.Reset();
206 return;
207 }
208
209 for (size_t i = 0; i < local_paths.size(); ++i) {
210 GetMimeTypeForLocalPath(
211 context_, local_paths[i],
212 base::BindOnce(&MimeTypeCollector::OnMimeTypeCollected,
213 weak_ptr_factory_.GetWeakPtr(), i));
214 }
215 }
216
OnMimeTypeCollected(size_t index,const std::string & mime_type)217 void MimeTypeCollector::OnMimeTypeCollected(size_t index,
218 const std::string& mime_type) {
219 (*result_)[index] = mime_type;
220 if (!--left_) {
221 base::ThreadTaskRunnerHandle::Get()->PostTask(
222 FROM_HERE, base::BindOnce(std::move(callback_), std::move(result_)));
223 // Release the callback to avoid a circullar reference in case an instance
224 // of this class is a member of a ref counted class, which instance is bound
225 // to this callback.
226 callback_.Reset();
227 }
228 }
229
230 } // namespace app_file_handler_util
231 } // namespace extensions
232