1 // Copyright 2014 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_external_updater.h"
8 #include "base/files/file_enumerator.h"
9 #include "base/files/file_util.h"
10 #include "base/json/json_file_value_serializer.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/version.h"
15 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
16 #include "chrome/browser/chromeos/ui/kiosk_external_update_notification.h"
17 #include "components/version_info/version_info.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "extensions/browser/sandboxed_unpacker.h"
20 #include "extensions/common/extension.h"
21 #include "grit/chromium_strings.h"
22 #include "grit/generated_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
30 const char kExternalUpdateManifest
[] = "external_update.json";
31 const char kExternalCrx
[] = "external_crx";
32 const char kExternalVersion
[] = "external_version";
34 void ParseExternalUpdateManifest(
35 const base::FilePath
& external_update_dir
,
36 base::DictionaryValue
* parsed_manifest
,
37 KioskExternalUpdater::ExternalUpdateErrorCode
* error_code
) {
38 base::FilePath manifest
=
39 external_update_dir
.AppendASCII(kExternalUpdateManifest
);
40 if (!base::PathExists(manifest
)) {
41 *error_code
= KioskExternalUpdater::ERROR_NO_MANIFEST
;
45 JSONFileValueDeserializer
deserializer(manifest
);
46 std::string error_msg
;
47 base::Value
* extensions
= deserializer
.Deserialize(NULL
, &error_msg
);
49 *error_code
= KioskExternalUpdater::ERROR_INVALID_MANIFEST
;
53 base::DictionaryValue
* dict_value
= NULL
;
54 if (!extensions
->GetAsDictionary(&dict_value
)) {
55 *error_code
= KioskExternalUpdater::ERROR_INVALID_MANIFEST
;
59 parsed_manifest
->Swap(dict_value
);
60 *error_code
= KioskExternalUpdater::ERROR_NONE
;
63 // Copies |external_crx_file| to |temp_crx_file|, and removes |temp_dir|
64 // created for unpacking |external_crx_file|.
65 void CopyExternalCrxAndDeleteTempDir(const base::FilePath
& external_crx_file
,
66 const base::FilePath
& temp_crx_file
,
67 const base::FilePath
& temp_dir
,
69 base::DeleteFile(temp_dir
, true);
70 *success
= base::CopyFile(external_crx_file
, temp_crx_file
);
73 // Returns true if |version_1| < |version_2|, and
74 // if |update_for_same_version| is true and |version_1| = |version_2|.
75 bool ShouldUpdateForHigherVersion(const std::string
& version_1
,
76 const std::string
& version_2
,
77 bool update_for_same_version
) {
78 const base::Version
v1(version_1
);
79 const base::Version
v2(version_2
);
80 if (!v1
.IsValid() || !v2
.IsValid())
82 int compare_result
= v1
.CompareTo(v2
);
83 if (compare_result
< 0)
85 else if (update_for_same_version
&& compare_result
== 0)
93 KioskExternalUpdater::ExternalUpdate::ExternalUpdate() {
96 KioskExternalUpdater::ExternalUpdate::~ExternalUpdate() {
99 KioskExternalUpdater::KioskExternalUpdater(
100 const scoped_refptr
<base::SequencedTaskRunner
>& backend_task_runner
,
101 const base::FilePath
& crx_cache_dir
,
102 const base::FilePath
& crx_unpack_dir
)
103 : backend_task_runner_(backend_task_runner
),
104 crx_cache_dir_(crx_cache_dir
),
105 crx_unpack_dir_(crx_unpack_dir
),
106 weak_factory_(this) {
107 // Subscribe to DiskMountManager.
108 DCHECK(disks::DiskMountManager::GetInstance());
109 disks::DiskMountManager::GetInstance()->AddObserver(this);
112 KioskExternalUpdater::~KioskExternalUpdater() {
113 if (disks::DiskMountManager::GetInstance())
114 disks::DiskMountManager::GetInstance()->RemoveObserver(this);
117 void KioskExternalUpdater::OnDiskEvent(
118 disks::DiskMountManager::DiskEvent event
,
119 const disks::DiskMountManager::Disk
* disk
) {
122 void KioskExternalUpdater::OnDeviceEvent(
123 disks::DiskMountManager::DeviceEvent event
,
124 const std::string
& device_path
) {
127 void KioskExternalUpdater::OnMountEvent(
128 disks::DiskMountManager::MountEvent event
,
129 MountError error_code
,
130 const disks::DiskMountManager::MountPointInfo
& mount_info
) {
131 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
132 if (mount_info
.mount_type
!= MOUNT_TYPE_DEVICE
||
133 error_code
!= MOUNT_ERROR_NONE
) {
137 if (event
== disks::DiskMountManager::MOUNTING
) {
138 // If multiple disks have been mounted, skip the rest of them if kiosk
139 // update has already been found.
140 if (!external_update_path_
.empty()) {
141 LOG(WARNING
) << "External update path already found, skip "
142 << mount_info
.mount_path
;
146 base::DictionaryValue
* parsed_manifest
= new base::DictionaryValue();
147 ExternalUpdateErrorCode
* parsing_error
= new ExternalUpdateErrorCode
;
148 backend_task_runner_
->PostTaskAndReply(
150 base::Bind(&ParseExternalUpdateManifest
,
151 base::FilePath(mount_info
.mount_path
),
154 base::Bind(&KioskExternalUpdater::ProcessParsedManifest
,
155 weak_factory_
.GetWeakPtr(),
156 base::Owned(parsing_error
),
157 base::FilePath(mount_info
.mount_path
),
158 base::Owned(parsed_manifest
)));
159 } else { // unmounting a removable device.
160 if (external_update_path_
.value().empty()) {
161 // Clear any previously displayed message.
162 DismissKioskUpdateNotification();
163 } else if (external_update_path_
.value() == mount_info
.mount_path
) {
164 DismissKioskUpdateNotification();
165 if (IsExternalUpdatePending()) {
166 LOG(ERROR
) << "External kiosk update is not completed when the usb "
167 "stick is unmoutned.";
169 external_updates_
.clear();
170 external_update_path_
.clear();
175 void KioskExternalUpdater::OnFormatEvent(
176 disks::DiskMountManager::FormatEvent event
,
177 FormatError error_code
,
178 const std::string
& device_path
) {
181 void KioskExternalUpdater::OnExtenalUpdateUnpackSuccess(
182 const std::string
& app_id
,
183 const std::string
& version
,
184 const std::string
& min_browser_version
,
185 const base::FilePath
& temp_dir
) {
186 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
188 // User might pull out the usb stick before updating is completed.
189 if (CheckExternalUpdateInterrupted())
192 if (!ShouldDoExternalUpdate(app_id
, version
, min_browser_version
)) {
193 external_updates_
[app_id
].update_status
= FAILED
;
194 MaybeValidateNextExternalUpdate();
198 // User might pull out the usb stick before updating is completed.
199 if (CheckExternalUpdateInterrupted())
202 base::FilePath external_crx_path
=
203 external_updates_
[app_id
].external_crx
.path
;
204 base::FilePath temp_crx_path
=
205 crx_unpack_dir_
.Append(external_crx_path
.BaseName());
206 bool* success
= new bool;
207 backend_task_runner_
->PostTaskAndReply(
209 base::Bind(&CopyExternalCrxAndDeleteTempDir
,
214 base::Bind(&KioskExternalUpdater::PutValidatedExtension
,
215 weak_factory_
.GetWeakPtr(),
216 base::Owned(success
),
222 void KioskExternalUpdater::OnExternalUpdateUnpackFailure(
223 const std::string
& app_id
) {
224 // User might pull out the usb stick before updating is completed.
225 if (CheckExternalUpdateInterrupted())
228 external_updates_
[app_id
].update_status
= FAILED
;
229 external_updates_
[app_id
].error
=
230 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
231 IDS_KIOSK_EXTERNAL_UPDATE_BAD_CRX
);
232 MaybeValidateNextExternalUpdate();
235 void KioskExternalUpdater::ProcessParsedManifest(
236 ExternalUpdateErrorCode
* parsing_error
,
237 const base::FilePath
& external_update_dir
,
238 base::DictionaryValue
* parsed_manifest
) {
239 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
241 if (*parsing_error
== ERROR_NO_MANIFEST
) {
242 KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(false);
244 } else if (*parsing_error
== ERROR_INVALID_MANIFEST
) {
245 NotifyKioskUpdateProgress(
246 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
247 IDS_KIOSK_EXTERNAL_UPDATE_INVALID_MANIFEST
));
248 KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(false);
252 NotifyKioskUpdateProgress(
253 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
254 IDS_KIOSK_EXTERNAL_UPDATE_IN_PROGRESS
));
256 external_update_path_
= external_update_dir
;
257 for (base::DictionaryValue::Iterator
it(*parsed_manifest
); !it
.IsAtEnd();
259 std::string app_id
= it
.key();
260 std::string cached_version_str
;
261 base::FilePath cached_crx
;
262 if (!KioskAppManager::Get()->GetCachedCrx(
263 app_id
, &cached_crx
, &cached_version_str
)) {
264 LOG(WARNING
) << "Can't find app in existing cache " << app_id
;
268 const base::DictionaryValue
* extension
= NULL
;
269 if (!it
.value().GetAsDictionary(&extension
)) {
270 LOG(ERROR
) << "Found bad entry in manifest type " << it
.value().GetType();
274 std::string external_crx_str
;
275 if (!extension
->GetString(kExternalCrx
, &external_crx_str
)) {
276 LOG(ERROR
) << "Can't find external crx in manifest " << app_id
;
280 std::string external_version_str
;
281 if (extension
->GetString(kExternalVersion
, &external_version_str
)) {
282 if (!ShouldUpdateForHigherVersion(
283 cached_version_str
, external_version_str
, false)) {
284 LOG(WARNING
) << "External app " << app_id
285 << " is at the same or lower version comparing to "
286 << " the existing one.";
291 ExternalUpdate update
;
292 KioskAppManager::App app
;
293 if (KioskAppManager::Get()->GetApp(app_id
, &app
)) {
294 update
.app_name
= app
.name
;
298 update
.external_crx
= extensions::CRXFileInfo(
299 app_id
, external_update_path_
.AppendASCII(external_crx_str
));
300 update
.update_status
= PENDING
;
301 external_updates_
[app_id
] = update
;
304 if (external_updates_
.empty()) {
305 NotifyKioskUpdateProgress(
306 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
307 IDS_KIOSK_EXTERNAL_UPDATE_NO_UPDATES
));
308 KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(false);
312 ValidateExternalUpdates();
315 bool KioskExternalUpdater::CheckExternalUpdateInterrupted() {
316 if (external_updates_
.empty()) {
317 // This could happen if user pulls out the usb stick before the updating
318 // operation is completed.
319 LOG(ERROR
) << "external_updates_ has been cleared before external "
320 << "updating completes.";
327 void KioskExternalUpdater::ValidateExternalUpdates() {
328 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
329 it
!= external_updates_
.end();
331 if (it
->second
.update_status
== PENDING
) {
332 scoped_refptr
<KioskExternalUpdateValidator
> crx_validator
=
333 new KioskExternalUpdateValidator(backend_task_runner_
,
334 it
->second
.external_crx
,
336 weak_factory_
.GetWeakPtr());
337 crx_validator
->Start();
343 bool KioskExternalUpdater::IsExternalUpdatePending() {
344 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
345 it
!= external_updates_
.end();
347 if (it
->second
.update_status
== PENDING
) {
354 bool KioskExternalUpdater::IsAllExternalUpdatesSucceeded() {
355 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
356 it
!= external_updates_
.end();
358 if (it
->second
.update_status
!= SUCCESS
) {
365 bool KioskExternalUpdater::ShouldDoExternalUpdate(
366 const std::string
& app_id
,
367 const std::string
& version
,
368 const std::string
& min_browser_version
) {
369 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
371 std::string existing_version_str
;
372 base::FilePath existing_path
;
373 bool cached
= KioskAppManager::Get()->GetCachedCrx(
374 app_id
, &existing_path
, &existing_version_str
);
377 // Compare app version.
378 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
379 if (!ShouldUpdateForHigherVersion(existing_version_str
, version
, false)) {
380 external_updates_
[app_id
].error
= rb
->GetLocalizedString(
381 IDS_KIOSK_EXTERNAL_UPDATE_SAME_OR_LOWER_APP_VERSION
);
385 // Check minimum browser version.
386 if (!min_browser_version
.empty()) {
387 if (!ShouldUpdateForHigherVersion(min_browser_version
,
388 version_info::GetVersionNumber(), true)) {
389 external_updates_
[app_id
].error
= l10n_util::GetStringFUTF16(
390 IDS_KIOSK_EXTERNAL_UPDATE_REQUIRE_HIGHER_BROWSER_VERSION
,
391 base::UTF8ToUTF16(min_browser_version
));
399 void KioskExternalUpdater::PutValidatedExtension(bool* crx_copied
,
400 const std::string
& app_id
,
401 const base::FilePath
& crx_file
,
402 const std::string
& version
) {
403 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
404 if (CheckExternalUpdateInterrupted())
408 LOG(ERROR
) << "Cannot copy external crx file to " << crx_file
.value();
409 external_updates_
[app_id
].update_status
= FAILED
;
410 external_updates_
[app_id
].error
= l10n_util::GetStringFUTF16(
411 IDS_KIOSK_EXTERNAL_UPDATE_FAILED_COPY_CRX_TO_TEMP
,
412 base::UTF8ToUTF16(crx_file
.value()));
413 MaybeValidateNextExternalUpdate();
417 chromeos::KioskAppManager::Get()->PutValidatedExternalExtension(
421 base::Bind(&KioskExternalUpdater::OnPutValidatedExtension
,
422 weak_factory_
.GetWeakPtr()));
425 void KioskExternalUpdater::OnPutValidatedExtension(const std::string
& app_id
,
427 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
428 if (CheckExternalUpdateInterrupted())
432 external_updates_
[app_id
].update_status
= FAILED
;
433 external_updates_
[app_id
].error
= l10n_util::GetStringFUTF16(
434 IDS_KIOSK_EXTERNAL_UPDATE_CANNOT_INSTALL_IN_LOCAL_CACHE
,
435 base::UTF8ToUTF16(external_updates_
[app_id
].external_crx
.path
.value()));
437 external_updates_
[app_id
].update_status
= SUCCESS
;
440 // Validate the next pending external update.
441 MaybeValidateNextExternalUpdate();
444 void KioskExternalUpdater::MaybeValidateNextExternalUpdate() {
445 if (IsExternalUpdatePending())
446 ValidateExternalUpdates();
448 MayBeNotifyKioskAppUpdate();
451 void KioskExternalUpdater::MayBeNotifyKioskAppUpdate() {
452 if (IsExternalUpdatePending())
455 NotifyKioskUpdateProgress(GetUpdateReportMessage());
456 NotifyKioskAppUpdateAvailable();
457 KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(
458 IsAllExternalUpdatesSucceeded());
461 void KioskExternalUpdater::NotifyKioskAppUpdateAvailable() {
462 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
463 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
464 it
!= external_updates_
.end();
466 if (it
->second
.update_status
== SUCCESS
) {
467 KioskAppManager::Get()->OnKioskAppCacheUpdated(it
->first
);
472 void KioskExternalUpdater::NotifyKioskUpdateProgress(
473 const base::string16
& message
) {
475 notification_
.reset(new KioskExternalUpdateNotification(message
));
477 notification_
->ShowMessage(message
);
480 void KioskExternalUpdater::DismissKioskUpdateNotification() {
481 if (notification_
.get()) {
482 notification_
.reset();
486 base::string16
KioskExternalUpdater::GetUpdateReportMessage() {
487 DCHECK(!IsExternalUpdatePending());
490 base::string16 updated_apps
;
491 base::string16 failed_apps
;
492 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
493 it
!= external_updates_
.end();
495 base::string16 app_name
= base::UTF8ToUTF16(it
->second
.app_name
);
496 if (it
->second
.update_status
== SUCCESS
) {
498 if (updated_apps
.empty())
499 updated_apps
= app_name
;
501 updated_apps
= updated_apps
+ base::ASCIIToUTF16(", ") + app_name
;
504 if (failed_apps
.empty()) {
505 failed_apps
= app_name
+ base::ASCIIToUTF16(": ") + it
->second
.error
;
507 failed_apps
= failed_apps
+ base::ASCIIToUTF16("\n") + app_name
+
508 base::ASCIIToUTF16(": ") + it
->second
.error
;
513 base::string16 message
;
514 message
= ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
515 IDS_KIOSK_EXTERNAL_UPDATE_COMPLETE
);
516 base::string16 success_app_msg
;
518 success_app_msg
= l10n_util::GetStringFUTF16(
519 IDS_KIOSK_EXTERNAL_UPDATE_SUCCESSFUL_UPDATED_APPS
, updated_apps
);
520 message
= message
+ base::ASCIIToUTF16("\n") + success_app_msg
;
523 base::string16 failed_app_msg
;
525 failed_app_msg
= ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
526 IDS_KIOSK_EXTERNAL_UPDATE_FAILED_UPDATED_APPS
) +
527 base::ASCIIToUTF16("\n") + failed_apps
;
528 message
= message
+ base::ASCIIToUTF16("\n") + failed_app_msg
;
533 } // namespace chromeos