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