summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/convert_web_app.cc
blob: 5f844aba93fe6c56cdd8cd5d67cb59b7d42a5836 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/convert_web_app.h"

#include <cmath>
#include <limits>
#include <string>
#include <vector>

#include "base/base64.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_file_value_serializer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/web_application_info.h"
#include "crypto/sha2.h"
#include "extensions/common/constants.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"

namespace extensions {

namespace keys = extension_manifest_keys;

using base::Time;

namespace {

const char kIconsDirName[] = "icons";

// Create the public key for the converted web app.
//
// Web apps are not signed, but the public key for an extension doubles as
// its unique identity, and we need one of those. A web app's unique identity
// is its manifest URL, so we hash that to create a public key. There will be
// no corresponding private key, which means that these extensions cannot be
// auto-updated using ExtensionUpdater. But Chrome does notice updates to the
// manifest and regenerates these extensions.
std::string GenerateKey(const GURL& manifest_url) {
  char raw[crypto::kSHA256Length] = {0};
  std::string key;
  crypto::SHA256HashString(manifest_url.spec().c_str(), raw,
                           crypto::kSHA256Length);
  base::Base64Encode(std::string(raw, crypto::kSHA256Length), &key);
  return key;
}

}


// Generates a version for the converted app using the current date. This isn't
// really needed, but it seems like useful information.
std::string ConvertTimeToExtensionVersion(const Time& create_time) {
  Time::Exploded create_time_exploded;
  create_time.UTCExplode(&create_time_exploded);

  double micros = static_cast<double>(
      (create_time_exploded.millisecond * Time::kMicrosecondsPerMillisecond) +
      (create_time_exploded.second * Time::kMicrosecondsPerSecond) +
      (create_time_exploded.minute * Time::kMicrosecondsPerMinute) +
      (create_time_exploded.hour * Time::kMicrosecondsPerHour));
  double day_fraction = micros / Time::kMicrosecondsPerDay;
  double stamp = day_fraction * std::numeric_limits<uint16>::max();

  // Ghetto-round, since VC++ doesn't have round().
  stamp = stamp >= (floor(stamp) + 0.5) ? (stamp + 1) : stamp;

  return base::StringPrintf("%i.%i.%i.%i",
                            create_time_exploded.year,
                            create_time_exploded.month,
                            create_time_exploded.day_of_month,
                            static_cast<uint16>(stamp));
}

scoped_refptr<Extension> ConvertWebAppToExtension(
    const WebApplicationInfo& web_app,
    const Time& create_time,
    const base::FilePath& extensions_dir) {
  base::FilePath install_temp_dir =
      extension_file_util::GetInstallTempDir(extensions_dir);
  if (install_temp_dir.empty()) {
    LOG(ERROR) << "Could not get path to profile temporary directory.";
    return NULL;
  }

  base::ScopedTempDir temp_dir;
  if (!temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) {
    LOG(ERROR) << "Could not create temporary directory.";
    return NULL;
  }

  // Create the manifest
  scoped_ptr<DictionaryValue> root(new DictionaryValue);
  if (!web_app.is_bookmark_app)
    root->SetString(keys::kPublicKey, GenerateKey(web_app.manifest_url));
  else
    root->SetString(keys::kPublicKey, GenerateKey(web_app.app_url));

  if (web_app.is_offline_enabled)
    root->SetBoolean(keys::kOfflineEnabled, true);

  root->SetString(keys::kName, UTF16ToUTF8(web_app.title));
  root->SetString(keys::kVersion, ConvertTimeToExtensionVersion(create_time));
  root->SetString(keys::kDescription, UTF16ToUTF8(web_app.description));
  root->SetString(keys::kLaunchWebURL, web_app.app_url.spec());

  if (!web_app.launch_container.empty())
    root->SetString(keys::kLaunchContainer, web_app.launch_container);

  // Add the icons.
  DictionaryValue* icons = new DictionaryValue();
  root->Set(keys::kIcons, icons);
  for (size_t i = 0; i < web_app.icons.size(); ++i) {
    std::string size = base::StringPrintf("%i", web_app.icons[i].width);
    std::string icon_path = base::StringPrintf("%s/%s.png", kIconsDirName,
                                               size.c_str());
    icons->SetString(size, icon_path);
  }

  // Add the permissions.
  base::ListValue* permissions = new base::ListValue();
  root->Set(keys::kPermissions, permissions);
  for (size_t i = 0; i < web_app.permissions.size(); ++i) {
    permissions->Append(Value::CreateStringValue(web_app.permissions[i]));
  }

  // Add the URLs.
  base::ListValue* urls = new base::ListValue();
  root->Set(keys::kWebURLs, urls);
  for (size_t i = 0; i < web_app.urls.size(); ++i) {
    urls->Append(Value::CreateStringValue(web_app.urls[i].spec()));
  }

  // Write the manifest.
  base::FilePath manifest_path = temp_dir.path().Append(kManifestFilename);
  JSONFileValueSerializer serializer(manifest_path);
  if (!serializer.Serialize(*root)) {
    LOG(ERROR) << "Could not serialize manifest.";
    return NULL;
  }

  // Write the icon files.
  base::FilePath icons_dir = temp_dir.path().AppendASCII(kIconsDirName);
  if (!file_util::CreateDirectory(icons_dir)) {
    LOG(ERROR) << "Could not create icons directory.";
    return NULL;
  }
  for (size_t i = 0; i < web_app.icons.size(); ++i) {
    // Skip unfetched bitmaps.
    if (web_app.icons[i].data.config() == SkBitmap::kNo_Config)
      continue;

    base::FilePath icon_file = icons_dir.AppendASCII(
        base::StringPrintf("%i.png", web_app.icons[i].width));
    std::vector<unsigned char> image_data;
    if (!gfx::PNGCodec::EncodeBGRASkBitmap(web_app.icons[i].data,
                                           false,
                                           &image_data)) {
      LOG(ERROR) << "Could not create icon file.";
      return NULL;
    }

    const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
    if (!file_util::WriteFile(icon_file, image_data_ptr, image_data.size())) {
      LOG(ERROR) << "Could not write icon file.";
      return NULL;
    }
  }

  // Finally, create the extension object to represent the unpacked directory.
  std::string error;
  int extension_flags = Extension::NO_FLAGS;
  if (web_app.is_bookmark_app)
    extension_flags |= Extension::FROM_BOOKMARK;
  scoped_refptr<Extension> extension = Extension::Create(
      temp_dir.path(),
      Manifest::INTERNAL,
      *root,
      extension_flags,
      &error);
  if (!extension.get()) {
    LOG(ERROR) << error;
    return NULL;
  }

  temp_dir.Take();  // The caller takes ownership of the directory.
  return extension;
}

}  // namespace extensions