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
33namespace extensions {
34
35namespace keys = extension_manifest_keys;
36
37using base::Time;
38
39namespace {
40
41const 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.
51std::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.
65std::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
87scoped_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