Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Side by Side Diff: app_installer/app_installer_main.cc

Issue 423293004: Move app_installer into chromium. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2014 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 <initguid.h>
6 #include <urlmon.h>
7 #pragma comment(lib, "urlmon.lib")
8 #include <windows.h>
9
10 #include "base/at_exit.h"
11 #include "base/base_paths.h"
12 #include "base/basictypes.h"
13 #include "base/command_line.h"
14 #include "base/file_util.h"
15 #include "base/logging.h"
16 #include "base/logging_win.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/path_service.h"
19 #include "base/process/launch.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/string_util.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
25 #include "chrome/installer/util/google_update_util.h"
26 #include "chrome/installer/util/html_dialog.h"
27 #include "chrome/installer/util/util_constants.h"
28 #include "third_party/omaha/base/extractor.h"
29
30 namespace app_installer {
31
32 enum ExitCode {
33 SUCCESS = 0,
34 COULD_NOT_GET_FILE_PATH,
35 COULD_NOT_READ_TAG,
36 INVALID_APP_ID,
37 EULA_CANCELLED,
38 COULD_NOT_FIND_CHROME,
39 COULD_NOT_GET_TMP_FILE_PATH,
40 FAILED_TO_DOWNLOAD_CHROME_SETUP,
41 FAILED_TO_LAUNCH_CHROME_SETUP,
42 };
43
44 } // namespace app_installer
45
46 namespace {
47
48 // Log provider UUID. Required for logging to Sawbuck.
49 // {d82c3b59-bacd-4625-8282-4d570c4dad12}
50 DEFINE_GUID(kAppInstallerLogProvider,
51 0xd82c3b59,
52 0xbacd,
53 0x4625,
54 0x82, 0x82, 0x4d, 0x57, 0x0c, 0x4d, 0xad, 0x12);
55
56 const char kEnvVariableUntrustedData[] = "GoogleUpdateUntrustedData";
57 const int kUntrustedDataMaxLength = 4096;
58
59 const char kInstallChromeApp[] = "install-chrome-app";
60
61 const wchar_t kDownloadAndEulaPage[] =
62 L"https://tools.google.com/dlpage/chromeappinstaller";
63
64 const wchar_t kSxSDownloadAndEulaPage[] =
65 L"https://tools.google.com/dlpage/chromeappinstaller?sxs=true";
66
67 const wchar_t kDialogDimensions[] = L"dialogWidth:750px;dialogHeight:500px";
68
69 // This uses HTMLDialog to show a Chrome download page as a modal dialog.
70 // The page includes the EULA and returns a download URL.
71 class DownloadAndEulaHTMLDialog {
72 public:
73 DownloadAndEulaHTMLDialog(bool is_canary) {
74 dialog_.reset(installer::CreateNativeHTMLDialog(
75 is_canary ? kSxSDownloadAndEulaPage : kDownloadAndEulaPage,
76 base::string16()));
77 }
78 ~DownloadAndEulaHTMLDialog() {}
79
80 // Shows the dialog and blocks for user input. The return value is one of
81 // the |Outcome| values and any form of failure maps to REJECTED.
82 base::string16 ShowModal() {
83 Customizer customizer;
84 dialog_->ShowModal(NULL, &customizer);
85 return dialog_->GetExtraResult();
86 }
87
88 private:
89 class Customizer : public installer::HTMLDialog::CustomizationCallback {
90 public:
91 virtual void OnBeforeCreation(wchar_t** extra) {
92 *extra = const_cast<wchar_t*>(kDialogDimensions);
93 }
94
95 virtual void OnBeforeDisplay(void* window) {
96 // Customize the window by removing the close button and replacing the
97 // existing 'e' icon with the standard informational icon.
98 if (!window)
99 return;
100 HWND top_window = static_cast<HWND>(window);
101 LONG_PTR style = GetWindowLongPtrW(top_window, GWL_STYLE);
102 SetWindowLongPtrW(top_window, GWL_STYLE, style & ~WS_SYSMENU);
103 HICON ico = LoadIcon(NULL, IDI_INFORMATION);
104 SendMessageW(top_window, WM_SETICON, ICON_SMALL,
105 reinterpret_cast<LPARAM>(ico));
106 }
107 };
108
109 scoped_ptr<installer::HTMLDialog> dialog_;
110 DISALLOW_COPY_AND_ASSIGN(DownloadAndEulaHTMLDialog);
111 };
112
113 // Gets the tag appended to a file. This uses the same format as Omaha.
114 // Returns the empty string on failure.
115 std::string GetTag(const base::FilePath& file_name_path) {
116 base::string16 file_name = file_name_path.value();
117 omaha::TagExtractor extractor;
118 if (!extractor.OpenFile(file_name.c_str())) {
119 LOG(ERROR) << "Tag extractor could not open file: " << file_name;
120 return std::string();
121 }
122
123 int tag_buffer_size = 0;
124 if (!extractor.ExtractTag(NULL, &tag_buffer_size) || tag_buffer_size <= 1) {
125 LOG(ERROR) << "Could not extract tag size.";
126 return std::string();
127 }
128
129 scoped_ptr<char[]> tag_buffer(new char[tag_buffer_size]);
130 extractor.ExtractTag(tag_buffer.get(), &tag_buffer_size);
131 return std::string(tag_buffer.get(), tag_buffer_size - 1);
132 }
133
134 bool IsNotPrintable(unsigned char c) {
135 return c < 32 || c >= 127;
136 }
137
138 // Returns whether or not |s| consists of printable characters.
139 bool IsStringPrintable(const std::string& s) {
140 return std::find_if(s.begin(), s.end(), IsNotPrintable) == s.end();
141 }
142
143 bool IsIllegalUntrustedDataKeyChar(unsigned char c) {
144 return !(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ||
145 c >= '0' && c <= '9' || c == '-' || c == '_' || c == '$');
146 }
147
148 // Returns true if |key| from untrusted data is valid.
149 bool IsUntrustedDataKeyValid(const std::string& key) {
150 return std::find_if(key.begin(), key.end(), IsIllegalUntrustedDataKeyChar)
151 == key.end();
152 }
153
154 // Parses |data_string| as key-value pairs and overwrites |untrusted_data| with
155 // the result. Returns true if the data could be parsed.
156 bool ParseUntrustedData(
157 const std::string& data_string,
158 std::map<std::string, std::string>* untrusted_data) {
159 DCHECK(untrusted_data);
160 if (data_string.length() > kUntrustedDataMaxLength ||
161 !IsStringPrintable(data_string)) {
162 LOG(ERROR) << "Invalid value in untrusted data string.";
163 return false;
164 }
165
166 VLOG(1) << "Untrusted data string: " << data_string;
167
168 std::vector<std::pair<std::string, std::string> > kv_pairs;
169 if (!base::SplitStringIntoKeyValuePairs(data_string, '=', '&', &kv_pairs)) {
170 LOG(ERROR) << "Failed to parse untrusted data: " << data_string;
171 return false;
172 }
173
174 untrusted_data->clear();
175 std::vector<std::pair<std::string, std::string> >::const_iterator it;
176 for (it = kv_pairs.begin(); it != kv_pairs.end(); ++it) {
177 const std::string& key(it->first);
178 // TODO(huangs): URL unescape |value|.
179 const std::string& value(it->second);
180 if (IsUntrustedDataKeyValid(key) && IsStringPrintable(value))
181 (*untrusted_data)[key] = value;
182 else
183 LOG(ERROR) << "Illegal character found in untrusted data.";
184 }
185 return true;
186 }
187
188 // Returns the value corresponding to |key| in untrusted data passed from
189 // |tag|. |tag| should be a printable list of key-value pairs, e.g.
190 // "key1=value1&key2=value2". Returns an empty string if |key| is absent or if
191 // its value contains non-printable characters.
192 std::string GetUntrustedDataValueFromTag(const std::string& tag,
193 const std::string& key) {
194 std::map<std::string, std::string> untrusted_data;
195 if (ParseUntrustedData(tag, &untrusted_data))
196 return untrusted_data[key];
197
198 return std::string();
199 }
200
201 bool IsValidAppId(const std::string& app_id) {
202 if (app_id.size() != 32)
203 return false;
204
205 for (size_t i = 0; i < app_id.size(); ++i) {
206 char c = base::ToLowerASCII(app_id[i]);
207 if (c < 'a' || c > 'p')
208 return false;
209 }
210
211 return true;
212 }
213
214 base::FilePath GetChromeExePath(bool is_canary) {
215 return is_canary ?
216 chrome_launcher_support::GetAnyChromeSxSPath() :
217 chrome_launcher_support::GetAnyChromePath();
218 }
219
220 app_installer::ExitCode GetChrome(bool is_canary) {
221 // Show UI to install Chrome. The UI returns a download URL.
222 base::string16 download_url =
223 DownloadAndEulaHTMLDialog(is_canary).ShowModal();
224 if (download_url.empty()) {
225 LOG(ERROR) << "EULA cancelled.";
226 return app_installer::EULA_CANCELLED;
227 }
228
229 VLOG(1) << "Chrome download url: " << download_url;
230
231 // Get a temporary file path.
232 base::FilePath setup_file;
233 if (!base::CreateTemporaryFile(&setup_file)) {
234 LOG(ERROR) << "Could not get temporary file path.";
235 return app_installer::COULD_NOT_GET_TMP_FILE_PATH;
236 }
237
238 // Download the Chrome installer.
239 HRESULT hr = URLDownloadToFile(
240 NULL, download_url.c_str(), setup_file.value().c_str(), 0, NULL);
241 if (FAILED(hr)) {
242 LOG(ERROR) << "Download failed: Error " << std::hex << hr << ". "
243 << setup_file.value();
244 return app_installer::FAILED_TO_DOWNLOAD_CHROME_SETUP;
245 }
246
247 VLOG(1) << "Chrome setup downloaded to: " << setup_file.value();
248
249 // Install Chrome. Wait for the installer to finish before returning.
250 base::LaunchOptions options;
251 options.wait = true;
252 if (!base::LaunchProcess(CommandLine(setup_file), options, NULL)) {
253 LOG(ERROR) << "Launch failed: ";
254 base::DeleteFile(setup_file, false);
255 return app_installer::FAILED_TO_LAUNCH_CHROME_SETUP;
256 }
257
258 base::DeleteFile(setup_file, false);
259 return app_installer::SUCCESS;
260 }
261
262 } // namespace
263
264 extern "C"
265 int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
266 wchar_t* command_line, int show_command) {
267 base::AtExitManager exit_manager;
268 CommandLine::Init(0, NULL);
269 logging::LogEventProvider::Initialize(kAppInstallerLogProvider);
270 logging::LoggingSettings settings;
271 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
272 logging::InitLogging(settings);
273
274 std::string app_id = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
275 switches::kAppId);
276 const char* sxs = installer::switches::kChromeSxS;
277 // --chrome-sxs on the command line takes precedence over chrome-sxs in the
278 // tag.
279 bool is_canary = CommandLine::ForCurrentProcess()->HasSwitch(sxs);
280
281 // --app-id on the command line inhibits tag parsing altogether.
282 if (app_id.empty()) {
283 base::FilePath current_exe;
284 if (!PathService::Get(base::FILE_EXE, &current_exe)) {
285 LOG(ERROR) << "Could not get current exe path.";
286 return app_installer::COULD_NOT_GET_FILE_PATH;
287 };
288
289 std::string tag = GetTag(current_exe);
290 if (tag.empty()) {
291 LOG(ERROR) << "Could not get tag.";
292 return app_installer::COULD_NOT_READ_TAG;
293 }
294
295 app_id = GetUntrustedDataValueFromTag(tag, switches::kAppId);
296
297 if (!is_canary)
298 is_canary = GetUntrustedDataValueFromTag(tag, sxs) == "1";
299 }
300
301 if (!IsValidAppId(app_id)) {
302 LOG(ERROR) << "Invalid app id: " << app_id;
303 return app_installer::INVALID_APP_ID;
304 }
305
306 base::FilePath chrome_path = GetChromeExePath(is_canary);
307 // If none found, show EULA, download, and install Chrome.
308 if (chrome_path.empty()) {
309 app_installer::ExitCode get_chrome_result = GetChrome(is_canary);
310 if (get_chrome_result != app_installer::SUCCESS)
311 return get_chrome_result;
312
313 chrome_path = GetChromeExePath(is_canary);
314 if (chrome_path.empty()) {
315 LOG(ERROR) << "Could not find Chrome after installing.";
316 return app_installer::COULD_NOT_FIND_CHROME;
317 }
318 }
319
320 CommandLine cmd(chrome_path);
321 cmd.AppendSwitchASCII(kInstallChromeApp, app_id);
322 VLOG(1) << "Install command: " << cmd.GetCommandLineString();
323 bool launched = base::LaunchProcess(cmd, base::LaunchOptions(), NULL);
324 VLOG(1) << "Launch " << (launched ? "success." : "failed.");
325
326 return app_installer::SUCCESS;
327 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698