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/webstore_inline_installer.h"
6
7#include "base/strings/stringprintf.h"
8#include "chrome/browser/profiles/profile.h"
9#include "content/public/browser/web_contents.h"
10
11using content::WebContents;
12
13namespace extensions {
14
15const char kVerifiedSiteKey[] = "verified_site";
16const char kVerifiedSitesKey[] = "verified_sites";
17const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported";
18const char kRedirectUrlKey[] = "redirect_url";
19
20const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
21const char kNoVerifiedSitesError[] =
22    "Inline installs can only be initiated for Chrome Web Store items that "
23    "have one or more verified sites";
24const char kNotFromVerifiedSitesError[] =
25    "Installs can only be initiated by one of the Chrome Web Store item's "
26    "verified sites";
27const char kInlineInstallSupportedError[] =
28    "Inline installation is not supported for this item. The user will be "
29    "redirected to the Chrome Web Store.";
30
31WebstoreInlineInstaller::WebstoreInlineInstaller(
32    content::WebContents* web_contents,
33    const std::string& webstore_item_id,
34    const GURL& requestor_url,
35    const Callback& callback)
36    : WebstoreStandaloneInstaller(
37          webstore_item_id,
38          Profile::FromBrowserContext(web_contents->GetBrowserContext()),
39          callback),
40      content::WebContentsObserver(web_contents),
41      requestor_url_(requestor_url) {
42}
43
44WebstoreInlineInstaller::~WebstoreInlineInstaller() {}
45
46bool WebstoreInlineInstaller::CheckRequestorAlive() const {
47  // The tab may have gone away - cancel installation in that case.
48  return web_contents() != NULL;
49}
50
51const GURL& WebstoreInlineInstaller::GetRequestorURL() const {
52  return requestor_url_;
53}
54
55scoped_ptr<ExtensionInstallPrompt::Prompt>
56WebstoreInlineInstaller::CreateInstallPrompt() const {
57  scoped_ptr<ExtensionInstallPrompt::Prompt> prompt(
58      new ExtensionInstallPrompt::Prompt(
59          ExtensionInstallPrompt::INLINE_INSTALL_PROMPT));
60
61  // crbug.com/260742: Don't display the user count if it's zero. The reason
62  // it's zero is very often that the number isn't actually being counted
63  // (intentionally), which means that it's unlikely to be correct.
64  prompt->SetInlineInstallWebstoreData(localized_user_count(),
65                                       show_user_count(),
66                                       average_rating(),
67                                       rating_count());
68  return prompt.Pass();
69}
70
71bool WebstoreInlineInstaller::ShouldShowPostInstallUI() const {
72  return true;
73}
74
75bool WebstoreInlineInstaller::ShouldShowAppInstalledBubble() const {
76  return true;
77}
78
79WebContents* WebstoreInlineInstaller::GetWebContents() const {
80  return web_contents();
81}
82
83bool WebstoreInlineInstaller::CheckInlineInstallPermitted(
84    const base::DictionaryValue& webstore_data,
85    std::string* error) const {
86  // The store may not support inline installs for this item, in which case
87  // we open the store-provided redirect URL in a new tab and abort the
88  // installation process.
89  bool inline_install_not_supported = false;
90  if (webstore_data.HasKey(kInlineInstallNotSupportedKey)
91      && !webstore_data.GetBoolean(kInlineInstallNotSupportedKey,
92                                    &inline_install_not_supported)) {
93    *error = kInvalidWebstoreResponseError;
94    return false;
95  }
96  if (inline_install_not_supported) {
97    std::string redirect_url;
98    if (!webstore_data.GetString(kRedirectUrlKey, &redirect_url)) {
99      *error = kInvalidWebstoreResponseError;
100      return false;
101    }
102    web_contents()->OpenURL(
103        content::OpenURLParams(
104            GURL(redirect_url),
105            content::Referrer(web_contents()->GetURL(),
106                              blink::WebReferrerPolicyDefault),
107            NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
108    *error = kInlineInstallSupportedError;
109    return false;
110  }
111
112  *error = "";
113  return true;
114}
115
116bool WebstoreInlineInstaller::CheckRequestorPermitted(
117    const base::DictionaryValue& webstore_data,
118    std::string* error) const {
119  // Ensure that there is at least one verified site present.
120  const bool data_has_single_site = webstore_data.HasKey(kVerifiedSiteKey);
121  const bool data_has_site_list = webstore_data.HasKey(kVerifiedSitesKey);
122  if (!data_has_single_site && !data_has_site_list) {
123    *error = kNoVerifiedSitesError;
124    return false;
125  }
126  bool requestor_is_ok = false;
127  // Handle the deprecated single-site case.
128  if (!data_has_site_list) {
129    std::string verified_site;
130    if (!webstore_data.GetString(kVerifiedSiteKey, &verified_site)) {
131      *error = kInvalidWebstoreResponseError;
132      return false;
133    }
134    requestor_is_ok = IsRequestorURLInVerifiedSite(requestor_url_,
135                                                   verified_site);
136  } else {
137    const base::ListValue* verified_sites = NULL;
138    if (!webstore_data.GetList(kVerifiedSitesKey, &verified_sites)) {
139      *error = kInvalidWebstoreResponseError;
140      return false;
141    }
142    for (base::ListValue::const_iterator it = verified_sites->begin();
143         it != verified_sites->end() && !requestor_is_ok; ++it) {
144      std::string verified_site;
145      if (!(*it)->GetAsString(&verified_site)) {
146        *error = kInvalidWebstoreResponseError;
147        return false;
148      }
149      if (IsRequestorURLInVerifiedSite(requestor_url_, verified_site)) {
150        requestor_is_ok = true;
151      }
152    }
153  }
154  if (!requestor_is_ok) {
155    *error = kNotFromVerifiedSitesError;
156    return false;
157  }
158  *error = "";
159  return true;
160}
161
162//
163// Private implementation.
164//
165
166void WebstoreInlineInstaller::WebContentsDestroyed(
167    content::WebContents* web_contents) {
168  AbortInstall();
169}
170
171// static
172bool WebstoreInlineInstaller::IsRequestorURLInVerifiedSite(
173    const GURL& requestor_url,
174    const std::string& verified_site) {
175  // Turn the verified site into a URL that can be parsed by URLPattern.
176  // |verified_site| must follow the format:
177  //
178  // [scheme://]host[:port][/path/specifier]
179  //
180  // If scheme is omitted, URLPattern will match against either an
181  // HTTP or HTTPS requestor. If scheme is specified, it must be either HTTP
182  // or HTTPS, and URLPattern will only match the scheme specified.
183  GURL verified_site_url(verified_site);
184  int valid_schemes = URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
185  if (!verified_site_url.is_valid() || !verified_site_url.IsStandard())
186    // If no scheme is specified, GURL will fail to parse the string correctly.
187    // It will either determine that the URL is invalid, or parse a
188    // host:port/path as scheme:host/path.
189    verified_site_url = GURL("http://" + verified_site);
190  else if (verified_site_url.SchemeIs("http"))
191    valid_schemes = URLPattern::SCHEME_HTTP;
192  else if (verified_site_url.SchemeIs("https"))
193    valid_schemes = URLPattern::SCHEME_HTTPS;
194  else
195    return false;
196
197  std::string port_spec =
198      verified_site_url.has_port() ? ":" + verified_site_url.port() : "";
199  std::string path_spec = verified_site_url.path() + "*";
200  std::string verified_site_pattern_spec =
201      base::StringPrintf(
202          "%s://*.%s%s%s",
203          verified_site_url.scheme().c_str(),
204          verified_site_url.host().c_str(),
205          port_spec.c_str(),
206          path_spec.c_str());
207
208  URLPattern verified_site_pattern(valid_schemes);
209  URLPattern::ParseResult parse_result =
210      verified_site_pattern.Parse(verified_site_pattern_spec);
211  if (parse_result != URLPattern::PARSE_SUCCESS) {
212    DLOG(WARNING) << "Could not parse " << verified_site_pattern_spec <<
213        " as URL pattern " << parse_result;
214    return false;
215  }
216  verified_site_pattern.SetScheme("*");
217
218  return verified_site_pattern.MatchesURL(requestor_url);
219}
220
221}  // namespace extensions
222