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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/extensions/convert_web_app.h" 6 7 #include <cmath> 8 #include <limits> 9 #include <string> 10 #include <vector> 11 12 #include "base/base64.h" 13 #include "base/file_path.h" 14 #include "base/file_util.h" 15 #include "base/logging.h" 16 #include "base/memory/scoped_temp_dir.h" 17 #include "base/path_service.h" 18 #include "base/stringprintf.h" 19 #include "base/time.h" 20 #include "base/utf_string_conversions.h" 21 #include "crypto/sha2.h" 22 #include "chrome/common/chrome_paths.h" 23 #include "chrome/common/extensions/extension.h" 24 #include "chrome/common/extensions/extension_constants.h" 25 #include "chrome/common/extensions/extension_file_util.h" 26 #include "chrome/common/web_apps.h" 27 #include "content/common/json_value_serializer.h" 28 #include "googleurl/src/gurl.h" 29 #include "third_party/skia/include/core/SkBitmap.h" 30 #include "ui/gfx/codec/png_codec.h" 31 32 namespace keys = extension_manifest_keys; 33 34 using base::Time; 35 36 namespace { 37 38 const char kIconsDirName[] = "icons"; 39 40 // Create the public key for the converted web app. 41 // 42 // Web apps are not signed, but the public key for an extension doubles as 43 // its unique identity, and we need one of those. A web app's unique identity 44 // is its manifest URL, so we hash that to create a public key. There will be 45 // no corresponding private key, which means that these extensions cannot be 46 // auto-updated using ExtensionUpdater. But Chrome does notice updates to the 47 // manifest and regenerates these extensions. 48 std::string GenerateKey(const GURL& manifest_url) { 49 char raw[crypto::SHA256_LENGTH] = {0}; 50 std::string key; 51 crypto::SHA256HashString(manifest_url.spec().c_str(), 52 raw, 53 crypto::SHA256_LENGTH); 54 base::Base64Encode(std::string(raw, crypto::SHA256_LENGTH), &key); 55 return key; 56 } 57 58 } 59 60 61 // Generates a version for the converted app using the current date. This isn't 62 // really needed, but it seems like useful information. 63 std::string ConvertTimeToExtensionVersion(const Time& create_time) { 64 Time::Exploded create_time_exploded; 65 create_time.UTCExplode(&create_time_exploded); 66 67 double micros = static_cast<double>( 68 (create_time_exploded.millisecond * Time::kMicrosecondsPerMillisecond) + 69 (create_time_exploded.second * Time::kMicrosecondsPerSecond) + 70 (create_time_exploded.minute * Time::kMicrosecondsPerMinute) + 71 (create_time_exploded.hour * Time::kMicrosecondsPerHour)); 72 double day_fraction = micros / Time::kMicrosecondsPerDay; 73 double stamp = day_fraction * std::numeric_limits<uint16>::max(); 74 75 // Ghetto-round, since VC++ doesn't have round(). 76 stamp = stamp >= (floor(stamp) + 0.5) ? (stamp + 1) : stamp; 77 78 return base::StringPrintf("%i.%i.%i.%i", 79 create_time_exploded.year, 80 create_time_exploded.month, 81 create_time_exploded.day_of_month, 82 static_cast<uint16>(stamp)); 83 } 84 85 scoped_refptr<Extension> ConvertWebAppToExtension( 86 const WebApplicationInfo& web_app, 87 const Time& create_time) { 88 FilePath user_data_temp_dir = extension_file_util::GetUserDataTempDir(); 89 if (user_data_temp_dir.empty()) { 90 LOG(ERROR) << "Could not get path to profile temporary directory."; 91 return NULL; 92 } 93 94 ScopedTempDir temp_dir; 95 if (!temp_dir.CreateUniqueTempDirUnderPath(user_data_temp_dir)) { 96 LOG(ERROR) << "Could not create temporary directory."; 97 return NULL; 98 } 99 100 // Create the manifest 101 scoped_ptr<DictionaryValue> root(new DictionaryValue); 102 root->SetString(keys::kPublicKey, GenerateKey(web_app.manifest_url)); 103 root->SetString(keys::kName, UTF16ToUTF8(web_app.title)); 104 root->SetString(keys::kVersion, ConvertTimeToExtensionVersion(create_time)); 105 root->SetString(keys::kDescription, UTF16ToUTF8(web_app.description)); 106 root->SetString(keys::kLaunchWebURL, web_app.app_url.spec()); 107 108 if (!web_app.launch_container.empty()) 109 root->SetString(keys::kLaunchContainer, web_app.launch_container); 110 111 // Add the icons. 112 DictionaryValue* icons = new DictionaryValue(); 113 root->Set(keys::kIcons, icons); 114 for (size_t i = 0; i < web_app.icons.size(); ++i) { 115 std::string size = StringPrintf("%i", web_app.icons[i].width); 116 std::string icon_path = StringPrintf("%s/%s.png", kIconsDirName, 117 size.c_str()); 118 icons->SetString(size, icon_path); 119 } 120 121 // Add the permissions. 122 ListValue* permissions = new ListValue(); 123 root->Set(keys::kPermissions, permissions); 124 for (size_t i = 0; i < web_app.permissions.size(); ++i) { 125 permissions->Append(Value::CreateStringValue(web_app.permissions[i])); 126 } 127 128 // Add the URLs. 129 ListValue* urls = new ListValue(); 130 root->Set(keys::kWebURLs, urls); 131 for (size_t i = 0; i < web_app.urls.size(); ++i) { 132 urls->Append(Value::CreateStringValue(web_app.urls[i].spec())); 133 } 134 135 // Write the manifest. 136 FilePath manifest_path = temp_dir.path().Append( 137 Extension::kManifestFilename); 138 JSONFileValueSerializer serializer(manifest_path); 139 if (!serializer.Serialize(*root)) { 140 LOG(ERROR) << "Could not serialize manifest."; 141 return NULL; 142 } 143 144 // Write the icon files. 145 FilePath icons_dir = temp_dir.path().AppendASCII(kIconsDirName); 146 if (!file_util::CreateDirectory(icons_dir)) { 147 LOG(ERROR) << "Could not create icons directory."; 148 return NULL; 149 } 150 for (size_t i = 0; i < web_app.icons.size(); ++i) { 151 FilePath icon_file = icons_dir.AppendASCII( 152 StringPrintf("%i.png", web_app.icons[i].width)); 153 std::vector<unsigned char> image_data; 154 if (!gfx::PNGCodec::EncodeBGRASkBitmap(web_app.icons[i].data, 155 false, 156 &image_data)) { 157 LOG(ERROR) << "Could not create icon file."; 158 return NULL; 159 } 160 161 const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); 162 if (!file_util::WriteFile(icon_file, image_data_ptr, image_data.size())) { 163 LOG(ERROR) << "Could not write icon file."; 164 return NULL; 165 } 166 } 167 168 // Finally, create the extension object to represent the unpacked directory. 169 std::string error; 170 scoped_refptr<Extension> extension = Extension::Create( 171 temp_dir.path(), 172 Extension::INTERNAL, 173 *root, 174 Extension::STRICT_ERROR_CHECKS, 175 &error); 176 if (!extension) { 177 LOG(ERROR) << error; 178 return NULL; 179 } 180 181 temp_dir.Take(); // The caller takes ownership of the directory. 182 return extension; 183 } 184