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 "chrome/common/chrome_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 chrome::VersionInfo current_version_info
;
388 if (!ShouldUpdateForHigherVersion(
389 min_browser_version
, current_version_info
.Version(), true)) {
390 external_updates_
[app_id
].error
= l10n_util::GetStringFUTF16(
391 IDS_KIOSK_EXTERNAL_UPDATE_REQUIRE_HIGHER_BROWSER_VERSION
,
392 base::UTF8ToUTF16(min_browser_version
));
400 void KioskExternalUpdater::PutValidatedExtension(bool* crx_copied
,
401 const std::string
& app_id
,
402 const base::FilePath
& crx_file
,
403 const std::string
& version
) {
404 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
405 if (CheckExternalUpdateInterrupted())
409 LOG(ERROR
) << "Cannot copy external crx file to " << crx_file
.value();
410 external_updates_
[app_id
].update_status
= FAILED
;
411 external_updates_
[app_id
].error
= l10n_util::GetStringFUTF16(
412 IDS_KIOSK_EXTERNAL_UPDATE_FAILED_COPY_CRX_TO_TEMP
,
413 base::UTF8ToUTF16(crx_file
.value()));
414 MaybeValidateNextExternalUpdate();
418 chromeos::KioskAppManager::Get()->PutValidatedExternalExtension(
422 base::Bind(&KioskExternalUpdater::OnPutValidatedExtension
,
423 weak_factory_
.GetWeakPtr()));
426 void KioskExternalUpdater::OnPutValidatedExtension(const std::string
& app_id
,
428 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
429 if (CheckExternalUpdateInterrupted())
433 external_updates_
[app_id
].update_status
= FAILED
;
434 external_updates_
[app_id
].error
= l10n_util::GetStringFUTF16(
435 IDS_KIOSK_EXTERNAL_UPDATE_CANNOT_INSTALL_IN_LOCAL_CACHE
,
436 base::UTF8ToUTF16(external_updates_
[app_id
].external_crx
.path
.value()));
438 external_updates_
[app_id
].update_status
= SUCCESS
;
441 // Validate the next pending external update.
442 MaybeValidateNextExternalUpdate();
445 void KioskExternalUpdater::MaybeValidateNextExternalUpdate() {
446 if (IsExternalUpdatePending())
447 ValidateExternalUpdates();
449 MayBeNotifyKioskAppUpdate();
452 void KioskExternalUpdater::MayBeNotifyKioskAppUpdate() {
453 if (IsExternalUpdatePending())
456 NotifyKioskUpdateProgress(GetUpdateReportMessage());
457 NotifyKioskAppUpdateAvailable();
458 KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(
459 IsAllExternalUpdatesSucceeded());
462 void KioskExternalUpdater::NotifyKioskAppUpdateAvailable() {
463 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
464 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
465 it
!= external_updates_
.end();
467 if (it
->second
.update_status
== SUCCESS
) {
468 KioskAppManager::Get()->OnKioskAppCacheUpdated(it
->first
);
473 void KioskExternalUpdater::NotifyKioskUpdateProgress(
474 const base::string16
& message
) {
476 notification_
.reset(new KioskExternalUpdateNotification(message
));
478 notification_
->ShowMessage(message
);
481 void KioskExternalUpdater::DismissKioskUpdateNotification() {
482 if (notification_
.get()) {
483 notification_
.reset();
487 base::string16
KioskExternalUpdater::GetUpdateReportMessage() {
488 DCHECK(!IsExternalUpdatePending());
491 base::string16 updated_apps
;
492 base::string16 failed_apps
;
493 for (ExternalUpdateMap::iterator it
= external_updates_
.begin();
494 it
!= external_updates_
.end();
496 base::string16 app_name
= base::UTF8ToUTF16(it
->second
.app_name
);
497 if (it
->second
.update_status
== SUCCESS
) {
499 if (updated_apps
.empty())
500 updated_apps
= app_name
;
502 updated_apps
= updated_apps
+ base::ASCIIToUTF16(", ") + app_name
;
505 if (failed_apps
.empty()) {
506 failed_apps
= app_name
+ base::ASCIIToUTF16(": ") + it
->second
.error
;
508 failed_apps
= failed_apps
+ base::ASCIIToUTF16("\n") + app_name
+
509 base::ASCIIToUTF16(": ") + it
->second
.error
;
514 base::string16 message
;
515 message
= ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
516 IDS_KIOSK_EXTERNAL_UPDATE_COMPLETE
);
517 base::string16 success_app_msg
;
519 success_app_msg
= l10n_util::GetStringFUTF16(
520 IDS_KIOSK_EXTERNAL_UPDATE_SUCCESSFUL_UPDATED_APPS
, updated_apps
);
521 message
= message
+ base::ASCIIToUTF16("\n") + success_app_msg
;
524 base::string16 failed_app_msg
;
526 failed_app_msg
= ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
527 IDS_KIOSK_EXTERNAL_UPDATE_FAILED_UPDATED_APPS
) +
528 base::ASCIIToUTF16("\n") + failed_apps
;
529 message
= message
+ base::ASCIIToUTF16("\n") + failed_app_msg
;
534 } // namespace chromeos