Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / chromeos / app_mode / kiosk_app_data.cc
blob5245aad495940b703167993d63812f8453df3f6a
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.
5 #include "chrome/browser/chromeos/app_mode/kiosk_app_data.h"
7 #include <vector>
9 #include "base/bind.h"
10 #include "base/files/file_util.h"
11 #include "base/json/json_writer.h"
12 #include "base/memory/ref_counted_memory.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/threading/sequenced_worker_pool.h"
16 #include "base/values.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chromeos/app_mode/kiosk_app_data_delegate.h"
19 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_util.h"
22 #include "chrome/browser/extensions/webstore_data_fetcher.h"
23 #include "chrome/browser/extensions/webstore_install_helper.h"
24 #include "chrome/browser/image_decoder.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/browser/image_loader.h"
29 #include "extensions/browser/sandboxed_unpacker.h"
30 #include "extensions/common/constants.h"
31 #include "extensions/common/extension_urls.h"
32 #include "extensions/common/manifest.h"
33 #include "extensions/common/manifest_constants.h"
34 #include "extensions/common/manifest_handlers/icons_handler.h"
35 #include "ui/gfx/codec/png_codec.h"
36 #include "ui/gfx/image/image.h"
38 using content::BrowserThread;
40 namespace chromeos {
42 namespace {
44 // Keys for local state data. See sample layout in KioskAppManager.
45 const char kKeyName[] = "name";
46 const char kKeyIcon[] = "icon";
48 const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
50 // Icon file extension.
51 const char kIconFileExtension[] = ".png";
53 // Save |raw_icon| for given |app_id|.
54 void SaveIconToLocalOnBlockingPool(
55 const base::FilePath& icon_path,
56 scoped_refptr<base::RefCountedString> raw_icon) {
57 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
59 base::FilePath dir = icon_path.DirName();
60 if (!base::PathExists(dir))
61 CHECK(base::CreateDirectory(dir));
63 CHECK_EQ(static_cast<int>(raw_icon->size()),
64 base::WriteFile(icon_path,
65 raw_icon->data().c_str(), raw_icon->size()));
68 // Returns true for valid kiosk app manifest.
69 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
70 bool kiosk_enabled;
71 if (manifest.GetBoolean(extensions::manifest_keys::kKioskEnabled,
72 &kiosk_enabled)) {
73 return kiosk_enabled;
76 return false;
79 std::string ValueToString(const base::Value* value) {
80 std::string json;
81 base::JSONWriter::Write(value, &json);
82 return json;
85 } // namespace
87 ////////////////////////////////////////////////////////////////////////////////
88 // KioskAppData::CrxLoader
89 // Loads meta data from crx file.
91 class KioskAppData::CrxLoader : public extensions::SandboxedUnpackerClient {
92 public:
93 CrxLoader(const base::WeakPtr<KioskAppData>& client,
94 const base::FilePath& crx_file)
95 : client_(client),
96 crx_file_(crx_file),
97 success_(false) {
100 void Start() {
101 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
102 base::SequencedWorkerPool::SequenceToken token =
103 pool->GetNamedSequenceToken("KioskAppData.CrxLoaderWorker");
104 task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
105 token,
106 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
107 task_runner_->PostTask(FROM_HERE,
108 base::Bind(&CrxLoader::StartOnBlockingPool, this));
111 bool success() const { return success_; }
112 const base::FilePath& crx_file() const { return crx_file_; }
113 const std::string& name() const { return name_; }
114 const SkBitmap& icon() const { return icon_; }
116 private:
117 ~CrxLoader() override {};
119 // extensions::SandboxedUnpackerClient
120 void OnUnpackSuccess(const base::FilePath& temp_dir,
121 const base::FilePath& extension_root,
122 const base::DictionaryValue* original_manifest,
123 const extensions::Extension* extension,
124 const SkBitmap& install_icon) override {
125 DCHECK(task_runner_->RunsTasksOnCurrentThread());
127 success_ = true;
128 name_ = extension->name();
129 icon_ = install_icon;
130 NotifyFinishedOnBlockingPool();
132 void OnUnpackFailure(const extensions::CrxInstallError& error) override {
133 DCHECK(task_runner_->RunsTasksOnCurrentThread());
135 success_ = false;
136 NotifyFinishedOnBlockingPool();
139 void StartOnBlockingPool() {
140 DCHECK(task_runner_->RunsTasksOnCurrentThread());
142 if (!temp_dir_.CreateUniqueTempDir()) {
143 success_ = false;
144 NotifyFinishedOnBlockingPool();
145 return;
148 scoped_refptr<extensions::SandboxedUnpacker> unpacker(
149 new extensions::SandboxedUnpacker(
150 extensions::CRXFileInfo(crx_file_), extensions::Manifest::INTERNAL,
151 extensions::Extension::NO_FLAGS, temp_dir_.path(),
152 task_runner_.get(), this));
153 unpacker->Start();
156 void NotifyFinishedOnBlockingPool() {
157 DCHECK(task_runner_->RunsTasksOnCurrentThread());
159 if (!temp_dir_.Delete()) {
160 LOG(WARNING) << "Can not delete temp directory at "
161 << temp_dir_.path().value();
164 BrowserThread::PostTask(
165 BrowserThread::UI, FROM_HERE,
166 base::Bind(&CrxLoader::NotifyFinishedOnUIThread, this));
169 void NotifyFinishedOnUIThread() {
170 DCHECK_CURRENTLY_ON(BrowserThread::UI);
172 if (client_)
173 client_->OnCrxLoadFinished(this);
176 base::WeakPtr<KioskAppData> client_;
177 base::FilePath crx_file_;
178 bool success_;
180 scoped_refptr<base::SequencedTaskRunner> task_runner_;
181 base::ScopedTempDir temp_dir_;
183 // Extracted meta data.
184 std::string name_;
185 SkBitmap icon_;
187 DISALLOW_COPY_AND_ASSIGN(CrxLoader);
190 ////////////////////////////////////////////////////////////////////////////////
191 // KioskAppData::IconLoader
192 // Loads locally stored icon data and decode it.
194 class KioskAppData::IconLoader {
195 public:
196 enum LoadResult {
197 SUCCESS,
198 FAILED_TO_LOAD,
199 FAILED_TO_DECODE,
202 IconLoader(const base::WeakPtr<KioskAppData>& client,
203 const base::FilePath& icon_path)
204 : client_(client),
205 icon_path_(icon_path),
206 load_result_(SUCCESS) {}
208 void Start() {
209 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
210 base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
211 task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
212 token,
213 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
214 task_runner_->PostTask(FROM_HERE,
215 base::Bind(&IconLoader::LoadOnBlockingPool,
216 base::Unretained(this)));
219 private:
220 friend class base::RefCountedThreadSafe<IconLoader>;
222 ~IconLoader() {}
224 class IconImageRequest : public ImageDecoder::ImageRequest {
225 public:
226 IconImageRequest(
227 const scoped_refptr<base::SequencedTaskRunner>& task_runner,
228 IconLoader* icon_loader)
229 : ImageRequest(task_runner), icon_loader_(icon_loader) {}
231 void OnImageDecoded(const SkBitmap& decoded_image) override {
232 icon_loader_->icon_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image);
233 icon_loader_->icon_.MakeThreadSafe();
234 icon_loader_->ReportResultOnBlockingPool(SUCCESS);
235 delete this;
238 void OnDecodeImageFailed() override {
239 icon_loader_->ReportResultOnBlockingPool(FAILED_TO_DECODE);
240 delete this;
243 private:
244 ~IconImageRequest() override {}
245 IconLoader* icon_loader_;
248 // Loads the icon from locally stored |icon_path_| on the blocking pool
249 void LoadOnBlockingPool() {
250 DCHECK(task_runner_->RunsTasksOnCurrentThread());
252 std::string data;
253 if (!base::ReadFileToString(base::FilePath(icon_path_), &data)) {
254 ReportResultOnBlockingPool(FAILED_TO_LOAD);
255 return;
257 raw_icon_ = base::RefCountedString::TakeString(&data);
259 IconImageRequest* image_request = new IconImageRequest(task_runner_, this);
260 ImageDecoder::Start(image_request, raw_icon_->data());
263 void ReportResultOnBlockingPool(LoadResult result) {
264 DCHECK(task_runner_->RunsTasksOnCurrentThread());
266 load_result_ = result;
267 BrowserThread::PostTask(
268 BrowserThread::UI,
269 FROM_HERE,
270 base::Bind(&IconLoader::ReportResultOnUIThread,
271 base::Unretained(this)));
274 void NotifyClient() {
275 if (!client_)
276 return;
278 if (load_result_ == SUCCESS)
279 client_->OnIconLoadSuccess(icon_);
280 else
281 client_->OnIconLoadFailure();
284 void ReportResultOnUIThread() {
285 DCHECK_CURRENTLY_ON(BrowserThread::UI);
287 NotifyClient();
288 delete this;
291 base::WeakPtr<KioskAppData> client_;
292 base::FilePath icon_path_;
294 LoadResult load_result_;
295 scoped_refptr<base::SequencedTaskRunner> task_runner_;
297 gfx::ImageSkia icon_;
298 scoped_refptr<base::RefCountedString> raw_icon_;
300 DISALLOW_COPY_AND_ASSIGN(IconLoader);
303 ////////////////////////////////////////////////////////////////////////////////
304 // KioskAppData::WebstoreDataParser
305 // Use WebstoreInstallHelper to parse the manifest and decode the icon.
307 class KioskAppData::WebstoreDataParser
308 : public extensions::WebstoreInstallHelper::Delegate {
309 public:
310 explicit WebstoreDataParser(const base::WeakPtr<KioskAppData>& client)
311 : client_(client) {}
313 void Start(const std::string& app_id,
314 const std::string& manifest,
315 const GURL& icon_url,
316 net::URLRequestContextGetter* context_getter) {
317 scoped_refptr<extensions::WebstoreInstallHelper> webstore_helper =
318 new extensions::WebstoreInstallHelper(this,
319 app_id,
320 manifest,
321 icon_url,
322 context_getter);
323 webstore_helper->Start();
326 private:
327 friend class base::RefCounted<WebstoreDataParser>;
329 ~WebstoreDataParser() override {}
331 void ReportFailure() {
332 if (client_)
333 client_->OnWebstoreParseFailure();
335 delete this;
338 // WebstoreInstallHelper::Delegate overrides:
339 void OnWebstoreParseSuccess(const std::string& id,
340 const SkBitmap& icon,
341 base::DictionaryValue* parsed_manifest) override {
342 // Takes ownership of |parsed_manifest|.
343 extensions::Manifest manifest(
344 extensions::Manifest::INVALID_LOCATION,
345 scoped_ptr<base::DictionaryValue>(parsed_manifest));
347 if (!IsValidKioskAppManifest(manifest)) {
348 ReportFailure();
349 return;
352 if (client_)
353 client_->OnWebstoreParseSuccess(icon);
354 delete this;
356 void OnWebstoreParseFailure(const std::string& id,
357 InstallHelperResultCode result_code,
358 const std::string& error_message) override {
359 ReportFailure();
362 base::WeakPtr<KioskAppData> client_;
364 DISALLOW_COPY_AND_ASSIGN(WebstoreDataParser);
367 ////////////////////////////////////////////////////////////////////////////////
368 // KioskAppData
370 KioskAppData::KioskAppData(KioskAppDataDelegate* delegate,
371 const std::string& app_id,
372 const std::string& user_id,
373 const GURL& update_url)
374 : delegate_(delegate),
375 status_(STATUS_INIT),
376 app_id_(app_id),
377 user_id_(user_id),
378 update_url_(update_url) {
381 KioskAppData::~KioskAppData() {}
383 void KioskAppData::Load() {
384 SetStatus(STATUS_LOADING);
386 if (LoadFromCache())
387 return;
389 StartFetch();
392 void KioskAppData::ClearCache() {
393 PrefService* local_state = g_browser_process->local_state();
395 DictionaryPrefUpdate dict_update(local_state,
396 KioskAppManager::kKioskDictionaryName);
398 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
399 dict_update->Remove(app_key, NULL);
401 if (!icon_path_.empty()) {
402 BrowserThread::PostBlockingPoolTask(
403 FROM_HERE,
404 base::Bind(base::IgnoreResult(&base::DeleteFile), icon_path_, false));
408 void KioskAppData::LoadFromInstalledApp(Profile* profile,
409 const extensions::Extension* app) {
410 SetStatus(STATUS_LOADING);
412 if (!app) {
413 app = extensions::ExtensionSystem::Get(profile)
414 ->extension_service()
415 ->GetInstalledExtension(app_id_);
418 DCHECK_EQ(app_id_, app->id());
420 name_ = app->name();
422 const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
423 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
424 app, kIconSize, ExtensionIconSet::MATCH_BIGGER);
425 extensions::ImageLoader::Get(profile)->LoadImageAsync(
426 app, image, gfx::Size(kIconSize, kIconSize),
427 base::Bind(&KioskAppData::OnExtensionIconLoaded, AsWeakPtr()));
430 void KioskAppData::SetCachedCrx(const base::FilePath& crx_file) {
431 if (crx_file_ == crx_file)
432 return;
434 crx_file_ = crx_file;
435 MaybeLoadFromCrx();
438 bool KioskAppData::IsLoading() const {
439 return status_ == STATUS_LOADING;
442 bool KioskAppData::IsFromWebStore() const {
443 return update_url_.is_empty() ||
444 extension_urls::IsWebstoreUpdateUrl(update_url_);
447 void KioskAppData::SetStatus(Status status) {
448 if (status_ == status)
449 return;
451 status_ = status;
453 if (!delegate_)
454 return;
456 switch (status_) {
457 case STATUS_INIT:
458 break;
459 case STATUS_LOADING:
460 case STATUS_LOADED:
461 delegate_->OnKioskAppDataChanged(app_id_);
462 break;
463 case STATUS_ERROR:
464 delegate_->OnKioskAppDataLoadFailure(app_id_);
465 break;
469 net::URLRequestContextGetter* KioskAppData::GetRequestContextGetter() {
470 return g_browser_process->system_request_context();
473 bool KioskAppData::LoadFromCache() {
474 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
475 std::string name_key = app_key + '.' + kKeyName;
476 std::string icon_path_key = app_key + '.' + kKeyIcon;
478 PrefService* local_state = g_browser_process->local_state();
479 const base::DictionaryValue* dict =
480 local_state->GetDictionary(KioskAppManager::kKioskDictionaryName);
482 icon_path_.clear();
483 std::string icon_path_string;
484 if (!dict->GetString(name_key, &name_) ||
485 !dict->GetString(icon_path_key, &icon_path_string)) {
486 return false;
488 icon_path_ = base::FilePath(icon_path_string);
490 // IconLoader deletes itself when done.
491 (new IconLoader(AsWeakPtr(), icon_path_))->Start();
492 return true;
495 void KioskAppData::SetCache(const std::string& name,
496 const base::FilePath& icon_path) {
497 std::string app_key = std::string(KioskAppManager::kKeyApps) + '.' + app_id_;
498 std::string name_key = app_key + '.' + kKeyName;
499 std::string icon_path_key = app_key + '.' + kKeyIcon;
501 PrefService* local_state = g_browser_process->local_state();
502 DictionaryPrefUpdate dict_update(local_state,
503 KioskAppManager::kKioskDictionaryName);
504 dict_update->SetString(name_key, name);
505 dict_update->SetString(icon_path_key, icon_path.value());
506 icon_path_ = icon_path;
509 void KioskAppData::SetCache(const std::string& name, const SkBitmap& icon) {
510 name_ = name;
512 icon_ = gfx::ImageSkia::CreateFrom1xBitmap(icon);
513 icon_.MakeThreadSafe();
515 std::vector<unsigned char> image_data;
516 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &image_data));
517 scoped_refptr<base::RefCountedString> raw_icon(new base::RefCountedString);
518 raw_icon->data().assign(image_data.begin(), image_data.end());
520 base::FilePath cache_dir;
521 if (delegate_)
522 delegate_->GetKioskAppIconCacheDir(&cache_dir);
524 base::FilePath icon_path =
525 cache_dir.AppendASCII(app_id_).AddExtension(kIconFileExtension);
526 BrowserThread::GetBlockingPool()->PostTask(
527 FROM_HERE,
528 base::Bind(&SaveIconToLocalOnBlockingPool, icon_path, raw_icon));
530 SetCache(name, icon_path);
533 void KioskAppData::OnExtensionIconLoaded(const gfx::Image& icon) {
534 if (icon.IsEmpty()) {
535 LOG(WARNING) << "Failed to load icon from installed app"
536 << ", id=" << app_id_;
537 SetCache(name_, *extensions::util::GetDefaultAppIcon().bitmap());
538 } else {
539 SetCache(name_, icon.AsBitmap());
542 SetStatus(STATUS_LOADED);
545 void KioskAppData::OnIconLoadSuccess(const gfx::ImageSkia& icon) {
546 DCHECK_CURRENTLY_ON(BrowserThread::UI);
547 icon_ = icon;
548 SetStatus(STATUS_LOADED);
551 void KioskAppData::OnIconLoadFailure() {
552 // Re-fetch data from web store when failed to load cached data.
553 StartFetch();
556 void KioskAppData::OnWebstoreParseSuccess(const SkBitmap& icon) {
557 SetCache(name_, icon);
558 SetStatus(STATUS_LOADED);
561 void KioskAppData::OnWebstoreParseFailure() {
562 SetStatus(STATUS_ERROR);
565 void KioskAppData::StartFetch() {
566 if (!IsFromWebStore()) {
567 MaybeLoadFromCrx();
568 return;
571 webstore_fetcher_.reset(new extensions::WebstoreDataFetcher(
572 this,
573 GetRequestContextGetter(),
574 GURL(),
575 app_id_));
576 webstore_fetcher_->set_max_auto_retries(3);
577 webstore_fetcher_->Start();
580 void KioskAppData::OnWebstoreRequestFailure() {
581 SetStatus(STATUS_ERROR);
584 void KioskAppData::OnWebstoreResponseParseSuccess(
585 scoped_ptr<base::DictionaryValue> webstore_data) {
586 // Takes ownership of |webstore_data|.
587 webstore_fetcher_.reset();
589 std::string manifest;
590 if (!CheckResponseKeyValue(webstore_data.get(), kManifestKey, &manifest))
591 return;
593 if (!CheckResponseKeyValue(webstore_data.get(), kLocalizedNameKey, &name_))
594 return;
596 std::string icon_url_string;
597 if (!CheckResponseKeyValue(webstore_data.get(), kIconUrlKey,
598 &icon_url_string))
599 return;
601 GURL icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
602 icon_url_string);
603 if (!icon_url.is_valid()) {
604 LOG(ERROR) << "Webstore response error (icon url): "
605 << ValueToString(webstore_data.get());
606 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
607 return;
610 // WebstoreDataParser deletes itself when done.
611 (new WebstoreDataParser(AsWeakPtr()))->Start(app_id_,
612 manifest,
613 icon_url,
614 GetRequestContextGetter());
617 void KioskAppData::OnWebstoreResponseParseFailure(const std::string& error) {
618 LOG(ERROR) << "Webstore failed for kiosk app " << app_id_
619 << ", " << error;
620 webstore_fetcher_.reset();
621 SetStatus(STATUS_ERROR);
624 bool KioskAppData::CheckResponseKeyValue(const base::DictionaryValue* response,
625 const char* key,
626 std::string* value) {
627 if (!response->GetString(key, value)) {
628 LOG(ERROR) << "Webstore response error (" << key
629 << "): " << ValueToString(response);
630 OnWebstoreResponseParseFailure(kInvalidWebstoreResponseError);
631 return false;
633 return true;
636 void KioskAppData::MaybeLoadFromCrx() {
637 if (status_ == STATUS_LOADED || crx_file_.empty())
638 return;
640 scoped_refptr<CrxLoader> crx_loader(new CrxLoader(AsWeakPtr(), crx_file_));
641 crx_loader->Start();
644 void KioskAppData::OnCrxLoadFinished(const CrxLoader* crx_loader) {
645 DCHECK(crx_loader);
647 if (crx_loader->crx_file() != crx_file_)
648 return;
650 if (!crx_loader->success()) {
651 SetStatus(STATUS_ERROR);
652 return;
655 SkBitmap icon = crx_loader->icon();
656 if (icon.empty())
657 icon = *extensions::util::GetDefaultAppIcon().bitmap();
658 SetCache(crx_loader->name(), icon);
660 SetStatus(STATUS_LOADED);
663 } // namespace chromeos