blob: 5af183300abbd0783a87913b8c853481ae9f9485 [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>
9#include <utility>
10
11#include "base/strings/string_util.h"
mgiuca39e40672017-01-31 04:16:4912#include "base/strings/utf_string_conversions.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"
constantina5558f3f32017-02-13 05:37:5420#include "chrome/common/pref_names.h"
21#include "components/prefs/pref_service.h"
constantinac8b2173b2016-12-15 05:55:5122#include "mojo/public/cpp/bindings/strong_binding.h"
constantina2cfa55e2017-01-13 06:36:5523#include "net/base/escape.h"
24
25namespace {
26
27// Determines whether a character is allowed in a URL template placeholder.
28bool IsIdentifier(char c) {
29 return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || c == '-' || c == '_';
30}
31
constantina2cfa55e2017-01-13 06:36:5532} // namespace
constantinac8b2173b2016-12-15 05:55:5133
mgiucabd4b24d2017-02-17 01:40:5734ShareServiceImpl::ShareServiceImpl() : weak_factory_(this) {}
constantina5558f3f32017-02-13 05:37:5435ShareServiceImpl::~ShareServiceImpl() = default;
36
constantinac8b2173b2016-12-15 05:55:5137// static
bena5c972c2017-05-04 01:38:4338void ShareServiceImpl::Create(
39 const service_manager::BindSourceInfo& source_info,
40 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(
constantinae4c513e72017-02-07 02:14:10108 const std::vector<std::pair<base::string16, GURL>>& targets,
ortuno928d14102017-05-02 00:09:05109 chrome::WebShareTargetPickerCallback callback) {
110 // TODO(mgiuca): Get the browser window as |parent_window|.
mgiuca39e40672017-01-31 04:16:49111 chrome::ShowWebShareTargetPickerDialog(nullptr /* parent_window */, targets,
ortuno928d14102017-05-02 00:09:05112 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:54125std::string ShareServiceImpl::GetTargetTemplate(
126 const std::string& target_url,
127 const base::DictionaryValue& share_targets) {
128 const base::DictionaryValue* share_target_info_dict = nullptr;
129 share_targets.GetDictionaryWithoutPathExpansion(target_url,
130 &share_target_info_dict);
131
132 std::string url_template;
133 share_target_info_dict->GetString("url_template", &url_template);
134 return url_template;
135}
136
137PrefService* ShareServiceImpl::GetPrefService() {
constantina5558f3f32017-02-13 05:37:54138 return GetBrowser()->profile()->GetPrefs();
constantina5558f3f32017-02-13 05:37:54139}
140
141blink::mojom::EngagementLevel ShareServiceImpl::GetEngagementLevel(
142 const GURL& url) {
constantina5558f3f32017-02-13 05:37:54143 SiteEngagementService* site_engagement_service =
144 SiteEngagementService::Get(GetBrowser()->profile());
145 return site_engagement_service->GetEngagementLevel(url);
constantina5558f3f32017-02-13 05:37:54146}
147
148// static
149std::vector<std::pair<base::string16, GURL>>
150ShareServiceImpl::GetTargetsWithSufficientEngagement(
151 const base::DictionaryValue& share_targets) {
152 constexpr blink::mojom::EngagementLevel kMinimumEngagementLevel =
153 blink::mojom::EngagementLevel::LOW;
154
155 std::vector<std::pair<base::string16, GURL>> sufficiently_engaged_targets;
156
157 for (base::DictionaryValue::Iterator it(share_targets); !it.IsAtEnd();
158 it.Advance()) {
159 GURL manifest_url(it.key());
160 if (GetEngagementLevel(manifest_url) >= kMinimumEngagementLevel) {
161 const base::DictionaryValue* share_target_dict;
162 bool result = it.value().GetAsDictionary(&share_target_dict);
163 DCHECK(result);
164
165 std::string name;
166 share_target_dict->GetString("name", &name);
167
168 sufficiently_engaged_targets.push_back(
169 make_pair(base::UTF8ToUTF16(name), manifest_url));
170 }
171 }
172
173 return sufficiently_engaged_targets;
174}
175
constantinac8b2173b2016-12-15 05:55:51176void ShareServiceImpl::Share(const std::string& title,
177 const std::string& text,
constantina2cfa55e2017-01-13 06:36:55178 const GURL& share_url,
tzikcf7bcd652017-06-15 04:19:30179 ShareCallback callback) {
constantina5558f3f32017-02-13 05:37:54180 std::unique_ptr<base::DictionaryValue> share_targets;
mgiuca39e40672017-01-31 04:16:49181
constantina5558f3f32017-02-13 05:37:54182 share_targets = GetPrefService()
183 ->GetDictionary(prefs::kWebShareVisitedTargets)
184 ->CreateDeepCopy();
constantina5558f3f32017-02-13 05:37:54185
186 std::vector<std::pair<base::string16, GURL>> sufficiently_engaged_targets =
187 GetTargetsWithSufficientEngagement(*share_targets);
188
189 ShowPickerDialog(
190 sufficiently_engaged_targets,
ortuno928d14102017-05-02 00:09:05191 base::BindOnce(&ShareServiceImpl::OnPickerClosed,
192 weak_factory_.GetWeakPtr(), base::Passed(&share_targets),
tzikcf7bcd652017-06-15 04:19:30193 title, text, share_url, std::move(callback)));
mgiuca39e40672017-01-31 04:16:49194}
195
constantina5558f3f32017-02-13 05:37:54196void ShareServiceImpl::OnPickerClosed(
197 std::unique_ptr<base::DictionaryValue> share_targets,
198 const std::string& title,
199 const std::string& text,
200 const GURL& share_url,
tzikcf7bcd652017-06-15 04:19:30201 ShareCallback callback,
ortuno928d14102017-05-02 00:09:05202 const base::Optional<std::string>& result) {
constantinae4c513e72017-02-07 02:14:10203 if (!result.has_value()) {
tzikcf7bcd652017-06-15 04:19:30204 std::move(callback).Run(blink::mojom::ShareError::CANCELED);
mgiuca39e40672017-01-31 04:16:49205 return;
206 }
207
constantina5558f3f32017-02-13 05:37:54208 std::string chosen_target = result.value();
constantina2cfa55e2017-01-13 06:36:55209
constantina5558f3f32017-02-13 05:37:54210 std::string url_template = GetTargetTemplate(chosen_target, *share_targets);
constantina2cfa55e2017-01-13 06:36:55211 std::string url_template_filled;
constantina5558f3f32017-02-13 05:37:54212 if (!ReplacePlaceholders(url_template, title, text, share_url,
constantina2cfa55e2017-01-13 06:36:55213 &url_template_filled)) {
mgiuca17e725c2017-03-03 02:57:23214 // TODO(mgiuca): This error should not be possible at share time, because
215 // targets with invalid templates should not be chooseable. Fix
216 // https://crbug.com/694380 and replace this with a DCHECK.
tzikcf7bcd652017-06-15 04:19:30217 std::move(callback).Run(blink::mojom::ShareError::INTERNAL_ERROR);
constantina2cfa55e2017-01-13 06:36:55218 return;
219 }
220
constantina5558f3f32017-02-13 05:37:54221 // The template is relative to the manifest URL (minus the filename).
222 // Concatenate to make an absolute URL.
223 base::StringPiece url_base(
224 chosen_target.data(),
225 chosen_target.size() - GURL(chosen_target).ExtractFileName().size());
226 const GURL target(url_base.as_string() + url_template_filled);
mgiucaf3a4c632017-02-21 08:08:59227 // User should not be able to cause an invalid target URL. Possibilities are:
228 // - The base URL: can't be invalid since it's derived from the manifest URL.
229 // - The template: can only be invalid if it contains a NUL character or
230 // invalid UTF-8 sequence (which it can't have).
231 // - The replaced pieces: these are escaped.
232 // If somehow we slip through this DCHECK, it will just open about:blank.
233 DCHECK(target.is_valid());
constantina5558f3f32017-02-13 05:37:54234 OpenTargetURL(target);
constantina2cfa55e2017-01-13 06:36:55235
tzikcf7bcd652017-06-15 04:19:30236 std::move(callback).Run(blink::mojom::ShareError::OK);
constantinac8b2173b2016-12-15 05:55:51237}