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 "chrome/browser/extensions/api/file_handlers/mime_util.h"
6
7#include "base/files/file_path.h"
8#include "base/files/file_util.h"
9#include "chrome/browser/profiles/profile.h"
10#include "content/public/browser/browser_thread.h"
11#include "net/base/filename_util.h"
12#include "net/base/mime_sniffer.h"
13#include "net/base/mime_util.h"
14#include "storage/browser/fileapi/file_system_url.h"
15
16#if defined(OS_CHROMEOS)
17#include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
18#endif
19
20using content::BrowserThread;
21
22namespace extensions {
23namespace app_file_handler_util {
24namespace {
25
26// Detects MIME type by reading initial bytes from the file. If found, then
27// writes the MIME type to |result|.
28void SniffMimeType(const base::FilePath& local_path, std::string* result) {
29  std::vector<char> content(net::kMaxBytesToSniff);
30
31  const int bytes_read =
32      base::ReadFile(local_path, &content[0], content.size());
33
34  if (bytes_read >= 0) {
35    net::SniffMimeType(&content[0],
36                       bytes_read,
37                       net::FilePathToFileURL(local_path),
38                       std::string(),  // type_hint (passes no hint)
39                       result);
40  }
41}
42
43#if defined(OS_CHROMEOS)
44// Converts a result passed as a scoped pointer to a dereferenced value passed
45// to |callback|.
46void OnGetMimeTypeFromFileForNonNativeLocalPathCompleted(
47    scoped_ptr<std::string> mime_type,
48    const base::Callback<void(const std::string&)>& callback) {
49  callback.Run(*mime_type);
50}
51
52// Called when fetching MIME type for a non-native local path is completed.
53// If |success| is false, then tries to guess the MIME type by looking at the
54// file name.
55void OnGetMimeTypeFromMetadataForNonNativeLocalPathCompleted(
56    const base::FilePath& local_path,
57    const base::Callback<void(const std::string&)>& callback,
58    bool success,
59    const std::string& mime_type) {
60  if (success) {
61    callback.Run(mime_type);
62    return;
63  }
64
65  // MIME type not available with metadata, hence try to guess it from the
66  // file's extension.
67  scoped_ptr<std::string> mime_type_from_extension(new std::string);
68  std::string* const mime_type_from_extension_ptr =
69      mime_type_from_extension.get();
70  BrowserThread::PostBlockingPoolTaskAndReply(
71      FROM_HERE,
72      base::Bind(base::IgnoreResult(&net::GetMimeTypeFromFile),
73                 local_path,
74                 mime_type_from_extension_ptr),
75      base::Bind(&OnGetMimeTypeFromFileForNonNativeLocalPathCompleted,
76                 base::Passed(&mime_type_from_extension),
77                 callback));
78}
79#endif
80
81// Called when sniffing for MIME type in the native local file is completed.
82void OnSniffMimeTypeForNativeLocalPathCompleted(
83    scoped_ptr<std::string> mime_type,
84    const base::Callback<void(const std::string&)>& callback) {
85  callback.Run(*mime_type);
86}
87
88}  // namespace
89
90// Handles response of net::GetMimeTypeFromFile for native file systems. If
91// MIME type is available, then forwards it to |callback|. Otherwise, fallbacks
92// to sniffing.
93void OnGetMimeTypeFromFileForNativeLocalPathCompleted(
94    const base::FilePath& local_path,
95    scoped_ptr<std::string> mime_type,
96    const base::Callback<void(const std::string&)>& callback) {
97  if (!mime_type->empty()) {
98    callback.Run(*mime_type);
99    return;
100  }
101
102  scoped_ptr<std::string> sniffed_mime_type(new std::string);
103  std::string* const sniffed_mime_type_ptr = sniffed_mime_type.get();
104  BrowserThread::PostBlockingPoolTaskAndReply(
105      FROM_HERE,
106      base::Bind(&SniffMimeType, local_path, sniffed_mime_type_ptr),
107      base::Bind(&OnSniffMimeTypeForNativeLocalPathCompleted,
108                 base::Passed(&sniffed_mime_type),
109                 callback));
110}
111
112// Fetches MIME type for a local path and returns it with a |callback|.
113void GetMimeTypeForLocalPath(
114    Profile* profile,
115    const base::FilePath& local_path,
116    const base::Callback<void(const std::string&)>& callback) {
117#if defined(OS_CHROMEOS)
118  if (file_manager::util::IsUnderNonNativeLocalPath(profile, local_path)) {
119    // For non-native files, try to get the MIME type from metadata. If not
120    // available, then try to guess from the extension. Never sniff (because
121    // it can be very slow).
122    file_manager::util::GetNonNativeLocalPathMimeType(
123        profile,
124        local_path,
125        base::Bind(&OnGetMimeTypeFromMetadataForNonNativeLocalPathCompleted,
126                   local_path,
127                   callback));
128    return;
129  }
130#endif
131
132  // For native local files, try to guess the mime from the extension. If
133  // not available, then try to sniff if.
134  scoped_ptr<std::string> mime_type_from_extension(new std::string);
135  std::string* const mime_type_from_extension_ptr =
136      mime_type_from_extension.get();
137  BrowserThread::PostBlockingPoolTaskAndReply(
138      FROM_HERE,
139      base::Bind(base::IgnoreResult(&net::GetMimeTypeFromFile),
140                 local_path,
141                 mime_type_from_extension_ptr),
142      base::Bind(&OnGetMimeTypeFromFileForNativeLocalPathCompleted,
143                 local_path,
144                 base::Passed(&mime_type_from_extension),
145                 callback));
146}
147
148MimeTypeCollector::MimeTypeCollector(Profile* profile)
149    : profile_(profile), left_(0), weak_ptr_factory_(this) {
150}
151
152MimeTypeCollector::~MimeTypeCollector() {
153}
154
155void MimeTypeCollector::CollectForURLs(
156    const std::vector<storage::FileSystemURL>& urls,
157    const CompletionCallback& callback) {
158  std::vector<base::FilePath> local_paths;
159  for (size_t i = 0; i < urls.size(); ++i) {
160    local_paths.push_back(urls[i].path());
161  }
162
163  CollectForLocalPaths(local_paths, callback);
164}
165
166void MimeTypeCollector::CollectForLocalPaths(
167    const std::vector<base::FilePath>& local_paths,
168    const CompletionCallback& callback) {
169  DCHECK(!callback.is_null());
170  callback_ = callback;
171
172  DCHECK(!result_.get());
173  result_.reset(new std::vector<std::string>(local_paths.size()));
174  left_ = local_paths.size();
175
176  if (!left_) {
177    // Nothing to process.
178    base::MessageLoopProxy::current()->PostTask(
179        FROM_HERE, base::Bind(callback_, base::Passed(&result_)));
180    callback_ = CompletionCallback();
181    return;
182  }
183
184  for (size_t i = 0; i < local_paths.size(); ++i) {
185    GetMimeTypeForLocalPath(profile_,
186                            local_paths[i],
187                            base::Bind(&MimeTypeCollector::OnMimeTypeCollected,
188                                       weak_ptr_factory_.GetWeakPtr(),
189                                       i));
190  }
191}
192
193void MimeTypeCollector::OnMimeTypeCollected(size_t index,
194                                            const std::string& mime_type) {
195  (*result_)[index] = mime_type;
196  if (!--left_) {
197    base::MessageLoopProxy::current()->PostTask(
198        FROM_HERE, base::Bind(callback_, base::Passed(&result_)));
199    // Release the callback to avoid a circullar reference in case an instance
200    // of this class is a member of a ref counted class, which instance is bound
201    // to this callback.
202    callback_ = CompletionCallback();
203  }
204}
205
206}  // namespace app_file_handler_util
207}  // namespace extensions
208