1 // Copyright (c) 2012 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/extensions/convert_web_app.h"
12 #include "base/base64.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/json/json_file_value_serializer.h"
17 #include "base/logging.h"
18 #include "base/numerics/safe_conversions.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "chrome/common/web_application_info.h"
24 #include "crypto/sha2.h"
25 #include "extensions/common/constants.h"
26 #include "extensions/common/extension.h"
27 #include "extensions/common/file_util.h"
28 #include "extensions/common/image_util.h"
29 #include "extensions/common/manifest_constants.h"
30 #include "third_party/skia/include/core/SkBitmap.h"
31 #include "ui/gfx/codec/png_codec.h"
34 namespace extensions
{
36 namespace keys
= manifest_keys
;
42 const char kIconsDirName
[] = "icons";
44 // Create the public key for the converted web app.
46 // Web apps are not signed, but the public key for an extension doubles as
47 // its unique identity, and we need one of those. A web app's unique identity
48 // is its manifest URL, so we hash that to create a public key. There will be
49 // no corresponding private key, which means that these extensions cannot be
50 // auto-updated using ExtensionUpdater.
51 std::string
GenerateKey(const GURL
& app_url
) {
52 char raw
[crypto::kSHA256Length
] = {0};
54 crypto::SHA256HashString(app_url
.spec().c_str(), raw
,
55 crypto::kSHA256Length
);
56 base::Base64Encode(std::string(raw
, crypto::kSHA256Length
), &key
);
62 // Generates a version for the converted app using the current date. This isn't
63 // really needed, but it seems like useful information.
64 std::string
ConvertTimeToExtensionVersion(const Time
& create_time
) {
65 Time::Exploded create_time_exploded
;
66 create_time
.UTCExplode(&create_time_exploded
);
68 double micros
= static_cast<double>(
69 (create_time_exploded
.millisecond
* Time::kMicrosecondsPerMillisecond
) +
70 (create_time_exploded
.second
* Time::kMicrosecondsPerSecond
) +
71 (create_time_exploded
.minute
* Time::kMicrosecondsPerMinute
) +
72 (create_time_exploded
.hour
* Time::kMicrosecondsPerHour
));
73 double day_fraction
= micros
/ Time::kMicrosecondsPerDay
;
74 double stamp
= day_fraction
* std::numeric_limits
<uint16
>::max();
76 // Ghetto-round, since VC++ doesn't have round().
77 stamp
= stamp
>= (floor(stamp
) + 0.5) ? (stamp
+ 1) : stamp
;
79 return base::StringPrintf("%i.%i.%i.%i",
80 create_time_exploded
.year
,
81 create_time_exploded
.month
,
82 create_time_exploded
.day_of_month
,
83 static_cast<uint16
>(stamp
));
86 scoped_refptr
<Extension
> ConvertWebAppToExtension(
87 const WebApplicationInfo
& web_app
,
88 const Time
& create_time
,
89 const base::FilePath
& extensions_dir
) {
90 base::FilePath install_temp_dir
=
91 file_util::GetInstallTempDir(extensions_dir
);
92 if (install_temp_dir
.empty()) {
93 LOG(ERROR
) << "Could not get path to profile temporary directory.";
97 base::ScopedTempDir temp_dir
;
98 if (!temp_dir
.CreateUniqueTempDirUnderPath(install_temp_dir
)) {
99 LOG(ERROR
) << "Could not create temporary directory.";
103 // Create the manifest
104 scoped_ptr
<base::DictionaryValue
> root(new base::DictionaryValue
);
105 root
->SetString(keys::kPublicKey
, GenerateKey(web_app
.app_url
));
106 root
->SetString(keys::kName
, base::UTF16ToUTF8(web_app
.title
));
107 root
->SetString(keys::kVersion
, ConvertTimeToExtensionVersion(create_time
));
108 root
->SetString(keys::kDescription
, base::UTF16ToUTF8(web_app
.description
));
109 root
->SetString(keys::kLaunchWebURL
, web_app
.app_url
.spec());
110 if (web_app
.generated_icon_color
!= SK_ColorTRANSPARENT
) {
111 root
->SetString(keys::kAppIconColor
, image_util::GenerateCSSColorString(
112 web_app
.generated_icon_color
));
115 // Add the icons and linked icon information.
116 base::DictionaryValue
* icons
= new base::DictionaryValue();
117 root
->Set(keys::kIcons
, icons
);
118 base::ListValue
* linked_icons
= new base::ListValue();
119 root
->Set(keys::kLinkedAppIcons
, linked_icons
);
120 for (const auto& icon
: web_app
.icons
) {
121 std::string size
= base::StringPrintf("%i", icon
.width
);
122 std::string icon_path
= base::StringPrintf("%s/%s.png", kIconsDirName
,
124 icons
->SetString(size
, icon_path
);
126 if (icon
.url
.is_valid()) {
127 base::DictionaryValue
* linked_icon
= new base::DictionaryValue();
128 linked_icon
->SetString(keys::kLinkedAppIconURL
, icon
.url
.spec());
129 linked_icon
->SetInteger(keys::kLinkedAppIconSize
, icon
.width
);
130 linked_icons
->Append(linked_icon
);
134 // Write the manifest.
135 base::FilePath manifest_path
= temp_dir
.path().Append(kManifestFilename
);
136 JSONFileValueSerializer
serializer(manifest_path
);
137 if (!serializer
.Serialize(*root
)) {
138 LOG(ERROR
) << "Could not serialize manifest.";
142 // Write the icon files.
143 base::FilePath icons_dir
= temp_dir
.path().AppendASCII(kIconsDirName
);
144 if (!base::CreateDirectory(icons_dir
)) {
145 LOG(ERROR
) << "Could not create icons directory.";
148 for (size_t i
= 0; i
< web_app
.icons
.size(); ++i
) {
149 // Skip unfetched bitmaps.
150 if (web_app
.icons
[i
].data
.colorType() == kUnknown_SkColorType
)
153 base::FilePath icon_file
= icons_dir
.AppendASCII(
154 base::StringPrintf("%i.png", web_app
.icons
[i
].width
));
155 std::vector
<unsigned char> image_data
;
156 if (!gfx::PNGCodec::EncodeBGRASkBitmap(web_app
.icons
[i
].data
,
159 LOG(ERROR
) << "Could not create icon file.";
163 const char* image_data_ptr
= reinterpret_cast<const char*>(&image_data
[0]);
164 int size
= base::checked_cast
<int>(image_data
.size());
165 if (base::WriteFile(icon_file
, image_data_ptr
, size
) != size
) {
166 LOG(ERROR
) << "Could not write icon file.";
171 // Finally, create the extension object to represent the unpacked directory.
173 scoped_refptr
<Extension
> extension
= Extension::Create(
177 Extension::FROM_BOOKMARK
,
179 if (!extension
.get()) {
184 temp_dir
.Take(); // The caller takes ownership of the directory.
188 } // namespace extensions