blob: f158f29779456ca0f5a9cbd169a0c4dc3dca6ef1 [file] [log] [blame]
constantinac8b2173b2016-12-15 05:55:511// Copyright 2016 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/webshare/share_service_impl.h"
6
constantina2cfa55e2017-01-13 06:36:557#include <algorithm>
8#include <functional>
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:379#include <map>
constantina2cfa55e2017-01-13 06:36:5510#include <utility>
11
12#include "base/strings/string_util.h"
constantina5558f3f32017-02-13 05:37:5413#include "chrome/browser/engagement/site_engagement_service.h"
14#include "chrome/browser/profiles/profile.h"
constantina2cfa55e2017-01-13 06:36:5515#include "chrome/browser/ui/browser_commands.h"
mgiuca39e40672017-01-31 04:16:4916#include "chrome/browser/ui/browser_dialogs.h"
constantina2cfa55e2017-01-13 06:36:5517#include "chrome/browser/ui/browser_list.h"
18#include "chrome/browser/ui/browser_tabstrip.h"
19#include "chrome/browser/ui/tabs/tab_strip_model.h"
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:3720#include "chrome/browser/webshare/webshare_target.h"
constantina5558f3f32017-02-13 05:37:5421#include "chrome/common/pref_names.h"
22#include "components/prefs/pref_service.h"
constantinac8b2173b2016-12-15 05:55:5123#include "mojo/public/cpp/bindings/strong_binding.h"
constantina2cfa55e2017-01-13 06:36:5524#include "net/base/escape.h"
25
26namespace {
27
28// Determines whether a character is allowed in a URL template placeholder.
29bool IsIdentifier(char c) {
30 return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || c == '-' || c == '_';
31}
32
constantina2cfa55e2017-01-13 06:36:5533} // namespace
constantinac8b2173b2016-12-15 05:55:5134
mgiucabd4b24d2017-02-17 01:40:5735ShareServiceImpl::ShareServiceImpl() : weak_factory_(this) {}
constantina5558f3f32017-02-13 05:37:5436ShareServiceImpl::~ShareServiceImpl() = default;
37
constantinac8b2173b2016-12-15 05:55:5138// static
bena5c972c2017-05-04 01:38:4339void ShareServiceImpl::Create(
bena5c972c2017-05-04 01:38:4340 blink::mojom::ShareServiceRequest request) {
constantinac8b2173b2016-12-15 05:55:5141 mojo::MakeStrongBinding(base::MakeUnique<ShareServiceImpl>(),
42 std::move(request));
43}
44
constantina2cfa55e2017-01-13 06:36:5545// static
46bool ShareServiceImpl::ReplacePlaceholders(base::StringPiece url_template,
47 base::StringPiece title,
48 base::StringPiece text,
49 const GURL& share_url,
50 std::string* url_template_filled) {
51 constexpr char kTitlePlaceholder[] = "title";
52 constexpr char kTextPlaceholder[] = "text";
53 constexpr char kUrlPlaceholder[] = "url";
54
55 std::map<base::StringPiece, std::string> placeholder_to_data;
56 placeholder_to_data[kTitlePlaceholder] =
57 net::EscapeQueryParamValue(title, false);
58 placeholder_to_data[kTextPlaceholder] =
59 net::EscapeQueryParamValue(text, false);
60 placeholder_to_data[kUrlPlaceholder] =
61 net::EscapeQueryParamValue(share_url.spec(), false);
62
63 std::vector<base::StringPiece> split_template;
64 bool last_saw_open = false;
65 size_t start_index_to_copy = 0;
66 for (size_t i = 0; i < url_template.size(); ++i) {
67 if (last_saw_open) {
68 if (url_template[i] == '}') {
69 base::StringPiece placeholder = url_template.substr(
70 start_index_to_copy + 1, i - 1 - start_index_to_copy);
71 auto it = placeholder_to_data.find(placeholder);
72 if (it != placeholder_to_data.end()) {
73 // Replace the placeholder text with the parameter value.
74 split_template.push_back(it->second);
75 }
76
77 last_saw_open = false;
78 start_index_to_copy = i + 1;
79 } else if (!IsIdentifier(url_template[i])) {
80 // Error: Non-identifier character seen after open.
81 return false;
82 }
83 } else {
84 if (url_template[i] == '}') {
85 // Error: Saw close, with no corresponding open.
86 return false;
87 } else if (url_template[i] == '{') {
88 split_template.push_back(
89 url_template.substr(start_index_to_copy, i - start_index_to_copy));
90
91 last_saw_open = true;
92 start_index_to_copy = i;
93 }
94 }
95 }
96 if (last_saw_open) {
97 // Error: Saw open that was never closed.
98 return false;
99 }
100 split_template.push_back(url_template.substr(
101 start_index_to_copy, url_template.size() - start_index_to_copy));
102
mgiucab0643122017-02-23 08:41:40103 *url_template_filled = base::JoinString(split_template, base::StringPiece());
constantina2cfa55e2017-01-13 06:36:55104 return true;
105}
106
mgiuca39e40672017-01-31 04:16:49107void ShareServiceImpl::ShowPickerDialog(
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37108 std::vector<WebShareTarget> targets,
ortuno928d14102017-05-02 00:09:05109 chrome::WebShareTargetPickerCallback callback) {
110 // TODO(mgiuca): Get the browser window as |parent_window|.
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37111 chrome::ShowWebShareTargetPickerDialog(
112 nullptr /* parent_window */, std::move(targets), std::move(callback));
mgiuca39e40672017-01-31 04:16:49113}
114
constantina5558f3f32017-02-13 05:37:54115Browser* ShareServiceImpl::GetBrowser() {
constantina5558f3f32017-02-13 05:37:54116 return BrowserList::GetInstance()->GetLastActive();
constantina5558f3f32017-02-13 05:37:54117}
118
constantina2cfa55e2017-01-13 06:36:55119void ShareServiceImpl::OpenTargetURL(const GURL& target_url) {
constantina5558f3f32017-02-13 05:37:54120 Browser* browser = GetBrowser();
constantina2cfa55e2017-01-13 06:36:55121 chrome::AddTabAt(browser, target_url,
122 browser->tab_strip_model()->active_index() + 1, true);
constantina2cfa55e2017-01-13 06:36:55123}
124
constantina5558f3f32017-02-13 05:37:54125PrefService* ShareServiceImpl::GetPrefService() {
constantina5558f3f32017-02-13 05:37:54126 return GetBrowser()->profile()->GetPrefs();
constantina5558f3f32017-02-13 05:37:54127}
128
129blink::mojom::EngagementLevel ShareServiceImpl::GetEngagementLevel(
130 const GURL& url) {
constantina5558f3f32017-02-13 05:37:54131 SiteEngagementService* site_engagement_service =
132 SiteEngagementService::Get(GetBrowser()->profile());
133 return site_engagement_service->GetEngagementLevel(url);
constantina5558f3f32017-02-13 05:37:54134}
135
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37136std::vector<WebShareTarget>
137ShareServiceImpl::GetTargetsWithSufficientEngagement() {
constantina5558f3f32017-02-13 05:37:54138 constexpr blink::mojom::EngagementLevel kMinimumEngagementLevel =
139 blink::mojom::EngagementLevel::LOW;
140
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37141 PrefService* pref_service = GetPrefService();
constantina5558f3f32017-02-13 05:37:54142
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37143 std::unique_ptr<base::DictionaryValue> share_targets_dict =
144 pref_service->GetDictionary(prefs::kWebShareVisitedTargets)
145 ->CreateDeepCopy();
constantina5558f3f32017-02-13 05:37:54146
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37147 std::vector<WebShareTarget> sufficiently_engaged_targets;
148 for (const auto& it : *share_targets_dict) {
149 GURL manifest_url(it.first);
150 DCHECK(manifest_url.is_valid());
constantina5558f3f32017-02-13 05:37:54151
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37152 if (GetEngagementLevel(manifest_url) < kMinimumEngagementLevel)
153 continue;
154
155 const base::DictionaryValue* share_target_dict;
156 bool result = it.second->GetAsDictionary(&share_target_dict);
157 DCHECK(result);
158
159 std::string name;
160 share_target_dict->GetString("name", &name);
161 std::string url_template;
162 share_target_dict->GetString("url_template", &url_template);
163
164 sufficiently_engaged_targets.emplace_back(
165 std::move(manifest_url), std::move(name), std::move(url_template));
constantina5558f3f32017-02-13 05:37:54166 }
167
168 return sufficiently_engaged_targets;
169}
170
constantinac8b2173b2016-12-15 05:55:51171void ShareServiceImpl::Share(const std::string& title,
172 const std::string& text,
constantina2cfa55e2017-01-13 06:36:55173 const GURL& share_url,
tzikcf7bcd652017-06-15 04:19:30174 ShareCallback callback) {
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37175 std::vector<WebShareTarget> sufficiently_engaged_targets =
176 GetTargetsWithSufficientEngagement();
mgiuca39e40672017-01-31 04:16:49177
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37178 ShowPickerDialog(std::move(sufficiently_engaged_targets),
179 base::BindOnce(&ShareServiceImpl::OnPickerClosed,
180 weak_factory_.GetWeakPtr(), title, text,
181 share_url, std::move(callback)));
mgiuca39e40672017-01-31 04:16:49182}
183
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37184void ShareServiceImpl::OnPickerClosed(const std::string& title,
185 const std::string& text,
186 const GURL& share_url,
187 ShareCallback callback,
188 const WebShareTarget* result) {
189 if (result == nullptr) {
tzikcf7bcd652017-06-15 04:19:30190 std::move(callback).Run(blink::mojom::ShareError::CANCELED);
mgiuca39e40672017-01-31 04:16:49191 return;
192 }
193
constantina2cfa55e2017-01-13 06:36:55194 std::string url_template_filled;
Giovanni Ortuño Urquidi97f8bd522017-06-16 04:04:37195 if (!ReplacePlaceholders(result->url_template(), title, text, share_url,
constantina2cfa55e2017-01-13 06:36:55196 &url_template_filled)) {
mgiuca17e725c2017-03-03 02:57:23197 // TODO(mgiuca): This error should not be possible at share time, because
198 // targets with invalid templates should not be chooseable. Fix
199 // https://crbug.com/694380 and replace this with a DCHECK.
tzikcf7bcd652017-06-15 04:19:30200 std::move(callback).Run(blink::mojom::ShareError::INTERNAL_ERROR);
constantina2cfa55e2017-01-13 06:36:55201 return;
202 }
203
constantina5558f3f32017-02-13 05:37:54204 // The template is relative to the manifest URL (minus the filename).
Giovanni Ortuño Urquidic88d5952017-07-31 04:35:58205 // Resolve it based on the manifest URL to make an absolute URL.
206 const GURL target = result->manifest_url().Resolve(url_template_filled);
mgiucaf3a4c632017-02-21 08:08:59207 // User should not be able to cause an invalid target URL. Possibilities are:
208 // - The base URL: can't be invalid since it's derived from the manifest URL.
209 // - The template: can only be invalid if it contains a NUL character or
210 // invalid UTF-8 sequence (which it can't have).
211 // - The replaced pieces: these are escaped.
212 // If somehow we slip through this DCHECK, it will just open about:blank.
213 DCHECK(target.is_valid());
constantina5558f3f32017-02-13 05:37:54214 OpenTargetURL(target);
constantina2cfa55e2017-01-13 06:36:55215
tzikcf7bcd652017-06-15 04:19:30216 std::move(callback).Run(blink::mojom::ShareError::OK);
constantinac8b2173b2016-12-15 05:55:51217}