Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(332)

Side by Side Diff: chrome/browser/extensions/extension_file_browser_private_api.cc

Issue 6749021: Added new fileBrowserPrivate and fileHandler extension APIs (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/extensions/extension_file_browser_private_api.h" 5 #include "chrome/browser/extensions/extension_file_browser_private_api.h"
6 6
7 #include "base/base64.h"
8 #include "base/crypto/symmetric_key.h"
9 #include "base/hmac.h"
7 #include "base/json/json_writer.h" 10 #include "base/json/json_writer.h"
11 #include "base/memory/singleton.h"
12 #include "base/stringprintf.h"
8 #include "base/task.h" 13 #include "base/task.h"
9 #include "base/values.h" 14 #include "base/values.h"
10 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/extensions/extension_function_dispatcher.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/tab_contents/context_menu_utils.h"
19 #include "chrome/browser/ui/webui/extension_icon_source.h"
11 #include "chrome/common/extensions/extension.h" 20 #include "chrome/common/extensions/extension.h"
12 #include "content/browser/browser_thread.h" 21 #include "content/browser/browser_thread.h"
22 #include "content/browser/child_process_security_policy.h"
23 #include "content/browser/renderer_host/render_process_host.h"
24 #include "content/browser/renderer_host/render_view_host.h"
25 #include "content/browser/tab_contents/tab_contents.h"
26 #include "webkit/fileapi/file_system_context.h"
13 #include "webkit/fileapi/file_system_operation.h" 27 #include "webkit/fileapi/file_system_operation.h"
28 #include "webkit/fileapi/file_system_path_manager.h"
14 #include "webkit/fileapi/file_system_types.h" 29 #include "webkit/fileapi/file_system_types.h"
30 #include "webkit/glue/context_menu.h"
31
32 #define SHA1_SIZE_IN_BITS 160
33
34 const char kContextTaskIdSchema[] = "context-task";
35
36 bool GetContextMenuItems(Profile* profile,
37 const ContextMenuParams& params,
38 ExtensionMenuItem::List* results) {
39 ExtensionService* service = profile->GetExtensionService();
40 if (!service)
41 return false; // In unit-tests, we may not have an ExtensionService.
42
43 // Get a list of extension id's that have context menu items, and sort it by
44 // the extension's name.
45 ExtensionMenuManager* menu_manager = service->menu_manager();
46 std::set<std::string> ids = menu_manager->ExtensionIds();
47 std::vector<std::pair<std::string, std::string> > sorted_ids;
48 for (std::set<std::string>::iterator i = ids.begin(); i != ids.end(); ++i) {
49 const Extension* extension = service->GetExtensionById(*i, false);
50 if (extension)
51 sorted_ids.push_back(
52 std::pair<std::string, std::string>(extension->name(), *i));
53 }
54
55 if (sorted_ids.empty())
56 return true;
57
58 std::vector<std::pair<std::string, std::string> >::const_iterator i;
59 for (i = sorted_ids.begin(); i != sorted_ids.end(); ++i) {
60 const std::string& extension_id = i->second;
61 const Extension* extension = service->GetExtensionById(extension_id, false);
62 bool can_cross_incognito = service->CanCrossIncognito(extension);
63 const ExtensionMenuItem::List* all_items =
64 menu_manager->MenuItems(extension_id);
65 ExtensionMenuItem::List relevant_items =
66 ContextMenuUtils::GetRelevantExtensionItems(*all_items,
67 params,
68 profile,
69 can_cross_incognito);
70 results->insert(results->end(), relevant_items.begin(),
71 relevant_items.end());
72 }
73 return true;
74 }
75
76 void CreateContextMenuParams(const GURL& source_url,
77 const std::string& file_url,
78 ContextMenuParams* params) {
79 DCHECK(params);
80 params->is_image_blocked = false;
81 params->spellcheck_enabled = false;
82 params->is_editable = false;
83 params->media_flags = 0;
84 params->edit_flags = 0;
85 params->media_type = WebKit::WebContextMenuData::MediaTypeFile;
86 params->src_url = GURL(file_url);
87 params->page_url = source_url;
88 }
89
90 // Given the list of selected files, returns array of context menu tasks
91 // that are sahred
92 bool FindCommonTasks(Profile* profile,
93 const GURL& source_url,
94 ListValue* files_list,
95 ExtensionMenuItem::List* common_tasks) {
96 common_tasks->clear();
97 for (size_t i = 0; i < files_list->GetSize(); ++i) {
98 std::string file_url;
99 if (!files_list->GetString(i, &file_url))
100 return false;
101
102 ContextMenuParams params;
103 CreateContextMenuParams(source_url, file_url, &params);
104
105 ExtensionMenuItem::List file_actions;
106 if (!GetContextMenuItems(profile, params, &file_actions))
107 return false;
108 // If there is nothing to do for one file, the intersection of tasks for all
109 // files will be empty at the end.
110 if (!file_actions.size()) {
111 common_tasks->clear();
112 return true;
113 }
114 // For the very first file, just copy elements.
115 if (i == 0) {
116 common_tasks->insert(common_tasks->begin(),
117 file_actions.begin(),
118 file_actions.end());
119 std::sort(common_tasks->begin(), common_tasks->end());
120 } else if (common_tasks->size()) {
121 // For all additional files, find intersection between the accumulated
122 // and file specific set.
123 std::sort(file_actions.begin(), file_actions.end());
124 ExtensionMenuItem::List intersection(common_tasks->size());
125 ExtensionMenuItem::List::iterator intersection_end =
126 std::set_intersection(common_tasks->begin(),
127 common_tasks->end(),
128 file_actions.begin(),
129 file_actions.end(),
130 intersection.begin());
131 common_tasks->clear();
132 common_tasks->insert(common_tasks->begin(),
133 intersection.begin(),
134 intersection_end);
135 std::sort(common_tasks->begin(), common_tasks->end());
136 }
137 }
138 return true;
139 }
140
141 // Breaks down task_id that is used between getFileTasks() and executeTask() on
142 // its building blocks. task_id field the following structure:
143 // <task-type>:<extension-id>/<task-action-id>
144 // Currently, the only supported task-type is of 'context'.
145 bool CrackTaskIdentifier(const std::string& task_id,
146 std::string* task_type,
147 std::string* target_extension_id,
148 std::string* action_id) {
149 std::string::size_type pos_col = task_id.find(':');
150 if (pos_col == std::string::npos)
151 return false;
152 *task_type = task_id.substr(0, pos_col);
153 std::string::size_type pos_slash = task_id.find('/');
154 if (pos_slash == std::string::npos)
155 return false;
156 *target_extension_id = task_id.substr(pos_col + 1, pos_slash - pos_col - 1);
157 *action_id = task_id.substr(pos_slash + 1);
158 return true;
159 }
160
161 std::string MakeTaskID(const char* task_schema,
162 const char* extension_id,
163 int action_id) {
164 return base::StringPrintf("%s:%s/%d", task_schema, extension_id, action_id);
165 }
166
167 // Generates hashes used for filesystem: urls that are sent to 3rd party
168 // extension. Hashes generated from extension id and file url.
169 // They are valid only during the lifetime of the browser instance.
170 class FileHashGenerator {
171 public:
172 ~FileHashGenerator() {}
173 static FileHashGenerator* GetInstance() {
174 return Singleton<FileHashGenerator>::get();
175 }
176 // Generate hash for given url and extension combination.
177 std::string GenerateFileHash(const std::string& file_url,
178 const std::string& extension_id) {
179 std::string data(file_url);
180 data = data.append(extension_id);
181 scoped_ptr<unsigned char> digest(new unsigned char[SHA1_SIZE_IN_BITS/8]);
182 if (!hmac_.Sign(data, digest.get(), SHA1_SIZE_IN_BITS/8))
183 return std::string();
184 std::string output;
185 if (!base::Base64Encode(std::string(reinterpret_cast<char*>(digest.get()),
186 SHA1_SIZE_IN_BITS/8),
187 &output))
188 return std::string();
189 return output;
190 }
191
192 private:
193 friend struct DefaultSingletonTraits<FileHashGenerator>;
194
195 FileHashGenerator() : hmac_(base::HMAC::SHA1) {
196 scoped_ptr<base::SymmetricKey> sym_key(
197 base::SymmetricKey::GenerateRandomKey(base::SymmetricKey::AES,
198 SHA1_SIZE_IN_BITS));
199 std::string raw_key;
200 sym_key->GetRawKey(&raw_key);
201 hmac_.Init(raw_key);
202 }
203
204 base::HMAC hmac_;
205 DISALLOW_COPY_AND_ASSIGN(FileHashGenerator);
206 };
15 207
16 class LocalFileSystemCallbackDispatcher 208 class LocalFileSystemCallbackDispatcher
17 : public fileapi::FileSystemCallbackDispatcher { 209 : public fileapi::FileSystemCallbackDispatcher {
18 public: 210 public:
19 explicit LocalFileSystemCallbackDispatcher( 211 explicit LocalFileSystemCallbackDispatcher(
20 RequestLocalFileSystemFunction* function) : function_(function) { 212 RequestLocalFileSystemFunctionBase* function,
213 Profile* profile,
214 int child_id,
215 const GURL& source_url,
216 const std::string& file_url)
217 : function_(function),
218 profile_(profile),
219 child_id_(child_id),
220 source_url_(source_url),
221 file_url_(file_url) {
21 DCHECK(function_); 222 DCHECK(function_);
22 } 223 }
23 // fileapi::FileSystemCallbackDispatcher overrides. 224 // fileapi::FileSystemCallbackDispatcher overrides.
24 virtual void DidSucceed() OVERRIDE { 225 virtual void DidSucceed() OVERRIDE {
25 NOTREACHED(); 226 NOTREACHED();
26 } 227 }
27 virtual void DidReadMetadata(const base::PlatformFileInfo& info, 228 virtual void DidReadMetadata(const base::PlatformFileInfo& info,
28 const FilePath& unused) OVERRIDE { 229 const FilePath& unused) OVERRIDE {
29 NOTREACHED(); 230 NOTREACHED();
30 } 231 }
31 virtual void DidReadDirectory( 232 virtual void DidReadDirectory(
32 const std::vector<base::FileUtilProxy::Entry>& entries, 233 const std::vector<base::FileUtilProxy::Entry>& entries,
33 bool has_more) OVERRIDE { 234 bool has_more) OVERRIDE {
34 NOTREACHED(); 235 NOTREACHED();
35 } 236 }
36 virtual void DidWrite(int64 bytes, bool complete) OVERRIDE { 237 virtual void DidWrite(int64 bytes, bool complete) OVERRIDE {
37 NOTREACHED(); 238 NOTREACHED();
38 } 239 }
39 virtual void DidOpenFileSystem(const std::string& name, 240 virtual void DidOpenFileSystem(const std::string& name,
40 const FilePath& path) OVERRIDE { 241 const FilePath& path) OVERRIDE {
242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
243 // Set up file permission access.
244 if (file_url_.length()) {
245 if (!SetupFileAccessPermissions()) {
246 DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
247 return;
248 }
249 }
250
41 BrowserThread::PostTask( 251 BrowserThread::PostTask(
42 BrowserThread::UI, FROM_HERE, 252 BrowserThread::UI, FROM_HERE,
43 NewRunnableMethod(function_, 253 NewRunnableMethod(function_,
44 &RequestLocalFileSystemFunction::RespondSuccessOnUIThread, 254 &RequestLocalFileSystemFunctionBase::RespondSuccessOnUIThread,
45 name, 255 name,
46 path)); 256 path,
257 file_url_));
47 } 258 }
48 virtual void DidFail(base::PlatformFileError error_code) OVERRIDE { 259 virtual void DidFail(base::PlatformFileError error_code) OVERRIDE {
49 BrowserThread::PostTask( 260 BrowserThread::PostTask(
50 BrowserThread::UI, FROM_HERE, 261 BrowserThread::UI, FROM_HERE,
51 NewRunnableMethod(function_, 262 NewRunnableMethod(function_,
52 &RequestLocalFileSystemFunction::RespondFailedOnUIThread, 263 &RequestLocalFileSystemFunctionBase::RespondFailedOnUIThread,
53 error_code)); 264 error_code));
54 } 265 }
55 private: 266 private:
56 RequestLocalFileSystemFunction* function_; 267
268 // Checks legitimacy of file url and grants access permissions.
269 bool SetupFileAccessPermissions() {
270 GURL file_origin_url;
271 FilePath virtual_path;
272 fileapi::FileSystemType type;
273 fileapi::FileSystemPathManager* path_manager =
274 profile_->GetFileSystemContext()->path_manager();
275
276 // Check file url hash first.
277 std::string::size_type pos = file_url_.find('#');
278 if (pos == std::string::npos)
279 return false;
280 std::string url_hash = file_url_.substr(pos + 1);
281 std::string file_url = file_url_.substr(0, pos);
282 std::string extension_id = source_url_.GetOrigin().host();
283 std::string expected_hash = FileHashGenerator::GetInstance()->
284 GenerateFileHash(file_url, extension_id);
285 if (expected_hash != url_hash)
286 return false;
287
288 if (!path_manager->CrackFileSystemPath(FilePath(file_url),
289 &file_origin_url,
290 &type,
291 &virtual_path)) {
292 return false;
293 }
294 // Make sure this url really used by the right caller extension.
295 if (source_url_.GetOrigin() != file_origin_url) {
296 DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
297 return false;
298 }
299 FilePath root_path = path_manager->GetFileSystemRootPathOnFileThread(
300 file_origin_url,
301 fileapi::kFileSystemTypeLocal,
302 FilePath(virtual_path),
303 false); // create
304 FilePath finalFilePath = root_path.Append(virtual_path);
305 // Grant read access permission to this file.
306 ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(child_id_,
307 finalFilePath);
308 return true;
309 }
310
311 RequestLocalFileSystemFunctionBase* function_;
312 Profile* profile_;
313 // Renderer process id.
314 int child_id_;
315 // Extension source URL.
316 GURL source_url_;
317 std::string file_url_;
57 DISALLOW_COPY_AND_ASSIGN(LocalFileSystemCallbackDispatcher); 318 DISALLOW_COPY_AND_ASSIGN(LocalFileSystemCallbackDispatcher);
58 }; 319 };
59 320
60 RequestLocalFileSystemFunction::RequestLocalFileSystemFunction() { 321 void RequestLocalFileSystemFunctionBase::RequestOnFileThread(
61 } 322 const GURL& source_url, const std::string& file_url) {
62
63 RequestLocalFileSystemFunction::~RequestLocalFileSystemFunction() {
64 }
65
66 bool RequestLocalFileSystemFunction::RunImpl() {
67 fileapi::FileSystemOperation* operation = 323 fileapi::FileSystemOperation* operation =
68 new fileapi::FileSystemOperation( 324 new fileapi::FileSystemOperation(
69 new LocalFileSystemCallbackDispatcher(this), 325 new LocalFileSystemCallbackDispatcher(
326 this,
327 profile(),
328 dispatcher()->render_view_host()->process()->id(),
329 source_url,
330 file_url),
70 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), 331 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
71 profile()->GetFileSystemContext(), 332 profile()->GetFileSystemContext(),
72 NULL); 333 NULL);
73 GURL origin_url = source_url().GetOrigin(); 334 GURL origin_url = source_url.GetOrigin();
74 operation->OpenFileSystem(origin_url, fileapi::kFileSystemTypeLocal, 335 operation->OpenFileSystem(origin_url, fileapi::kFileSystemTypeLocal,
75 false); // create 336 false); // create
337 }
338
339 bool RequestLocalFileSystemFunctionBase::RunImpl() {
340 std::string file_url;
341 if (args_->GetSize())
342 args_->GetString(0, &file_url);
343 BrowserThread::PostTask(
344 BrowserThread::FILE, FROM_HERE,
345 NewRunnableMethod(this,
346 &RequestLocalFileSystemFunctionBase::RequestOnFileThread,
347 source_url_,
348 file_url));
76 // Will finish asynchronously. 349 // Will finish asynchronously.
77 return true; 350 return true;
78 } 351 }
79 352
80 void RequestLocalFileSystemFunction::RespondSuccessOnUIThread( 353 void RequestLocalFileSystemFunctionBase::RespondSuccessOnUIThread(
81 const std::string& name, const FilePath& path) { 354 const std::string& name, const FilePath& path,
355 const std::string& file_url) {
82 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 356 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
83 result_.reset(new DictionaryValue()); 357 result_.reset(new DictionaryValue());
84 DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get()); 358 DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get());
85 dict->SetString("name", name); 359 dict->SetString("name", name);
86 dict->SetString("path", path.value()); 360 dict->SetString("path", path.value());
87 dict->SetInteger("error", base::PLATFORM_FILE_OK); 361 dict->SetInteger("error", base::PLATFORM_FILE_OK);
362 if (file_url.size())
363 dict->SetString("fileUrl", file_url);
88 SendResponse(true); 364 SendResponse(true);
89 } 365 }
90 366
91 void RequestLocalFileSystemFunction::RespondFailedOnUIThread( 367 void RequestLocalFileSystemFunctionBase::RespondFailedOnUIThread(
92 base::PlatformFileError error_code) { 368 base::PlatformFileError error_code) {
93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 369 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
94 result_.reset(new DictionaryValue()); 370 result_.reset(new DictionaryValue());
95 DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get()); 371 DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get());
96 dict->SetInteger("error", static_cast<int>(error_code)); 372 dict->SetInteger("error", static_cast<int>(error_code));
97 SendResponse(true); 373 SendResponse(true);
98 } 374 }
99 375
376 bool GetFileTasksFileBrowserFunction::RunImpl() {
377 ListValue* files_list = NULL;
378 if (!args_->GetList(0, &files_list))
379 return false;
380
381 result_.reset(new ListValue());
382 ListValue* result_list = reinterpret_cast<ListValue*>(result_.get());
383
384 ExtensionMenuItem::List common_tasks;
385 if (!FindCommonTasks(profile_, source_url_, files_list, &common_tasks))
386 return false;
387
388 ExtensionService* service = profile_->GetExtensionService();
389 for (ExtensionMenuItem::List::iterator iter = common_tasks.begin();
390 iter != common_tasks.end();
391 ++iter) {
392 if ((*iter)->type() != ExtensionMenuItem::NORMAL)
393 continue;
394 const std::string extension_id = (*iter)->extension_id();
395 const Extension* extension = service->GetExtensionById(extension_id, false);
396 if (!extension) {
397 LOG(WARNING) << "Disabled extension" << extension_id;
398 continue;
399 }
400 DictionaryValue* task = new DictionaryValue();
401 task->SetString("taskId", MakeTaskID(kContextTaskIdSchema,
402 extension_id.c_str(),
403 (*iter)->id().uid));
404 task->SetString("title", (*iter)->title());
405 GURL icon =
406 ExtensionIconSource::GetIconURL(extension,
407 Extension::EXTENSION_ICON_SMALLISH,
408 ExtensionIconSet::MATCH_BIGGER,
409 false); // grayscale
410 task->SetString("iconUrl", icon.spec());
411 result_list->Append(task);
412 }
413
414 // TODO(zelidrag, serya): Add intent content tasks to result_list once we
415 // implement that API.
416 SendResponse(true);
417 return true;
418 }
419
420 bool ExecuteTasksFileBrowserFunction::RunImpl() {
421 // First param is task id that was to the extension with getFileTasks call.
422 std::string task_id;
423 if (!args_->GetString(0, &task_id) || !task_id.size())
424 return false;
425
426 // The second param is the list of files that need to be executed with this
427 // task.
428 ListValue* files_list = NULL;
429 if (!args_->GetList(1, &files_list))
430 return false;
431
432 if (!files_list->GetSize())
433 return true;
434
435 std::string task_type;
436 std::string target_extension_id;
437 std::string action_id;
438 if (!CrackTaskIdentifier(task_id, &task_type, &target_extension_id,
439 &action_id)) {
440 return false;
441 }
442
443 if (task_type == kContextTaskIdSchema) {
444 ExecuteContextMenuTasks(target_extension_id, action_id, files_list);
445 } else {
446 LOG(WARNING) << "Unsupported task type of: " << task_type;
447 // TODO(zelidrag, serya): Add intent content tasks here once we implement
448 // that API.
449 return false;
450 }
451 SendResponse(true);
452 return true;
453 }
454
455 std::string ExecuteTasksFileBrowserFunction::MakeSafeFileUrl(
456 const std::string& origin_file_url, const std::string& extension_id) {
457 // Replace extension part of the url with one from the target.
458 std::string::size_type pos = origin_file_url.find("/local/");
459 if (pos == std::string::npos)
460 return std::string();
461 std::string file_url = base::StringPrintf(
462 "filesystem:chrome-extension://%s/%s",
463 extension_id.c_str(),
464 origin_file_url.substr(pos + 1).c_str());
465 std::string hash =
466 FileHashGenerator::GetInstance()->GenerateFileHash(file_url,
467 extension_id);
468 file_url = file_url.append("#");
469 return file_url.append(hash);
470 }
471
472 bool ExecuteTasksFileBrowserFunction::ExecuteContextMenuTasks(
473 const std::string& extension_id, const std::string& action_id,
474 ListValue* files_list) {
475 ExtensionMenuManager* manager =
476 profile_->GetExtensionService()->menu_manager();
477 for (size_t i = 0; i < files_list->GetSize(); i++) {
478 std::string origin_file_url;
479 if (!files_list->GetString(i, &origin_file_url)) {
480 result_.reset(new FundamentalValue(false));
481 SendResponse(true);
482 return false;
483 }
484 std::string file_url = MakeSafeFileUrl(origin_file_url, extension_id);
485 if (!file_url.size()) {
486 result_.reset(new FundamentalValue(false));
487 SendResponse(true);
488 return false;
489 }
490 ContextMenuParams params;
491 CreateContextMenuParams(source_url_, file_url, &params);
492 ExtensionMenuItem::Id menuItemId(profile_, extension_id,
493 atoi(action_id.c_str()));
494 manager->ExecuteCommand(profile_,
495 NULL, // tab_contents, not needed in args.
496 params,
497 menuItemId);
498 }
499 result_.reset(new FundamentalValue(true));
500 SendResponse(true);
501 return true;
502 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698