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// Implementation of the installation validator.
6
7#include "chrome/installer/util/installation_validator.h"
8
9#include <algorithm>
10#include <set>
11#include <string>
12
13#include "base/logging.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/version.h"
16#include "chrome/common/chrome_switches.h"
17#include "chrome/installer/util/browser_distribution.h"
18#include "chrome/installer/util/google_update_constants.h"
19#include "chrome/installer/util/helper.h"
20#include "chrome/installer/util/installation_state.h"
21
22namespace installer {
23
24BrowserDistribution::Type
25    InstallationValidator::ChromeRules::distribution_type() const {
26  return BrowserDistribution::CHROME_BROWSER;
27}
28
29void InstallationValidator::ChromeRules::AddUninstallSwitchExpectations(
30    const ProductContext& ctx,
31    SwitchExpectations* expectations) const {
32  const bool is_multi_install =
33      ctx.state.uninstall_command().HasSwitch(switches::kMultiInstall);
34
35  // --chrome should be present for uninstall iff --multi-install.  This wasn't
36  // the case in Chrome 10 (between r68996 and r72497), though, so consider it
37  // optional.
38
39  // --chrome-frame --ready-mode should be present for uninstall iff CF in ready
40  // mode.
41  const ProductState* cf_state =
42      ctx.machine_state.GetProductState(ctx.system_install,
43                                        BrowserDistribution::CHROME_FRAME);
44  const bool ready_mode =
45      cf_state != NULL &&
46      cf_state->uninstall_command().HasSwitch(switches::kChromeFrameReadyMode);
47  expectations->push_back(std::make_pair(std::string(switches::kChromeFrame),
48                                         ready_mode));
49  expectations->push_back(
50      std::make_pair(std::string(switches::kChromeFrameReadyMode), ready_mode));
51}
52
53void InstallationValidator::ChromeRules::AddRenameSwitchExpectations(
54    const ProductContext& ctx,
55    SwitchExpectations* expectations) const {
56  const bool is_multi_install =
57      ctx.state.uninstall_command().HasSwitch(switches::kMultiInstall);
58
59  // --chrome should not be present for rename.  It was for a time, so we'll be
60  // lenient so that mini_installer tests pass.
61
62  // --chrome-frame --ready-mode should never be present.
63  expectations->push_back(
64      std::make_pair(std::string(switches::kChromeFrame), false));
65  expectations->push_back(
66      std::make_pair(std::string(switches::kChromeFrameReadyMode), false));
67}
68
69bool InstallationValidator::ChromeRules::UsageStatsAllowed(
70    const ProductContext& ctx) const {
71  // Products must not have usagestats consent values when multi-install
72  // (only the multi-install binaries may).
73  return !ctx.state.is_multi_install();
74}
75
76BrowserDistribution::Type
77    InstallationValidator::ChromeFrameRules::distribution_type() const {
78  return BrowserDistribution::CHROME_FRAME;
79}
80
81void InstallationValidator::ChromeFrameRules::AddUninstallSwitchExpectations(
82    const ProductContext& ctx,
83    SwitchExpectations* expectations) const {
84  // --chrome-frame must be present.
85  expectations->push_back(std::make_pair(std::string(switches::kChromeFrame),
86                                         true));
87  // --chrome must not be present.
88  expectations->push_back(std::make_pair(std::string(switches::kChrome),
89                                         false));
90}
91
92void InstallationValidator::ChromeFrameRules::AddRenameSwitchExpectations(
93    const ProductContext& ctx,
94    SwitchExpectations* expectations) const {
95  // --chrome-frame must be present for SxS rename.
96  expectations->push_back(std::make_pair(std::string(switches::kChromeFrame),
97                                         !ctx.state.is_multi_install()));
98  // --chrome must not be present.
99  expectations->push_back(std::make_pair(std::string(switches::kChrome),
100                                         false));
101}
102
103bool InstallationValidator::ChromeFrameRules::UsageStatsAllowed(
104    const ProductContext& ctx) const {
105  // Products must not have usagestats consent values when multi-install
106  // (only the multi-install binaries may).
107  return !ctx.state.is_multi_install();
108}
109
110BrowserDistribution::Type
111    InstallationValidator::ChromeAppHostRules::distribution_type() const {
112  return BrowserDistribution::CHROME_APP_HOST;
113}
114
115void InstallationValidator::ChromeAppHostRules::AddUninstallSwitchExpectations(
116    const ProductContext& ctx,
117    SwitchExpectations* expectations) const {
118  // --app-launcher must be present.
119  expectations->push_back(
120      std::make_pair(std::string(switches::kChromeAppLauncher), true));
121
122  // --chrome must not be present.
123  expectations->push_back(std::make_pair(std::string(switches::kChrome),
124                                         false));
125  // --chrome-frame must not be present.
126  expectations->push_back(std::make_pair(std::string(switches::kChromeFrame),
127                                         false));
128}
129
130void InstallationValidator::ChromeAppHostRules::AddRenameSwitchExpectations(
131    const ProductContext& ctx,
132    SwitchExpectations* expectations) const {
133  // TODO(erikwright): I guess there will be none?
134}
135
136bool InstallationValidator::ChromeAppHostRules::UsageStatsAllowed(
137    const ProductContext& ctx) const {
138  // App Host doesn't manage usage stats. The Chrome Binaries will.
139  return false;
140}
141
142BrowserDistribution::Type
143    InstallationValidator::ChromeBinariesRules::distribution_type() const {
144  return BrowserDistribution::CHROME_BINARIES;
145}
146
147void InstallationValidator::ChromeBinariesRules::AddUninstallSwitchExpectations(
148    const ProductContext& ctx,
149    SwitchExpectations* expectations) const {
150  NOTREACHED();
151}
152
153void InstallationValidator::ChromeBinariesRules::AddRenameSwitchExpectations(
154    const ProductContext& ctx,
155    SwitchExpectations* expectations) const {
156  NOTREACHED();
157}
158
159bool InstallationValidator::ChromeBinariesRules::UsageStatsAllowed(
160    const ProductContext& ctx) const {
161  // UsageStats consent values are always allowed on the binaries.
162  return true;
163}
164
165// static
166const InstallationValidator::InstallationType
167    InstallationValidator::kInstallationTypes[] = {
168  NO_PRODUCTS,
169  CHROME_SINGLE,
170  CHROME_MULTI,
171  CHROME_FRAME_SINGLE,
172  CHROME_FRAME_SINGLE_CHROME_SINGLE,
173  CHROME_FRAME_SINGLE_CHROME_MULTI,
174  CHROME_FRAME_MULTI,
175  CHROME_FRAME_MULTI_CHROME_MULTI,
176  CHROME_FRAME_READY_MODE_CHROME_MULTI,
177  CHROME_APP_HOST,
178  CHROME_APP_HOST_CHROME_FRAME_SINGLE,
179  CHROME_APP_HOST_CHROME_FRAME_SINGLE_CHROME_MULTI,
180  CHROME_APP_HOST_CHROME_FRAME_MULTI,
181  CHROME_APP_HOST_CHROME_FRAME_MULTI_CHROME_MULTI,
182  CHROME_APP_HOST_CHROME_MULTI,
183  CHROME_APP_HOST_CHROME_MULTI_CHROME_FRAME_READY_MODE,
184};
185
186void InstallationValidator::ValidateAppCommandFlags(
187    const ProductContext& ctx,
188    const AppCommand& app_cmd,
189    const std::set<string16>& flags_exp,
190    const string16& name,
191    bool* is_valid) {
192  const struct {
193    const string16 exp_key;
194    bool val;
195    const char* msg;
196  } check_list[] = {
197    {google_update::kRegSendsPingsField,
198         app_cmd.sends_pings(),
199         "be configured to send pings"},
200    {google_update::kRegWebAccessibleField,
201         app_cmd.is_web_accessible(),
202         "be web accessible"},
203    {google_update::kRegAutoRunOnOSUpgradeField,
204         app_cmd.is_auto_run_on_os_upgrade(),
205         "be marked to run on OS upgrade"},
206    {google_update::kRegRunAsUserField,
207         app_cmd.is_run_as_user(),
208         "be marked to run as user"},
209  };
210  for (int i = 0; i < arraysize(check_list); ++i) {
211    bool expected = flags_exp.find(check_list[i].exp_key) != flags_exp.end();
212    if (check_list[i].val != expected) {
213      *is_valid = false;
214      LOG(ERROR) << ctx.dist->GetAppShortCutName() << ": "
215                 << name << " command should " << (expected ? "" : "not ")
216                 << check_list[i].msg << ".";
217    }
218  }
219}
220
221// Validates both "install-application" and "install-extension" depending on
222// what is passed in.
223void InstallationValidator::ValidateInstallCommand(
224    const ProductContext& ctx,
225    const AppCommand& app_cmd,
226    const wchar_t* expected_command,
227    const wchar_t* expected_app_name,
228    const char* expected_switch,
229    bool* is_valid) {
230  DCHECK(is_valid);
231
232  CommandLine cmd_line(CommandLine::FromString(app_cmd.command_line()));
233  string16 name(expected_command);
234
235  base::FilePath expected_path(
236      installer::GetChromeInstallPath(ctx.system_install, ctx.dist)
237      .Append(expected_app_name));
238
239  if (!base::FilePath::CompareEqualIgnoreCase(expected_path.value(),
240                                              cmd_line.GetProgram().value())) {
241    *is_valid = false;
242    LOG(ERROR) << name << "'s path is not "
243               << expected_path.value() << ": "
244               << cmd_line.GetProgram().value();
245  }
246
247  SwitchExpectations expected;
248  expected.push_back(std::make_pair(std::string(expected_switch), true));
249
250  ValidateCommandExpectations(ctx, cmd_line, expected, name, is_valid);
251
252  std::set<string16> flags_exp;
253  flags_exp.insert(google_update::kRegSendsPingsField);
254  flags_exp.insert(google_update::kRegWebAccessibleField);
255  flags_exp.insert(google_update::kRegRunAsUserField);
256  ValidateAppCommandFlags(ctx, app_cmd, flags_exp, name, is_valid);
257}
258
259// Validates the "install-application" Google Update product command.
260void InstallationValidator::ValidateInstallAppCommand(
261    const ProductContext& ctx,
262    const AppCommand& app_cmd,
263    bool* is_valid) {
264  ValidateInstallCommand(ctx, app_cmd, kCmdInstallApp,
265                         installer::kChromeAppHostExe,
266                         ::switches::kInstallFromWebstore, is_valid);
267}
268
269// Validates the "install-extension" Google Update product command.
270void InstallationValidator::ValidateInstallExtensionCommand(
271    const ProductContext& ctx,
272    const AppCommand& app_cmd,
273    bool* is_valid) {
274  ValidateInstallCommand(ctx, app_cmd, kCmdInstallExtension,
275                         installer::kChromeExe,
276                         ::switches::kLimitedInstallFromWebstore, is_valid);
277}
278
279// Validates the "on-os-upgrade" Google Update internal command.
280void InstallationValidator::ValidateOnOsUpgradeCommand(
281    const ProductContext& ctx,
282    const AppCommand& app_cmd,
283    bool* is_valid) {
284  DCHECK(is_valid);
285
286  CommandLine cmd_line(CommandLine::FromString(app_cmd.command_line()));
287  string16 name(kCmdOnOsUpgrade);
288
289  ValidateSetupPath(ctx, cmd_line.GetProgram(), name, is_valid);
290
291  SwitchExpectations expected;
292  expected.push_back(std::make_pair(std::string(switches::kOnOsUpgrade), true));
293  expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
294                                    ctx.system_install));
295  expected.push_back(std::make_pair(std::string(switches::kMultiInstall),
296                                    ctx.state.is_multi_install()));
297  // Expecting kChrome if and only if kMultiInstall.
298  expected.push_back(std::make_pair(std::string(switches::kChrome),
299                                    ctx.state.is_multi_install()));
300
301  ValidateCommandExpectations(ctx, cmd_line, expected, name, is_valid);
302
303  std::set<string16> flags_exp;
304  flags_exp.insert(google_update::kRegAutoRunOnOSUpgradeField);
305  ValidateAppCommandFlags(ctx, app_cmd, flags_exp, name, is_valid);
306}
307
308// Validates the "query-eula-acceptance" Google Update product command.
309void InstallationValidator::ValidateQueryEULAAcceptanceCommand(
310    const ProductContext& ctx,
311    const AppCommand& app_cmd,
312    bool* is_valid) {
313  DCHECK(is_valid);
314
315  CommandLine cmd_line(CommandLine::FromString(app_cmd.command_line()));
316  string16 name(kCmdQueryEULAAcceptance);
317
318  ValidateSetupPath(ctx, cmd_line.GetProgram(), name, is_valid);
319
320  SwitchExpectations expected;
321  expected.push_back(std::make_pair(std::string(switches::kQueryEULAAcceptance),
322                                    true));
323  expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
324                                    ctx.system_install));
325
326  ValidateCommandExpectations(ctx, cmd_line, expected, name, is_valid);
327
328  std::set<string16> flags_exp;
329  flags_exp.insert(google_update::kRegWebAccessibleField);
330  flags_exp.insert(google_update::kRegRunAsUserField);
331  ValidateAppCommandFlags(ctx, app_cmd, flags_exp, name, is_valid);
332}
333
334// Validates the "quick-enable-cf" Google Update product command.
335void InstallationValidator::ValidateQuickEnableCfCommand(
336    const ProductContext& ctx,
337    const AppCommand& app_cmd,
338    bool* is_valid) {
339  DCHECK(is_valid);
340
341  CommandLine cmd_line(CommandLine::FromString(app_cmd.command_line()));
342  string16 name(kCmdQuickEnableCf);
343
344  ValidateSetupPath(ctx, cmd_line.GetProgram(), name, is_valid);
345
346  SwitchExpectations expected;
347
348  expected.push_back(
349      std::make_pair(std::string(switches::kChromeFrameQuickEnable), true));
350  expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
351                                    ctx.system_install));
352  expected.push_back(std::make_pair(std::string(switches::kMultiInstall),
353                                    ctx.state.is_multi_install()));
354
355  ValidateCommandExpectations(ctx, cmd_line, expected, name, is_valid);
356
357  std::set<string16> flags_exp;
358  flags_exp.insert(google_update::kRegSendsPingsField);
359  flags_exp.insert(google_update::kRegWebAccessibleField);
360  ValidateAppCommandFlags(ctx, app_cmd, flags_exp, name, is_valid);
361}
362
363// Validates the "quick-enable-application-host" Google Update product command.
364void InstallationValidator::ValidateQuickEnableApplicationHostCommand(
365    const ProductContext& ctx,
366    const AppCommand& app_cmd,
367    bool* is_valid) {
368  DCHECK(is_valid);
369
370  CommandLine cmd_line(CommandLine::FromString(app_cmd.command_line()));
371  string16 name(kCmdQuickEnableApplicationHost);
372
373  ValidateSetupPath(ctx, cmd_line.GetProgram(), name, is_valid);
374
375  SwitchExpectations expected;
376
377  expected.push_back(std::make_pair(
378      std::string(switches::kChromeAppLauncher), true));
379  expected.push_back(std::make_pair(
380      std::string(switches::kSystemLevel), false));
381  expected.push_back(std::make_pair(
382      std::string(switches::kMultiInstall), true));
383  expected.push_back(std::make_pair(
384      std::string(switches::kEnsureGoogleUpdatePresent), true));
385
386  ValidateCommandExpectations(ctx, cmd_line, expected, name, is_valid);
387
388  std::set<string16> flags_exp;
389  flags_exp.insert(google_update::kRegSendsPingsField);
390  flags_exp.insert(google_update::kRegWebAccessibleField);
391  flags_exp.insert(google_update::kRegRunAsUserField);
392  ValidateAppCommandFlags(ctx, app_cmd, flags_exp, name, is_valid);
393}
394
395// Validates a product's set of Google Update product commands against a
396// collection of expectations.
397void InstallationValidator::ValidateAppCommandExpectations(
398    const ProductContext& ctx,
399    const CommandExpectations& expectations,
400    bool* is_valid) {
401  DCHECK(is_valid);
402
403  CommandExpectations the_expectations(expectations);
404
405  AppCommands::CommandMapRange cmd_iterators(
406      ctx.state.commands().GetIterators());
407  CommandExpectations::iterator expectation;
408  for (; cmd_iterators.first != cmd_iterators.second; ++cmd_iterators.first) {
409    const string16& cmd_id = cmd_iterators.first->first;
410    // Do we have an expectation for this command?
411    expectation = the_expectations.find(cmd_id);
412    if (expectation != the_expectations.end()) {
413      (expectation->second)(ctx, cmd_iterators.first->second, is_valid);
414      // Remove this command from the set of expectations since we found it.
415      the_expectations.erase(expectation);
416    } else {
417      *is_valid = false;
418      LOG(ERROR) << ctx.dist->GetAppShortCutName()
419                 << " has an unexpected Google Update product command named \""
420                 << cmd_id << "\".";
421    }
422  }
423
424  // Report on any expected commands that weren't present.
425  CommandExpectations::const_iterator scan(the_expectations.begin());
426  CommandExpectations::const_iterator end(the_expectations.end());
427  for (; scan != end; ++scan) {
428    *is_valid = false;
429    LOG(ERROR) << ctx.dist->GetAppShortCutName()
430               << " is missing the Google Update product command named \""
431               << scan->first << "\".";
432  }
433}
434
435// Validates the multi-install binaries' Google Update commands.
436void InstallationValidator::ValidateBinariesCommands(
437    const ProductContext& ctx,
438    bool* is_valid) {
439  DCHECK(is_valid);
440
441  // The quick-enable-cf command must be present if Chrome Binaries are
442  // installed and Chrome Frame is not installed (or installed in ready mode).
443  const ChannelInfo& channel = ctx.state.channel();
444  const ProductState* binaries_state = ctx.machine_state.GetProductState(
445      ctx.system_install, BrowserDistribution::CHROME_BINARIES);
446  const ProductState* cf_state = ctx.machine_state.GetProductState(
447      ctx.system_install, BrowserDistribution::CHROME_FRAME);
448
449  CommandExpectations expectations;
450
451  if (binaries_state != NULL) {
452    if (cf_state == NULL || channel.IsReadyMode())
453      expectations[kCmdQuickEnableCf] = &ValidateQuickEnableCfCommand;
454
455    expectations[kCmdQuickEnableApplicationHost] =
456        &ValidateQuickEnableApplicationHostCommand;
457
458    expectations[kCmdQueryEULAAcceptance] = &ValidateQueryEULAAcceptanceCommand;
459  }
460
461  ValidateAppCommandExpectations(ctx, expectations, is_valid);
462}
463
464// Validates the multi-install binaries at level |system_level|.
465void InstallationValidator::ValidateBinaries(
466    const InstallationState& machine_state,
467    bool system_install,
468    const ProductState& binaries_state,
469    bool* is_valid) {
470  const ChannelInfo& channel = binaries_state.channel();
471
472  // ap must have -multi
473  if (!channel.IsMultiInstall()) {
474    *is_valid = false;
475    LOG(ERROR) << "Chrome Binaries are missing \"-multi\" in channel name: \""
476               << channel.value() << "\"";
477  }
478
479  // ap must have -chrome iff Chrome is installed
480  const ProductState* chrome_state = machine_state.GetProductState(
481      system_install, BrowserDistribution::CHROME_BROWSER);
482  if (chrome_state != NULL) {
483    if (!channel.IsChrome()) {
484      *is_valid = false;
485      LOG(ERROR) << "Chrome Binaries are missing \"chrome\" in channel name:"
486                 << " \"" << channel.value() << "\"";
487    }
488  } else if (channel.IsChrome()) {
489    *is_valid = false;
490    LOG(ERROR) << "Chrome Binaries have \"-chrome\" in channel name, yet Chrome"
491                  " is not installed: \"" << channel.value() << "\"";
492  }
493
494  // ap must have -chromeframe iff Chrome Frame is installed multi
495  const ProductState* cf_state = machine_state.GetProductState(
496      system_install, BrowserDistribution::CHROME_FRAME);
497  if (cf_state != NULL && cf_state->is_multi_install()) {
498    if (!channel.IsChromeFrame()) {
499      *is_valid = false;
500      LOG(ERROR) << "Chrome Binaries are missing \"-chromeframe\" in channel"
501                    " name: \"" << channel.value() << "\"";
502    }
503  } else if (channel.IsChromeFrame()) {
504    *is_valid = false;
505    LOG(ERROR) << "Chrome Binaries have \"-chromeframe\" in channel name, yet "
506                  "Chrome Frame is not installed multi: \"" << channel.value()
507               << "\"";
508  }
509
510  // ap must have -readymode iff Chrome Frame is installed in ready-mode
511  if (cf_state != NULL &&
512      cf_state->uninstall_command().HasSwitch(
513          installer::switches::kChromeFrameReadyMode)) {
514    if (!channel.IsReadyMode()) {
515      *is_valid = false;
516      LOG(ERROR) << "Chrome Binaries are missing \"-readymode\" in channel"
517                    " name: \"" << channel.value() << "\"";
518    }
519  } else if (channel.IsReadyMode()) {
520    *is_valid = false;
521    LOG(ERROR) << "Chrome Binaries have \"-readymode\" in channel name, yet "
522                  "Chrome Frame is not in ready mode: \"" << channel.value()
523               << "\"";
524  }
525
526  // ap must have -applauncher iff Chrome App Launcher is installed multi
527  const ProductState* app_host_state = machine_state.GetProductState(
528      system_install, BrowserDistribution::CHROME_APP_HOST);
529  if (app_host_state != NULL) {
530    if (!app_host_state->is_multi_install()) {
531      *is_valid = false;
532      LOG(ERROR) << "Chrome App Launcher is installed in non-multi mode.";
533    }
534    if (!channel.IsAppLauncher()) {
535      *is_valid = false;
536      LOG(ERROR) << "Chrome Binaries are missing \"-applauncher\" in channel"
537                    " name: \"" << channel.value() << "\"";
538    }
539  } else if (channel.IsAppLauncher()) {
540    *is_valid = false;
541    LOG(ERROR) << "Chrome Binaries have \"-applauncher\" in channel name, yet "
542                  "Chrome App Launcher is not installed: \"" << channel.value()
543               << "\"";
544  }
545
546  // Chrome, Chrome Frame, or App Host must be present
547  if (chrome_state == NULL && cf_state == NULL && app_host_state == NULL) {
548    *is_valid = false;
549    LOG(ERROR) << "Chrome Binaries are present with no other products.";
550  }
551
552  // Chrome must be multi-install if present.
553  if (chrome_state != NULL && !chrome_state->is_multi_install()) {
554    *is_valid = false;
555    LOG(ERROR)
556        << "Chrome Binaries are present yet Chrome is not multi-install.";
557  }
558
559  // Chrome Frame must be multi-install if Chrome & App Host are not present.
560  if (cf_state != NULL && app_host_state == NULL && chrome_state == NULL &&
561      !cf_state->is_multi_install()) {
562    *is_valid = false;
563    LOG(ERROR) << "Chrome Binaries are present without Chrome nor App Launcher "
564               << "yet Chrome Frame is not multi-install.";
565  }
566
567  ChromeBinariesRules binaries_rules;
568  ProductContext ctx(machine_state, system_install, binaries_state,
569                     binaries_rules);
570
571  ValidateBinariesCommands(ctx, is_valid);
572
573  ValidateUsageStats(ctx, is_valid);
574}
575
576// Validates the path to |setup_exe| for the product described by |ctx|.
577void InstallationValidator::ValidateSetupPath(const ProductContext& ctx,
578                                              const base::FilePath& setup_exe,
579                                              const string16& purpose,
580                                              bool* is_valid) {
581  DCHECK(is_valid);
582
583  BrowserDistribution* bins_dist = ctx.dist;
584  if (ctx.state.is_multi_install()) {
585    bins_dist = BrowserDistribution::GetSpecificDistribution(
586        BrowserDistribution::CHROME_BINARIES);
587  }
588
589  base::FilePath expected_path = installer::GetChromeInstallPath(
590      ctx.system_install, bins_dist);
591  expected_path = expected_path
592      .AppendASCII(ctx.state.version().GetString())
593      .Append(installer::kInstallerDir)
594      .Append(installer::kSetupExe);
595  if (!base::FilePath::CompareEqualIgnoreCase(expected_path.value(),
596                                              setup_exe.value())) {
597    *is_valid = false;
598    LOG(ERROR) << ctx.dist->GetAppShortCutName() << " path to " << purpose
599               << " is not " << expected_path.value() << ": "
600               << setup_exe.value();
601  }
602}
603
604// Validates that |command| meets the expectations described in |expected|.
605void InstallationValidator::ValidateCommandExpectations(
606    const ProductContext& ctx,
607    const CommandLine& command,
608    const SwitchExpectations& expected,
609    const string16& source,
610    bool* is_valid) {
611  for (SwitchExpectations::size_type i = 0, size = expected.size(); i < size;
612       ++i) {
613    const SwitchExpectations::value_type& expectation = expected[i];
614    if (command.HasSwitch(expectation.first) != expectation.second) {
615      *is_valid = false;
616      LOG(ERROR) << ctx.dist->GetAppShortCutName() << " " << source
617                 << (expectation.second ? " is missing" : " has") << " \""
618                 << expectation.first << "\""
619                 << (expectation.second ? "" : " but shouldn't") << ": "
620                 << command.GetCommandLineString();
621    }
622  }
623}
624
625// Validates that |command|, originating from |source|, is formed properly for
626// the product described by |ctx|
627void InstallationValidator::ValidateUninstallCommand(const ProductContext& ctx,
628                                                     const CommandLine& command,
629                                                     const string16& source,
630                                                     bool* is_valid) {
631  DCHECK(is_valid);
632
633  ValidateSetupPath(ctx, command.GetProgram(), ASCIIToUTF16("uninstaller"),
634                    is_valid);
635
636  const bool is_multi_install = ctx.state.is_multi_install();
637  SwitchExpectations expected;
638
639  expected.push_back(std::make_pair(std::string(switches::kUninstall), true));
640  expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
641                                    ctx.system_install));
642  expected.push_back(std::make_pair(std::string(switches::kMultiInstall),
643                                    is_multi_install));
644  ctx.rules.AddUninstallSwitchExpectations(ctx, &expected);
645
646  ValidateCommandExpectations(ctx, command, expected, source, is_valid);
647}
648
649// Validates the rename command for the product described by |ctx|.
650void InstallationValidator::ValidateRenameCommand(const ProductContext& ctx,
651                                                  bool* is_valid) {
652  DCHECK(is_valid);
653  DCHECK(!ctx.state.rename_cmd().empty());
654
655  CommandLine command = CommandLine::FromString(ctx.state.rename_cmd());
656  string16 name(ASCIIToUTF16("in-use renamer"));
657
658  ValidateSetupPath(ctx, command.GetProgram(), name, is_valid);
659
660  SwitchExpectations expected;
661
662  expected.push_back(std::make_pair(std::string(switches::kRenameChromeExe),
663                                    true));
664  expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
665                                    ctx.system_install));
666  expected.push_back(std::make_pair(std::string(switches::kMultiInstall),
667                                    ctx.state.is_multi_install()));
668  ctx.rules.AddRenameSwitchExpectations(ctx, &expected);
669
670  ValidateCommandExpectations(ctx, command, expected, name, is_valid);
671}
672
673// Validates the "opv" and "cmd" values for the product described in |ctx|.
674void InstallationValidator::ValidateOldVersionValues(
675    const ProductContext& ctx,
676    bool* is_valid) {
677  DCHECK(is_valid);
678
679  // opv and cmd must both be present or both absent
680  if (ctx.state.old_version() == NULL) {
681    if (!ctx.state.rename_cmd().empty()) {
682      *is_valid = false;
683      LOG(ERROR) << ctx.dist->GetAppShortCutName()
684                 << " has a rename command but no opv: "
685                 << ctx.state.rename_cmd();
686    }
687  } else {
688    if (ctx.state.rename_cmd().empty()) {
689      *is_valid = false;
690      LOG(ERROR) << ctx.dist->GetAppShortCutName()
691                 << " has an opv but no rename command: "
692                 << ctx.state.old_version()->GetString();
693    } else {
694      ValidateRenameCommand(ctx, is_valid);
695    }
696  }
697}
698
699// Validates the multi-install state of the product described in |ctx|.
700void InstallationValidator::ValidateMultiInstallProduct(
701    const ProductContext& ctx,
702    bool* is_valid) {
703  DCHECK(is_valid);
704
705  const ProductState* binaries =
706      ctx.machine_state.GetProductState(ctx.system_install,
707                                        BrowserDistribution::CHROME_BINARIES);
708  if (!binaries) {
709    if (ctx.dist->GetType() == BrowserDistribution::CHROME_APP_HOST) {
710      if (!ctx.machine_state.GetProductState(
711              true,  // system-level
712              BrowserDistribution::CHROME_BINARIES) &&
713          !ctx.machine_state.GetProductState(
714              true,  // system-level
715              BrowserDistribution::CHROME_BROWSER)) {
716        *is_valid = false;
717        LOG(ERROR) << ctx.dist->GetAppShortCutName()
718                   << " (" << ctx.state.version().GetString() << ") is "
719                   << "installed without Chrome Binaries or a system-level "
720                   << "Chrome.";
721      }
722    } else {
723      *is_valid = false;
724      LOG(ERROR) << ctx.dist->GetAppShortCutName()
725                 << " (" << ctx.state.version().GetString() << ") is installed "
726                 << "without Chrome Binaries.";
727    }
728  } else {
729    // Version must match that of binaries.
730    if (ctx.state.version().CompareTo(binaries->version()) != 0) {
731      *is_valid = false;
732      LOG(ERROR) << "Version of " << ctx.dist->GetAppShortCutName()
733                 << " (" << ctx.state.version().GetString() << ") does not "
734                    "match that of Chrome Binaries ("
735                 << binaries->version().GetString() << ").";
736    }
737
738    // Channel value must match that of binaries.
739    if (!ctx.state.channel().Equals(binaries->channel())) {
740      *is_valid = false;
741      LOG(ERROR) << "Channel name of " << ctx.dist->GetAppShortCutName()
742                 << " (" << ctx.state.channel().value()
743                 << ") does not match that of Chrome Binaries ("
744                 << binaries->channel().value() << ").";
745    }
746  }
747}
748
749// Validates the Google Update commands for the product described in |ctx|.
750void InstallationValidator::ValidateAppCommands(
751    const ProductContext& ctx,
752    bool* is_valid) {
753  DCHECK(is_valid);
754
755  CommandExpectations expectations;
756
757  if (ctx.dist->GetType() == BrowserDistribution::CHROME_APP_HOST) {
758    expectations[kCmdInstallApp] = &ValidateInstallAppCommand;
759  }
760  if (ctx.dist->GetType() == BrowserDistribution::CHROME_BROWSER) {
761    expectations[kCmdInstallExtension] = &ValidateInstallExtensionCommand;
762    expectations[kCmdOnOsUpgrade] = &ValidateOnOsUpgradeCommand;
763  }
764
765  ValidateAppCommandExpectations(ctx, expectations, is_valid);
766}
767
768// Validates usagestats for the product or binaries in |ctx|.
769void InstallationValidator::ValidateUsageStats(const ProductContext& ctx,
770                                               bool* is_valid) {
771  DWORD usagestats = 0;
772  if (ctx.state.GetUsageStats(&usagestats)) {
773    if (!ctx.rules.UsageStatsAllowed(ctx)) {
774      *is_valid = false;
775      LOG(ERROR) << ctx.dist->GetAppShortCutName()
776                 << " has a usagestats value (" << usagestats
777                 << "), yet should not.";
778    } else if (usagestats != 0 && usagestats != 1) {
779      *is_valid = false;
780      LOG(ERROR) << ctx.dist->GetAppShortCutName()
781                 << " has an unsupported usagestats value (" << usagestats
782                 << ").";
783    }
784  }
785}
786
787// Validates the product described in |product_state| according to |rules|.
788void InstallationValidator::ValidateProduct(
789    const InstallationState& machine_state,
790    bool system_install,
791    const ProductState& product_state,
792    const ProductRules& rules,
793    bool* is_valid) {
794  DCHECK(is_valid);
795
796  ProductContext ctx(machine_state, system_install, product_state, rules);
797
798  ValidateUninstallCommand(ctx, ctx.state.uninstall_command(),
799                           ASCIIToUTF16("Google Update uninstall command"),
800                           is_valid);
801
802  ValidateOldVersionValues(ctx, is_valid);
803
804  if (ctx.state.is_multi_install())
805    ValidateMultiInstallProduct(ctx, is_valid);
806
807  ValidateAppCommands(ctx, is_valid);
808
809  ValidateUsageStats(ctx, is_valid);
810}
811
812// static
813bool InstallationValidator::ValidateInstallationTypeForState(
814    const InstallationState& machine_state,
815    bool system_level,
816    InstallationType* type) {
817  DCHECK(type);
818  bool rock_on = true;
819  *type = NO_PRODUCTS;
820
821  // Does the system have any multi-installed products?
822  const ProductState* multi_state =
823      machine_state.GetProductState(system_level,
824                                    BrowserDistribution::CHROME_BINARIES);
825  if (multi_state != NULL)
826    ValidateBinaries(machine_state, system_level, *multi_state, &rock_on);
827
828  // Is Chrome installed?
829  const ProductState* product_state =
830      machine_state.GetProductState(system_level,
831                                    BrowserDistribution::CHROME_BROWSER);
832  if (product_state != NULL) {
833    ChromeRules chrome_rules;
834    ValidateProduct(machine_state, system_level, *product_state,
835                    chrome_rules, &rock_on);
836    *type = static_cast<InstallationType>(
837        *type | (product_state->is_multi_install() ?
838                 ProductBits::CHROME_MULTI :
839                 ProductBits::CHROME_SINGLE));
840  }
841
842  // Is Chrome Frame installed?
843  product_state =
844      machine_state.GetProductState(system_level,
845                                    BrowserDistribution::CHROME_FRAME);
846  if (product_state != NULL) {
847    ChromeFrameRules chrome_frame_rules;
848    ValidateProduct(machine_state, system_level, *product_state,
849                    chrome_frame_rules, &rock_on);
850    int cf_bit = !product_state->is_multi_install() ?
851        ProductBits::CHROME_FRAME_SINGLE :
852        (product_state->uninstall_command().HasSwitch(
853             switches::kChromeFrameReadyMode) ?
854                 ProductBits::CHROME_FRAME_READY_MODE :
855                 ProductBits::CHROME_FRAME_MULTI);
856    *type = static_cast<InstallationType>(*type | cf_bit);
857  }
858
859  // Is Chrome App Host installed?
860  product_state =
861      machine_state.GetProductState(system_level,
862                                    BrowserDistribution::CHROME_APP_HOST);
863  if (product_state != NULL) {
864    ChromeAppHostRules chrome_app_host_rules;
865    ValidateProduct(machine_state, system_level, *product_state,
866                    chrome_app_host_rules, &rock_on);
867    *type = static_cast<InstallationType>(*type | ProductBits::CHROME_APP_HOST);
868    if (!product_state->is_multi_install()) {
869      LOG(ERROR) << "Chrome App Launcher must always be multi-install.";
870      rock_on = false;
871    }
872  }
873
874  DCHECK_NE(std::find(&kInstallationTypes[0],
875                      &kInstallationTypes[arraysize(kInstallationTypes)],
876                      *type),
877            &kInstallationTypes[arraysize(kInstallationTypes)])
878      << "Invalid combination of products found on system (" << *type << ")";
879
880  return rock_on;
881}
882
883// static
884bool InstallationValidator::ValidateInstallationType(bool system_level,
885                                                     InstallationType* type) {
886  DCHECK(type);
887  InstallationState machine_state;
888
889  machine_state.Initialize();
890
891  return ValidateInstallationTypeForState(machine_state, system_level, type);
892}
893
894}  // namespace installer
895