blob: ed0fd51c48d196bd6cb3fb291ecdbdcf064c48ae [file] [log] [blame]
fgorski1d4c9c92015-12-17 20:39:321// Copyright 2015 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
Dmitry Titov909e8d62017-07-19 01:39:565#include "chrome/browser/offline_pages/offline_page_utils.h"
fgorski1d4c9c92015-12-17 20:39:326
dewittjfd1614e52016-06-06 18:36:017#include "base/bind.h"
8#include "base/location.h"
jianli81fd5882017-04-12 01:42:049#include "base/metrics/histogram_macros.h"
tripta.g3e097a92017-06-22 07:56:2610#include "base/stl_util.h"
bburns16d15332016-03-04 00:34:0911#include "base/strings/string_number_conversions.h"
fgorski1d4c9c92015-12-17 20:39:3212#include "base/strings/string_piece.h"
13#include "base/strings/string_util.h"
Jian Li009b19a2018-01-05 23:05:3014#include "base/task_scheduler/post_task.h"
dewittjfd1614e52016-06-06 18:36:0115#include "base/threading/thread_task_runner_handle.h"
fgorskie8aae2c2016-11-23 23:45:3916#include "base/time/time.h"
Dmitry Titov909e8d62017-07-19 01:39:5617#include "build/build_config.h"
jianlib0fe12b2017-01-12 01:14:0818#include "chrome/browser/net/net_error_tab_helper.h"
Dmitry Titov909e8d62017-07-19 01:39:5619#include "chrome/browser/offline_pages/offline_page_mhtml_archiver.h"
Jian Lie632ebc82017-06-30 23:40:3220#include "chrome/browser/offline_pages/offline_page_model_factory.h"
Cathy Li69ed5a82017-08-25 23:03:5021#include "chrome/browser/offline_pages/offline_page_origin_utils.h"
22#include "chrome/browser/offline_pages/offline_page_tab_helper.h"
Dmitry Titov909e8d62017-07-19 01:39:5623#include "chrome/browser/offline_pages/request_coordinator_factory.h"
chiliecd23802016-12-07 10:27:1124#include "components/offline_pages/core/background/request_coordinator.h"
25#include "components/offline_pages/core/background/save_page_request.h"
26#include "components/offline_pages/core/client_namespace_constants.h"
27#include "components/offline_pages/core/client_policy_controller.h"
28#include "components/offline_pages/core/offline_page_feature.h"
29#include "components/offline_pages/core/offline_page_item.h"
30#include "components/offline_pages/core/offline_page_model.h"
31#include "components/offline_pages/core/request_header/offline_page_header.h"
fgorski1d4c9c92015-12-17 20:39:3232#include "content/public/browser/browser_context.h"
jianli918265d2017-02-28 01:50:1733#include "content/public/browser/navigation_entry.h"
dewittj44d11cb2016-06-17 23:30:2834#include "content/public/browser/web_contents.h"
jianli030745882017-04-28 08:35:1835#include "net/base/mime_util.h"
fgorski1d4c9c92015-12-17 20:39:3236
37namespace offline_pages {
38namespace {
39
Jian Li009b19a2018-01-05 23:05:3040class OfflinePageComparer {
41 public:
42 OfflinePageComparer() = default;
43
44 bool operator()(const OfflinePageItem& a, const OfflinePageItem& b) {
45 return a.creation_time > b.creation_time;
46 }
47};
48
jianli610b1fd2016-11-17 23:17:5249void OnGetPagesByURLDone(
50 const GURL& url,
jianlib76ea422016-08-11 02:30:5751 int tab_id,
dewittj7be8aa02016-10-07 18:19:1452 const std::vector<std::string>& namespaces_to_show_in_original_tab,
Jian Li009b19a2018-01-05 23:05:3053 const base::Callback<void(const std::vector<OfflinePageItem>&)>& callback,
jianlib76ea422016-08-11 02:30:5754 const MultipleOfflinePageItemResult& pages) {
Jian Li009b19a2018-01-05 23:05:3055 std::vector<OfflinePageItem> selected_pages;
jianlib76ea422016-08-11 02:30:5756 std::string tab_id_str = base::IntToString(tab_id);
dewittj7be8aa02016-10-07 18:19:1457
Jian Li009b19a2018-01-05 23:05:3058 // Exclude pages whose tab id does not match.
jianli610b1fd2016-11-17 23:17:5259 for (const auto& page : pages) {
tripta.g3e097a92017-06-22 07:56:2660 if (base::ContainsValue(namespaces_to_show_in_original_tab,
61 page.client_id.name_space) &&
jianli610b1fd2016-11-17 23:17:5262 page.client_id.id != tab_id_str) {
dewittj7be8aa02016-10-07 18:19:1463 continue;
64 }
Jian Li009b19a2018-01-05 23:05:3065 selected_pages.push_back(page);
jianlib76ea422016-08-11 02:30:5766 }
jianli610b1fd2016-11-17 23:17:5267
Jian Li009b19a2018-01-05 23:05:3068 // Sort based on creation date.
69 std::sort(selected_pages.begin(), selected_pages.end(),
70 OfflinePageComparer());
71
72 callback.Run(selected_pages);
jianlib76ea422016-08-11 02:30:5773}
74
jianli81fd5882017-04-12 01:42:0475bool IsSupportedByDownload(content::BrowserContext* browser_context,
76 const std::string& name_space) {
77 OfflinePageModel* offline_page_model =
78 OfflinePageModelFactory::GetForBrowserContext(browser_context);
79 DCHECK(offline_page_model);
80 ClientPolicyController* policy_controller =
81 offline_page_model->GetPolicyController();
82 DCHECK(policy_controller);
83 return policy_controller->IsSupportedByDownload(name_space);
84}
85
86void CheckDuplicateOngoingDownloads(
87 content::BrowserContext* browser_context,
88 const GURL& url,
89 const OfflinePageUtils::DuplicateCheckCallback& callback) {
90 RequestCoordinator* request_coordinator =
91 RequestCoordinatorFactory::GetForBrowserContext(browser_context);
92 if (!request_coordinator)
93 return;
94
95 auto request_coordinator_continuation =
96 [](content::BrowserContext* browser_context, const GURL& url,
97 const OfflinePageUtils::DuplicateCheckCallback& callback,
98 std::vector<std::unique_ptr<SavePageRequest>> requests) {
99 base::Time latest_request_time;
100 for (auto& request : requests) {
101 if (IsSupportedByDownload(browser_context,
102 request->client_id().name_space) &&
103 request->url() == url &&
104 latest_request_time < request->creation_time()) {
105 latest_request_time = request->creation_time();
106 }
107 }
108
109 if (latest_request_time.is_null()) {
110 callback.Run(OfflinePageUtils::DuplicateCheckResult::NOT_FOUND);
111 } else {
112 // Using CUSTOM_COUNTS instead of time-oriented histogram to record
113 // samples in seconds rather than milliseconds.
114 UMA_HISTOGRAM_CUSTOM_COUNTS(
115 "OfflinePages.DownloadRequestTimeSinceDuplicateRequested",
116 (base::Time::Now() - latest_request_time).InSeconds(),
117 base::TimeDelta::FromSeconds(1).InSeconds(),
118 base::TimeDelta::FromDays(7).InSeconds(), 50);
119
120 callback.Run(
121 OfflinePageUtils::DuplicateCheckResult::DUPLICATE_REQUEST_FOUND);
122 }
123 };
124
125 request_coordinator->GetAllRequests(base::Bind(
126 request_coordinator_continuation, browser_context, url, callback));
127}
128
romax444db4b2017-05-16 23:52:38129void DoCalculateSizeBetween(
130 const offline_pages::SizeInBytesCallback& callback,
131 const base::Time& begin_time,
132 const base::Time& end_time,
133 const offline_pages::MultipleOfflinePageItemResult& result) {
134 int64_t total_size = 0;
135 for (auto& page : result) {
136 if (begin_time <= page.creation_time && page.creation_time < end_time)
137 total_size += page.file_size;
138 }
139 callback.Run(total_size);
140}
141
fgorski1d4c9c92015-12-17 20:39:32142} // namespace
143
144// static
Jian Li009b19a2018-01-05 23:05:30145void OfflinePageUtils::SelectPagesForURL(
jianlib76ea422016-08-11 02:30:57146 content::BrowserContext* browser_context,
jianli610b1fd2016-11-17 23:17:52147 const GURL& url,
Yafei Duan3caef8c2017-08-10 00:55:24148 URLSearchMode url_search_mode,
jianlib76ea422016-08-11 02:30:57149 int tab_id,
Jian Li009b19a2018-01-05 23:05:30150 const base::Callback<void(const std::vector<OfflinePageItem>&)>& callback) {
jianlib76ea422016-08-11 02:30:57151 OfflinePageModel* offline_page_model =
152 OfflinePageModelFactory::GetForBrowserContext(browser_context);
153 if (!offline_page_model) {
154 base::ThreadTaskRunnerHandle::Get()->PostTask(
Jian Li009b19a2018-01-05 23:05:30155 FROM_HERE, base::Bind(callback, std::vector<OfflinePageItem>()));
jianlib76ea422016-08-11 02:30:57156 return;
157 }
158
jianli610b1fd2016-11-17 23:17:52159 offline_page_model->GetPagesByURL(
160 url,
161 url_search_mode,
162 base::Bind(&OnGetPagesByURLDone, url, tab_id,
163 offline_page_model->GetPolicyController()
164 ->GetNamespacesRestrictedToOriginalTab(),
165 callback));
jianlib76ea422016-08-11 02:30:57166}
167
dewittj44d11cb2016-06-17 23:30:28168const OfflinePageItem* OfflinePageUtils::GetOfflinePageFromWebContents(
169 content::WebContents* web_contents) {
170 OfflinePageTabHelper* tab_helper =
171 OfflinePageTabHelper::FromWebContents(web_contents);
jianlif68c52f2016-09-20 22:10:00172 if (!tab_helper)
173 return nullptr;
174 const OfflinePageItem* offline_page = tab_helper->offline_page();
175 if (!offline_page)
176 return nullptr;
Jian Li5de7b7e2017-12-15 22:28:24177 // TODO(jianli): Remove this when the UI knows how to handle untrusted
178 // offline pages.
179 if (!tab_helper->IsShowingTrustedOfflinePage())
180 return nullptr;
jianlif68c52f2016-09-20 22:10:00181
Jian Li5de7b7e2017-12-15 22:28:24182 // If a pending navigation that hasn't committed yet, don't return the cached
183 // offline page that was set at the last commit time. This is to prevent
jianlif68c52f2016-09-20 22:10:00184 // from returning the wrong offline page if DidStartNavigation is never called
185 // to clear it up.
Jian Li5de7b7e2017-12-15 22:28:24186 if (!EqualsIgnoringFragment(web_contents->GetVisibleURL(),
187 web_contents->GetLastCommittedURL())) {
188 return nullptr;
189 }
190
191 return offline_page;
jianlif68c52f2016-09-20 22:10:00192}
193
194// static
195const OfflinePageHeader* OfflinePageUtils::GetOfflineHeaderFromWebContents(
196 content::WebContents* web_contents) {
197 OfflinePageTabHelper* tab_helper =
198 OfflinePageTabHelper::FromWebContents(web_contents);
199 return tab_helper ? &(tab_helper->offline_header()) : nullptr;
dewittj44d11cb2016-06-17 23:30:28200}
201
fgorski7a82d312016-06-28 22:09:34202// static
ryansturmad9e85d32016-10-28 17:28:06203bool OfflinePageUtils::IsShowingOfflinePreview(
204 content::WebContents* web_contents) {
205 OfflinePageTabHelper* tab_helper =
206 OfflinePageTabHelper::FromWebContents(web_contents);
Ryan Sturm94938902017-12-09 23:53:53207 return tab_helper && tab_helper->GetOfflinePreviewItem();
ryansturmad9e85d32016-10-28 17:28:06208}
209
210// static
jianlib0fe12b2017-01-12 01:14:08211bool OfflinePageUtils::IsShowingDownloadButtonInErrorPage(
212 content::WebContents* web_contents) {
213 chrome_browser_net::NetErrorTabHelper* tab_helper =
214 chrome_browser_net::NetErrorTabHelper::FromWebContents(web_contents);
215 return tab_helper && tab_helper->is_showing_download_button_in_error_page();
216}
217
218// static
fgorski2684ff82016-10-07 17:49:40219bool OfflinePageUtils::EqualsIgnoringFragment(const GURL& lhs,
220 const GURL& rhs) {
221 GURL::Replacements remove_params;
222 remove_params.ClearRef();
223
224 GURL lhs_stripped = lhs.ReplaceComponents(remove_params);
fgorski1acc2c42016-11-29 01:15:01225 GURL rhs_stripped = rhs.ReplaceComponents(remove_params);
fgorski2684ff82016-10-07 17:49:40226
227 return lhs_stripped == rhs_stripped;
228}
229
dimich4f6b2802016-12-20 00:18:33230// static
jianli918265d2017-02-28 01:50:17231GURL OfflinePageUtils::GetOriginalURLFromWebContents(
232 content::WebContents* web_contents) {
233 content::NavigationEntry* entry =
234 web_contents->GetController().GetLastCommittedEntry();
235 if (!entry || entry->GetRedirectChain().size() <= 1)
236 return GURL();
237 return entry->GetRedirectChain().front();
238}
239
jianli81fd5882017-04-12 01:42:04240// static
241void OfflinePageUtils::CheckDuplicateDownloads(
242 content::BrowserContext* browser_context,
243 const GURL& url,
244 const DuplicateCheckCallback& callback) {
245 // First check for finished downloads, that is, saved pages.
246 OfflinePageModel* offline_page_model =
247 OfflinePageModelFactory::GetForBrowserContext(browser_context);
248 if (!offline_page_model)
249 return;
250
251 auto continuation = [](content::BrowserContext* browser_context,
252 const GURL& url,
253 const DuplicateCheckCallback& callback,
254 const std::vector<OfflinePageItem>& pages) {
255 base::Time latest_saved_time;
256 for (const auto& offline_page_item : pages) {
257 if (IsSupportedByDownload(browser_context,
258 offline_page_item.client_id.name_space) &&
259 latest_saved_time < offline_page_item.creation_time) {
260 latest_saved_time = offline_page_item.creation_time;
261 }
262 }
263 if (latest_saved_time.is_null()) {
264 // Then check for ongoing downloads, that is, requests.
265 CheckDuplicateOngoingDownloads(browser_context, url, callback);
266 } else {
267 // Using CUSTOM_COUNTS instead of time-oriented histogram to record
268 // samples in seconds rather than milliseconds.
269 UMA_HISTOGRAM_CUSTOM_COUNTS(
270 "OfflinePages.DownloadRequestTimeSinceDuplicateSaved",
271 (base::Time::Now() - latest_saved_time).InSeconds(),
272 base::TimeDelta::FromSeconds(1).InSeconds(),
273 base::TimeDelta::FromDays(7).InSeconds(), 50);
274
275 callback.Run(DuplicateCheckResult::DUPLICATE_PAGE_FOUND);
276 }
277 };
278
279 offline_page_model->GetPagesByURL(
Yafei Duan3caef8c2017-08-10 00:55:24280 url, URLSearchMode::SEARCH_BY_ALL_URLS,
jianli81fd5882017-04-12 01:42:04281 base::Bind(continuation, browser_context, url, callback));
282}
283
284// static
285void OfflinePageUtils::ScheduleDownload(content::WebContents* web_contents,
286 const std::string& name_space,
287 const GURL& url,
Cathy Li7187388f2017-08-09 15:34:51288 DownloadUIActionFlags ui_action,
289 const std::string& request_origin) {
jianli81fd5882017-04-12 01:42:04290 DCHECK(web_contents);
291
292 OfflinePageTabHelper* tab_helper =
293 OfflinePageTabHelper::FromWebContents(web_contents);
294 if (!tab_helper)
295 return;
Cathy Li7187388f2017-08-09 15:34:51296 tab_helper->ScheduleDownloadHelper(web_contents, name_space, url, ui_action,
297 request_origin);
298}
299
300// static
301void OfflinePageUtils::ScheduleDownload(content::WebContents* web_contents,
302 const std::string& name_space,
303 const GURL& url,
304 DownloadUIActionFlags ui_action) {
Cathy Li69ed5a82017-08-25 23:03:50305 std::string origin =
306 OfflinePageOriginUtils::GetEncodedOriginAppFor(web_contents);
307 ScheduleDownload(web_contents, name_space, url, ui_action, origin);
jianli81fd5882017-04-12 01:42:04308}
309
jianli030745882017-04-28 08:35:18310// static
311bool OfflinePageUtils::CanDownloadAsOfflinePage(
312 const GURL& url,
313 const std::string& contents_mime_type) {
314 return url.SchemeIsHTTPOrHTTPS() &&
315 (net::MatchesMimeType(contents_mime_type, "text/html") ||
316 net::MatchesMimeType(contents_mime_type, "application/xhtml+xml"));
317}
318
romax444db4b2017-05-16 23:52:38319// static
320bool OfflinePageUtils::GetCachedOfflinePageSizeBetween(
321 content::BrowserContext* browser_context,
322 const SizeInBytesCallback& callback,
323 const base::Time& begin_time,
324 const base::Time& end_time) {
325 OfflinePageModel* offline_page_model =
326 OfflinePageModelFactory::GetForBrowserContext(browser_context);
327 if (!offline_page_model || begin_time > end_time)
328 return false;
Filip Gorskifea9e532017-09-22 06:07:44329 offline_page_model->GetPagesRemovedOnCacheReset(
romax444db4b2017-05-16 23:52:38330 base::Bind(&DoCalculateSizeBetween, callback, begin_time, end_time));
331 return true;
332}
333
Jian Li63ceb212018-01-04 02:04:33334// static
Jian Li63ceb212018-01-04 02:04:33335std::string OfflinePageUtils::ExtractOfflineHeaderValueFromNavigationEntry(
336 const content::NavigationEntry& entry) {
337 std::string extra_headers = entry.GetExtraHeaders();
338 if (extra_headers.empty())
339 return std::string();
340
341 // The offline header will be the only extra header if it is present.
342 std::string offline_header_key(offline_pages::kOfflinePageHeader);
343 offline_header_key += ": ";
344 if (!base::StartsWith(extra_headers, offline_header_key,
345 base::CompareCase::INSENSITIVE_ASCII)) {
346 return std::string();
347 }
348 std::string header_value = extra_headers.substr(offline_header_key.length());
349 if (header_value.find("\n") != std::string::npos)
350 return std::string();
351
352 return header_value;
353}
354
fgorski1d4c9c92015-12-17 20:39:32355} // namespace offline_pages