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