Home | History | Annotate | Download | only in extensions
      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