1 // Copyright 2013 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/renderer/net/net_error_helper_core.h"
6 
7 #include <stddef.h>
8 
9 #include <memory>
10 #include <set>
11 #include <string>
12 #include <utility>
13 #include <vector>
14 
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/command_line.h"
18 #include "base/feature_list.h"
19 #include "base/i18n/rtl.h"
20 #include "base/json/json_reader.h"
21 #include "base/json/json_value_converter.h"
22 #include "base/json/json_writer.h"
23 #include "base/location.h"
24 #include "base/logging.h"
25 #include "base/metrics/histogram_functions.h"
26 #include "base/metrics/histogram_macros.h"
27 #include "base/stl_util.h"
28 #include "base/strings/string16.h"
29 #include "base/strings/string_util.h"
30 #include "base/values.h"
31 #include "build/build_config.h"
32 #include "chrome/common/chrome_features.h"
33 #include "components/error_page/common/error_page_params.h"
34 #include "components/error_page/common/localized_error.h"
35 #include "components/strings/grit/components_strings.h"
36 #include "components/url_formatter/url_formatter.h"
37 #include "content/public/common/content_switches.h"
38 #include "content/public/common/url_constants.h"
39 #include "content/public/renderer/render_thread.h"
40 #include "net/base/escape.h"
41 #include "net/base/net_errors.h"
42 #include "third_party/blink/public/platform/web_string.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "url/gurl.h"
45 #include "url/url_constants.h"
46 
47 namespace {
48 
49 // |NetErrorNavigationCorrectionTypes| enum id for Web search query.
50 // Other correction types uses the |kCorrectionResourceTable| array order.
51 const int kWebSearchQueryUMAId = 100;
52 
53 // Number of URL correction suggestions to display.
54 const int kMaxUrlCorrectionsToDisplay = 1;
55 
56 struct CorrectionTypeToResourceTable {
57   int resource_id;
58   const char* correction_type;
59 };
60 
61 // Note: Ordering should be the same as |NetErrorNavigationCorrectionTypes| enum
62 // in histograms.xml.
63 const CorrectionTypeToResourceTable kCorrectionResourceTable[] = {
64     {IDS_ERRORPAGES_SUGGESTION_VISIT_GOOGLE_CACHE, "cachedPage"},
65     // "reloadPage" is has special handling.
66     {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "urlCorrection"},
67     {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "siteDomain"},
68     {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "host"},
69     {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "sitemap"},
70     {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "pathParentFolder"},
71     // "siteSearchQuery" is not yet supported.
72     // TODO(mmenke):  Figure out what format "siteSearchQuery" uses for its
73     // suggestions.
74     // "webSearchQuery" has special handling.
75     {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "contentOverlap"},
76     {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "emphasizedUrlCorrection"},
77 };
78 
79 struct NavigationCorrection {
NavigationCorrection__anond57e67b40111::NavigationCorrection80   NavigationCorrection() : is_porn(false), is_soft_porn(false) {}
81 
RegisterJSONConverter__anond57e67b40111::NavigationCorrection82   static void RegisterJSONConverter(
83       base::JSONValueConverter<NavigationCorrection>* converter) {
84     converter->RegisterStringField("correctionType",
85                                    &NavigationCorrection::correction_type);
86     converter->RegisterStringField("urlCorrection",
87                                    &NavigationCorrection::url_correction);
88     converter->RegisterStringField("clickType",
89                                    &NavigationCorrection::click_type);
90     converter->RegisterStringField("clickData",
91                                    &NavigationCorrection::click_data);
92     converter->RegisterBoolField("isPorn", &NavigationCorrection::is_porn);
93     converter->RegisterBoolField("isSoftPorn",
94                                  &NavigationCorrection::is_soft_porn);
95   }
96 
97   std::string correction_type;
98   std::string url_correction;
99   std::string click_type;
100   std::string click_data;
101   bool is_porn;
102   bool is_soft_porn;
103 };
104 
105 struct NavigationCorrectionResponse {
106   std::string event_id;
107   std::string fingerprint;
108   std::vector<std::unique_ptr<NavigationCorrection>> corrections;
109 
RegisterJSONConverter__anond57e67b40111::NavigationCorrectionResponse110   static void RegisterJSONConverter(
111       base::JSONValueConverter<NavigationCorrectionResponse>* converter) {
112     converter->RegisterStringField("result.eventId",
113                                    &NavigationCorrectionResponse::event_id);
114     converter->RegisterStringField("result.fingerprint",
115                                    &NavigationCorrectionResponse::fingerprint);
116     converter->RegisterRepeatedMessage(
117         "result.UrlCorrections", &NavigationCorrectionResponse::corrections);
118   }
119 };
120 
GetAutoReloadTime(size_t reload_count)121 base::TimeDelta GetAutoReloadTime(size_t reload_count) {
122   static const int kDelaysMs[] = {0,      5000,   30000,  60000,
123                                   300000, 600000, 1800000};
124   if (reload_count >= base::size(kDelaysMs))
125     reload_count = base::size(kDelaysMs) - 1;
126   return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
127 }
128 
129 // Returns whether |error| is a DNS-related error (and therefore whether
130 // the tab helper should start a DNS probe after receiving it).
IsNetDnsError(const error_page::Error & error)131 bool IsNetDnsError(const error_page::Error& error) {
132   return error.domain() == error_page::Error::kNetErrorDomain &&
133          net::IsHostnameResolutionError(error.reason());
134 }
135 
SanitizeURL(const GURL & url)136 GURL SanitizeURL(const GURL& url) {
137   GURL::Replacements remove_params;
138   remove_params.ClearUsername();
139   remove_params.ClearPassword();
140   remove_params.ClearQuery();
141   remove_params.ClearRef();
142   return url.ReplaceComponents(remove_params);
143 }
144 
145 // Sanitizes and formats a URL for upload to the error correction service.
PrepareUrlForUpload(const GURL & url)146 std::string PrepareUrlForUpload(const GURL& url) {
147   // TODO(yuusuke): Change to url_formatter::FormatUrl when Link Doctor becomes
148   // unicode-capable.
149   std::string spec_to_send = SanitizeURL(url).spec();
150 
151   // Notify navigation correction service of the url truncation by sending of
152   // "?" at the end.
153   if (url.has_query())
154     spec_to_send.append("?");
155   return spec_to_send;
156 }
157 
158 // Given an Error, returns true if the FixURL service should be used
159 // for that error.  Also sets |error_param| to the string that should be sent to
160 // the FixURL service to identify the error type.
ShouldUseFixUrlServiceForError(const error_page::Error & error,std::string * error_param)161 bool ShouldUseFixUrlServiceForError(const error_page::Error& error,
162                                     std::string* error_param) {
163   error_param->clear();
164 
165   // Don't use the correction service for HTTPS (for privacy reasons).
166   GURL unreachable_url(error.url());
167   if (GURL(unreachable_url).SchemeIsCryptographic())
168     return false;
169 
170   const auto& domain = error.domain();
171   if (domain == error_page::Error::kHttpErrorDomain && error.reason() == 404) {
172     *error_param = "http404";
173     return true;
174   }
175   // Don't use the link doctor for secure DNS network errors, since the
176   // additional navigation may interfere with the captive portal probe state.
177   if (IsNetDnsError(error) &&
178       !error.resolve_error_info().is_secure_network_error) {
179     *error_param = "dnserror";
180     return true;
181   }
182   if (domain == error_page::Error::kNetErrorDomain &&
183       (error.reason() == net::ERR_CONNECTION_FAILED ||
184        error.reason() == net::ERR_CONNECTION_REFUSED ||
185        error.reason() == net::ERR_ADDRESS_UNREACHABLE ||
186        error.reason() == net::ERR_CONNECTION_TIMED_OUT)) {
187     *error_param = "connectionFailure";
188     return true;
189   }
190   return false;
191 }
192 
193 // Creates a request body for use with the fixurl service.  Sets parameters
194 // shared by all types of requests to the service.  |correction_params| must
195 // contain the parameters specific to the actual request type.
CreateRequestBody(const std::string & method,const std::string & error_param,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,std::unique_ptr<base::DictionaryValue> params_dict)196 std::string CreateRequestBody(
197     const std::string& method,
198     const std::string& error_param,
199     const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
200     std::unique_ptr<base::DictionaryValue> params_dict) {
201   // Set params common to all request types.
202   params_dict->SetString("key", correction_params.api_key);
203   params_dict->SetString("clientName", "chrome");
204   params_dict->SetString("error", error_param);
205 
206   if (!correction_params.language.empty())
207     params_dict->SetString("language", correction_params.language);
208 
209   if (!correction_params.country_code.empty())
210     params_dict->SetString("originCountry", correction_params.country_code);
211 
212   base::DictionaryValue request_dict;
213   request_dict.SetString("method", method);
214   request_dict.SetString("apiVersion", "v1");
215   request_dict.Set("params", std::move(params_dict));
216 
217   std::string request_body;
218   bool success = base::JSONWriter::Write(request_dict, &request_body);
219   DCHECK(success);
220   return request_body;
221 }
222 
223 // If URL correction information should be retrieved remotely for a main frame
224 // load that failed with |error|, returns true and sets
225 // |correction_request_body| to be the body for the correction request.
CreateFixUrlRequestBody(const error_page::Error & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params)226 std::string CreateFixUrlRequestBody(
227     const error_page::Error& error,
228     const NetErrorHelperCore::NavigationCorrectionParams& correction_params) {
229   std::string error_param;
230   bool result = ShouldUseFixUrlServiceForError(error, &error_param);
231   DCHECK(result);
232 
233   // TODO(mmenke):  Investigate open sourcing the relevant protocol buffers and
234   //                using those directly instead.
235   std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
236   params->SetString("urlQuery", PrepareUrlForUpload(error.url()));
237   return CreateRequestBody("linkdoctor.fixurl.fixurl", error_param,
238                            correction_params, std::move(params));
239 }
240 
CreateClickTrackingUrlRequestBody(const error_page::Error & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,const NavigationCorrectionResponse & response,const NavigationCorrection & correction)241 std::string CreateClickTrackingUrlRequestBody(
242     const error_page::Error& error,
243     const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
244     const NavigationCorrectionResponse& response,
245     const NavigationCorrection& correction) {
246   std::string error_param;
247   bool result = ShouldUseFixUrlServiceForError(error, &error_param);
248   DCHECK(result);
249 
250   std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
251 
252   params->SetString("originalUrlQuery", PrepareUrlForUpload(error.url()));
253 
254   params->SetString("clickedUrlCorrection", correction.url_correction);
255   params->SetString("clickType", correction.click_type);
256   params->SetString("clickData", correction.click_data);
257 
258   params->SetString("eventId", response.event_id);
259   params->SetString("fingerprint", response.fingerprint);
260 
261   return CreateRequestBody("linkdoctor.fixurl.clicktracking", error_param,
262                            correction_params, std::move(params));
263 }
264 
FormatURLForDisplay(const GURL & url,bool is_rtl)265 base::string16 FormatURLForDisplay(const GURL& url, bool is_rtl) {
266   // Translate punycode into UTF8, unescape UTF8 URLs.
267   base::string16 url_for_display(url_formatter::FormatUrl(
268       url, url_formatter::kFormatUrlOmitNothing, net::UnescapeRule::NORMAL,
269       nullptr, nullptr, nullptr));
270   // URLs are always LTR.
271   if (is_rtl)
272     base::i18n::WrapStringWithLTRFormatting(&url_for_display);
273   return url_for_display;
274 }
275 
ParseNavigationCorrectionResponse(const std::string raw_response)276 std::unique_ptr<NavigationCorrectionResponse> ParseNavigationCorrectionResponse(
277     const std::string raw_response) {
278   // TODO(mmenke):  Open source related protocol buffers and use them directly.
279   std::unique_ptr<base::Value> parsed =
280       base::JSONReader::ReadDeprecated(raw_response);
281   std::unique_ptr<NavigationCorrectionResponse> response(
282       new NavigationCorrectionResponse());
283   base::JSONValueConverter<NavigationCorrectionResponse> converter;
284   if (!parsed || !converter.Convert(*parsed, response.get()))
285     response.reset();
286   return response;
287 }
288 
LogCorrectionTypeShown(int type_id)289 void LogCorrectionTypeShown(int type_id) {
290   UMA_HISTOGRAM_ENUMERATION(
291       "Net.ErrorPageCounts.NavigationCorrectionLinksShown", type_id,
292       kWebSearchQueryUMAId + 1);
293 }
294 
CreateErrorPageParams(const NavigationCorrectionResponse & response,const error_page::Error & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,bool is_rtl)295 std::unique_ptr<error_page::ErrorPageParams> CreateErrorPageParams(
296     const NavigationCorrectionResponse& response,
297     const error_page::Error& error,
298     const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
299     bool is_rtl) {
300   // Version of URL for display in suggestions.  It has to be sanitized first
301   // because any received suggestions will be relative to the sanitized URL.
302   base::string16 original_url_for_display =
303       FormatURLForDisplay(SanitizeURL(GURL(error.url())), is_rtl);
304 
305   std::unique_ptr<error_page::ErrorPageParams> params(
306       new error_page::ErrorPageParams());
307   params->override_suggestions.reset(new base::ListValue());
308   std::unique_ptr<base::ListValue> parsed_corrections(new base::ListValue());
309   for (auto it = response.corrections.begin(); it != response.corrections.end();
310        ++it) {
311     // Doesn't seem like a good idea to show these.
312     if ((*it)->is_porn || (*it)->is_soft_porn)
313       continue;
314 
315     int tracking_id = it - response.corrections.begin();
316 
317     if ((*it)->correction_type == "reloadPage") {
318       params->suggest_reload = true;
319       params->reload_tracking_id = tracking_id;
320       continue;
321     }
322 
323     if ((*it)->correction_type == "webSearchQuery") {
324       // If there are multiple searches suggested, use the first suggestion.
325       if (params->search_terms.empty()) {
326         params->search_url = correction_params.search_url;
327         params->search_terms = (*it)->url_correction;
328         params->search_tracking_id = tracking_id;
329         LogCorrectionTypeShown(kWebSearchQueryUMAId);
330       }
331       continue;
332     }
333 
334     // Allow reload page and web search query to be empty strings, but not
335     // links.
336     if ((*it)->url_correction.empty() ||
337         (params->override_suggestions->GetSize() >=
338          kMaxUrlCorrectionsToDisplay)) {
339       continue;
340     }
341 
342     size_t correction_index;
343     for (correction_index = 0;
344          correction_index < base::size(kCorrectionResourceTable);
345          ++correction_index) {
346       if ((*it)->correction_type !=
347           kCorrectionResourceTable[correction_index].correction_type) {
348         continue;
349       }
350       std::unique_ptr<base::DictionaryValue> suggest(
351           new base::DictionaryValue());
352       suggest->SetString(
353           "summary",
354           l10n_util::GetStringUTF16(
355               kCorrectionResourceTable[correction_index].resource_id));
356       suggest->SetString("urlCorrection", (*it)->url_correction);
357       suggest->SetString(
358           "urlCorrectionForDisplay",
359           FormatURLForDisplay(GURL((*it)->url_correction), is_rtl));
360       suggest->SetString("originalUrlForDisplay", original_url_for_display);
361       suggest->SetInteger("trackingId", tracking_id);
362       suggest->SetInteger("type", static_cast<int>(correction_index));
363 
364       params->override_suggestions->Append(std::move(suggest));
365       LogCorrectionTypeShown(static_cast<int>(correction_index));
366       break;
367     }
368   }
369 
370   if (params->override_suggestions->empty() && !params->search_url.is_valid())
371     params.reset();
372   return params;
373 }
374 
375 // Tracks navigation correction service usage in UMA to enable more in depth
376 // analysis.
TrackClickUMA(std::string type_id)377 void TrackClickUMA(std::string type_id) {
378   // Web search suggestion isn't in |kCorrectionResourceTable| array.
379   if (type_id == "webSearchQuery") {
380     UMA_HISTOGRAM_ENUMERATION(
381         "Net.ErrorPageCounts.NavigationCorrectionLinksUsed",
382         kWebSearchQueryUMAId, kWebSearchQueryUMAId + 1);
383     return;
384   }
385 
386   size_t correction_index;
387   for (correction_index = 0;
388        correction_index < base::size(kCorrectionResourceTable);
389        ++correction_index) {
390     if (kCorrectionResourceTable[correction_index].correction_type == type_id) {
391       UMA_HISTOGRAM_ENUMERATION(
392           "Net.ErrorPageCounts.NavigationCorrectionLinksUsed",
393           static_cast<int>(correction_index), kWebSearchQueryUMAId + 1);
394       break;
395     }
396   }
397 }
398 
399 }  // namespace
400 
401 struct NetErrorHelperCore::ErrorPageInfo {
ErrorPageInfoNetErrorHelperCore::ErrorPageInfo402   ErrorPageInfo(error_page::Error error, bool was_failed_post)
403       : error(error),
404         was_failed_post(was_failed_post),
405         needs_dns_updates(false),
406         needs_load_navigation_corrections(false),
407         is_finished_loading(false),
408         auto_reload_triggered(false) {}
409 
410   // Information about the failed page load.
411   error_page::Error error;
412   bool was_failed_post;
413 
414   // Information about the status of the error page.
415 
416   // True if a page is a DNS error page and has not yet received a final DNS
417   // probe status.
418   bool needs_dns_updates;
419   bool dns_probe_complete = false;
420 
421   // True if a blank page was loaded, and navigation corrections need to be
422   // loaded to generate the real error page.
423   bool needs_load_navigation_corrections;
424 
425   // Navigation correction service paramers, which will be used in response to
426   // certain types of network errors.  They are all stored here in case they
427   // change over the course of displaying the error page.
428   std::unique_ptr<NetErrorHelperCore::NavigationCorrectionParams>
429       navigation_correction_params;
430 
431   std::unique_ptr<NavigationCorrectionResponse> navigation_correction_response;
432 
433   // All the navigation corrections that have been clicked, for tracking
434   // purposes.
435   std::set<int> clicked_corrections;
436 
437   // True if a page has completed loading, at which point it can receive
438   // updates.
439   bool is_finished_loading;
440 
441   // True if the auto-reload timer has fired and a reload is or has been in
442   // flight.
443   bool auto_reload_triggered;
444 
445   error_page::LocalizedError::PageState page_state;
446 };
447 
NavigationCorrectionParams()448 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {}
449 
450 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams(
451     const NavigationCorrectionParams& other) = default;
452 
~NavigationCorrectionParams()453 NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {}
454 
IsReloadableError(const NetErrorHelperCore::ErrorPageInfo & info)455 bool NetErrorHelperCore::IsReloadableError(
456     const NetErrorHelperCore::ErrorPageInfo& info) {
457   GURL url = info.error.url();
458   return info.error.domain() == error_page::Error::kNetErrorDomain &&
459          info.error.reason() != net::ERR_ABORTED &&
460          // For now, net::ERR_UNKNOWN_URL_SCHEME is only being displayed on
461          // Chrome for Android.
462          info.error.reason() != net::ERR_UNKNOWN_URL_SCHEME &&
463          // Do not trigger if the server rejects a client certificate.
464          // https://crbug.com/431387
465          !net::IsClientCertificateError(info.error.reason()) &&
466          // Some servers reject client certificates with a generic
467          // handshake_failure alert.
468          // https://crbug.com/431387
469          info.error.reason() != net::ERR_SSL_PROTOCOL_ERROR &&
470          // Do not trigger for blacklisted URLs.
471          // https://crbug.com/803839
472          info.error.reason() != net::ERR_BLOCKED_BY_ADMINISTRATOR &&
473          // Do not trigger for requests that were blocked by the browser itself.
474          info.error.reason() != net::ERR_BLOCKED_BY_CLIENT &&
475          !info.was_failed_post &&
476          // Do not trigger for this error code because it is used by Chrome
477          // while an auth prompt is being displayed.
478          info.error.reason() != net::ERR_INVALID_AUTH_CREDENTIALS &&
479          // Don't auto-reload non-http/https schemas.
480          // https://crbug.com/471713
481          url.SchemeIsHTTPOrHTTPS() &&
482          // Don't auto reload if the error was a secure DNS network error, since
483          // the reload may interfere with the captive portal probe state.
484          // TODO(crbug.com/1016164): Explore how to allow reloads for secure DNS
485          // network errors without interfering with the captive portal probe
486          // state.
487          !info.error.resolve_error_info().is_secure_network_error;
488 }
489 
NetErrorHelperCore(Delegate * delegate,bool auto_reload_enabled,bool is_visible)490 NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate,
491                                        bool auto_reload_enabled,
492                                        bool is_visible)
493     : delegate_(delegate),
494       last_probe_status_(error_page::DNS_PROBE_POSSIBLE),
495       can_show_network_diagnostics_dialog_(false),
496       auto_reload_enabled_(auto_reload_enabled),
497       auto_reload_timer_(new base::OneShotTimer()),
498       auto_reload_paused_(false),
499       auto_reload_in_flight_(false),
500       uncommitted_load_started_(false),
501       online_(content::RenderThread::Get()->IsOnline()),
502       visible_(is_visible),
503       auto_reload_count_(0),
504       navigation_from_button_(NO_BUTTON)
505 #if defined(OS_ANDROID)
506       ,
507       page_auto_fetcher_helper_(
508           std::make_unique<PageAutoFetcherHelper>(delegate->GetRenderFrame()))
509 #endif
510 {
511 }
512 
513 NetErrorHelperCore::~NetErrorHelperCore() = default;
514 
CancelPendingFetches()515 void NetErrorHelperCore::CancelPendingFetches() {
516   // Cancel loading the alternate error page, and prevent any pending error page
517   // load from starting a new error page load.  Swapping in the error page when
518   // it's finished loading could abort the navigation, otherwise.
519   if (committed_error_page_info_)
520     committed_error_page_info_->needs_load_navigation_corrections = false;
521   if (pending_error_page_info_)
522     pending_error_page_info_->needs_load_navigation_corrections = false;
523   delegate_->CancelFetchNavigationCorrections();
524   auto_reload_timer_->Stop();
525   auto_reload_paused_ = false;
526 }
527 
OnStop()528 void NetErrorHelperCore::OnStop() {
529   CancelPendingFetches();
530   uncommitted_load_started_ = false;
531   auto_reload_count_ = 0;
532   auto_reload_in_flight_ = false;
533 }
534 
OnWasShown()535 void NetErrorHelperCore::OnWasShown() {
536   visible_ = true;
537   if (auto_reload_paused_)
538     MaybeStartAutoReloadTimer();
539 }
540 
OnWasHidden()541 void NetErrorHelperCore::OnWasHidden() {
542   visible_ = false;
543   PauseAutoReloadTimer();
544 }
545 
OnStartLoad(FrameType frame_type,PageType page_type)546 void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
547   if (frame_type != MAIN_FRAME)
548     return;
549 
550   uncommitted_load_started_ = true;
551 
552   // If there's no pending error page information associated with the page load,
553   // or the new page is not an error page, then reset pending error page state.
554   if (!pending_error_page_info_ || page_type != ERROR_PAGE) {
555     CancelPendingFetches();
556   } else {
557     // Halt auto-reload if it's currently scheduled. OnFinishLoad will trigger
558     // auto-reload if appropriate.
559     PauseAutoReloadTimer();
560   }
561 }
562 
OnCommitLoad(FrameType frame_type,const GURL & url)563 void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) {
564   if (frame_type != MAIN_FRAME)
565     return;
566 
567   // If a page is committing, either it's an error page and autoreload will be
568   // started again below, or it's a success page and we need to clear autoreload
569   // state.
570   auto_reload_in_flight_ = false;
571 
572   // uncommitted_load_started_ could already be false, since RenderFrameImpl
573   // calls OnCommitLoad once for each in-page navigation (like a fragment
574   // change) with no corresponding OnStartLoad.
575   uncommitted_load_started_ = false;
576 
577 #if defined(OS_ANDROID)
578   // Don't need this state. It will be refreshed if another error page is
579   // loaded.
580   available_content_helper_.Reset();
581   page_auto_fetcher_helper_->OnCommitLoad();
582 #endif
583 
584   // Track if an error occurred due to a page button press.
585   // This isn't perfect; if (for instance), the server is slow responding
586   // to a request generated from the page reload button, and the user hits
587   // the browser reload button, this code will still believe the
588   // result is from the page reload button.
589   if (committed_error_page_info_ && pending_error_page_info_ &&
590       navigation_from_button_ != NO_BUTTON &&
591       committed_error_page_info_->error.url() ==
592           pending_error_page_info_->error.url()) {
593     DCHECK(navigation_from_button_ == RELOAD_BUTTON);
594     RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR);
595   }
596   navigation_from_button_ = NO_BUTTON;
597 
598   committed_error_page_info_ = std::move(pending_error_page_info_);
599 }
600 
ErrorPageLoadedWithFinalErrorCode()601 void NetErrorHelperCore::ErrorPageLoadedWithFinalErrorCode() {
602   ErrorPageInfo* page_info = committed_error_page_info_.get();
603   DCHECK(page_info);
604   error_page::Error updated_error = GetUpdatedError(*page_info);
605 
606   if (page_info->page_state.is_offline_error)
607     RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_ERROR_SHOWN);
608 
609 #if defined(OS_ANDROID)
610   // The fetch functions shouldn't be triggered multiple times per page load.
611   if (page_info->page_state.offline_content_feature_enabled) {
612     available_content_helper_.FetchAvailableContent(base::BindOnce(
613         &Delegate::OfflineContentAvailable, base::Unretained(delegate_)));
614   }
615 
616   // |TrySchedule()| shouldn't be called more than once per page.
617   if (page_info->page_state.auto_fetch_allowed) {
618     page_auto_fetcher_helper_->TrySchedule(
619         false, base::BindOnce(&Delegate::SetAutoFetchState,
620                               base::Unretained(delegate_)));
621   }
622 #endif  // defined(OS_ANDROID)
623 
624   if (page_info->page_state.download_button_shown)
625     RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_SHOWN);
626 
627   if (page_info->page_state.reload_button_shown)
628     RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN);
629 
630   delegate_->SetIsShowingDownloadButton(
631       page_info->page_state.download_button_shown);
632 }
633 
OnFinishLoad(FrameType frame_type)634 void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
635   if (frame_type != MAIN_FRAME)
636     return;
637 
638   if (!committed_error_page_info_) {
639     auto_reload_count_ = 0;
640     return;
641   }
642   committed_error_page_info_->is_finished_loading = true;
643 
644   RecordEvent(error_page::NETWORK_ERROR_PAGE_SHOWN);
645   if (committed_error_page_info_->page_state.show_cached_copy_button_shown) {
646     RecordEvent(error_page::NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_SHOWN);
647   }
648 
649   delegate_->SetIsShowingDownloadButton(
650       committed_error_page_info_->page_state.download_button_shown);
651 
652   delegate_->EnablePageHelperFunctions();
653 
654   if (committed_error_page_info_->needs_load_navigation_corrections) {
655     // If there is another pending error page load, |fix_url| should have been
656     // cleared.
657     DCHECK(!pending_error_page_info_);
658     DCHECK(!committed_error_page_info_->needs_dns_updates);
659     delegate_->FetchNavigationCorrections(
660         committed_error_page_info_->navigation_correction_params->url,
661         CreateFixUrlRequestBody(
662             committed_error_page_info_->error,
663             *committed_error_page_info_->navigation_correction_params));
664   } else if (auto_reload_enabled_ &&
665              IsReloadableError(*committed_error_page_info_)) {
666     MaybeStartAutoReloadTimer();
667   }
668 
669   DVLOG(1) << "Error page finished loading; sending saved status.";
670   if (committed_error_page_info_->needs_dns_updates) {
671     if (last_probe_status_ != error_page::DNS_PROBE_POSSIBLE)
672       UpdateErrorPage();
673   } else {
674     ErrorPageLoadedWithFinalErrorCode();
675   }
676 }
677 
PrepareErrorPage(FrameType frame_type,const error_page::Error & error,bool is_failed_post,std::string * error_html)678 void NetErrorHelperCore::PrepareErrorPage(FrameType frame_type,
679                                           const error_page::Error& error,
680                                           bool is_failed_post,
681                                           std::string* error_html) {
682   if (frame_type == MAIN_FRAME) {
683     // If navigation corrections were needed before, that should have been
684     // cancelled earlier by starting a new page load (Which has now failed).
685     DCHECK(!committed_error_page_info_ ||
686            !committed_error_page_info_->needs_load_navigation_corrections);
687 
688     pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
689     pending_error_page_info_->navigation_correction_params.reset(
690         new NavigationCorrectionParams(navigation_correction_params_));
691     PrepareErrorPageForMainFrame(pending_error_page_info_.get(), error_html);
692   } else {
693     if (error_html) {
694       delegate_->GenerateLocalizedErrorPage(
695           error, is_failed_post,
696           false /* No diagnostics dialogs allowed for subframes. */, nullptr,
697           error_html);
698     }
699   }
700 }
701 
OnNetErrorInfo(error_page::DnsProbeStatus status)702 void NetErrorHelperCore::OnNetErrorInfo(error_page::DnsProbeStatus status) {
703   DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, status);
704 
705   last_probe_status_ = status;
706 
707   if (!committed_error_page_info_ ||
708       !committed_error_page_info_->needs_dns_updates ||
709       !committed_error_page_info_->is_finished_loading) {
710     return;
711   }
712 
713   UpdateErrorPage();
714 }
715 
OnSetCanShowNetworkDiagnosticsDialog(bool can_show_network_diagnostics_dialog)716 void NetErrorHelperCore::OnSetCanShowNetworkDiagnosticsDialog(
717     bool can_show_network_diagnostics_dialog) {
718   can_show_network_diagnostics_dialog_ = can_show_network_diagnostics_dialog;
719 }
720 
OnSetNavigationCorrectionInfo(const GURL & navigation_correction_url,const std::string & language,const std::string & country_code,const std::string & api_key,const GURL & search_url)721 void NetErrorHelperCore::OnSetNavigationCorrectionInfo(
722     const GURL& navigation_correction_url,
723     const std::string& language,
724     const std::string& country_code,
725     const std::string& api_key,
726     const GURL& search_url) {
727   navigation_correction_params_.url = navigation_correction_url;
728   navigation_correction_params_.language = language;
729   navigation_correction_params_.country_code = country_code;
730   navigation_correction_params_.api_key = api_key;
731   navigation_correction_params_.search_url = search_url;
732 }
733 
OnEasterEggHighScoreReceived(int high_score)734 void NetErrorHelperCore::OnEasterEggHighScoreReceived(int high_score) {
735   if (!committed_error_page_info_ ||
736       !committed_error_page_info_->is_finished_loading) {
737     return;
738   }
739 
740   delegate_->InitializeErrorPageEasterEggHighScore(high_score);
741 }
742 
PrepareErrorPageForMainFrame(ErrorPageInfo * pending_error_page_info,std::string * error_html)743 void NetErrorHelperCore::PrepareErrorPageForMainFrame(
744     ErrorPageInfo* pending_error_page_info,
745     std::string* error_html) {
746   std::string error_param;
747   error_page::Error error = pending_error_page_info->error;
748 
749   if (pending_error_page_info->navigation_correction_params &&
750       pending_error_page_info->navigation_correction_params->url.is_valid() &&
751       ShouldUseFixUrlServiceForError(error, &error_param)) {
752     pending_error_page_info->needs_load_navigation_corrections = true;
753     return;
754   }
755 
756   if (IsNetDnsError(pending_error_page_info->error)) {
757     // The last probe status needs to be reset if this is a DNS error.  This
758     // means that if a DNS error page is committed but has not yet finished
759     // loading, a DNS probe status scheduled to be sent to it may be thrown
760     // out, but since the new error page should trigger a new DNS probe, it
761     // will just get the results for the next page load.
762     last_probe_status_ = error_page::DNS_PROBE_POSSIBLE;
763     pending_error_page_info->needs_dns_updates = true;
764     error = GetUpdatedError(*pending_error_page_info);
765   }
766   if (error_html) {
767     pending_error_page_info->page_state = delegate_->GenerateLocalizedErrorPage(
768         error, pending_error_page_info->was_failed_post,
769         can_show_network_diagnostics_dialog_, nullptr, error_html);
770   }
771 }
772 
UpdateErrorPage()773 void NetErrorHelperCore::UpdateErrorPage() {
774   DCHECK(committed_error_page_info_->needs_dns_updates);
775   DCHECK(committed_error_page_info_->is_finished_loading);
776   DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, last_probe_status_);
777 
778   UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
779                             last_probe_status_, error_page::DNS_PROBE_MAX);
780   // Every status other than error_page::DNS_PROBE_POSSIBLE and
781   // error_page::DNS_PROBE_STARTED is a final status code.  Once one is reached,
782   // the page does not need further updates.
783   if (last_probe_status_ != error_page::DNS_PROBE_STARTED) {
784     committed_error_page_info_->needs_dns_updates = false;
785     committed_error_page_info_->dns_probe_complete = true;
786   }
787 
788   error_page::LocalizedError::PageState new_state =
789       delegate_->UpdateErrorPage(GetUpdatedError(*committed_error_page_info_),
790                                  committed_error_page_info_->was_failed_post,
791                                  can_show_network_diagnostics_dialog_);
792 
793   // This button can't be changed by a DNS error update, so there's no code
794   // to update the related UMA in ErrorPageLoadedWithFinalErrorCode(). Instead,
795   // verify there's no change in this button's state.
796   DCHECK_EQ(
797       committed_error_page_info_->page_state.show_cached_copy_button_shown,
798       new_state.show_cached_copy_button_shown);
799 
800   committed_error_page_info_->page_state = std::move(new_state);
801   if (!committed_error_page_info_->needs_dns_updates)
802     ErrorPageLoadedWithFinalErrorCode();
803 }
804 
OnNavigationCorrectionsFetched(const std::string & corrections,bool is_rtl)805 void NetErrorHelperCore::OnNavigationCorrectionsFetched(
806     const std::string& corrections,
807     bool is_rtl) {
808   // Loading suggestions only starts when a blank error page finishes loading,
809   // and is cancelled with a new load.
810   DCHECK(!pending_error_page_info_);
811   DCHECK(committed_error_page_info_->is_finished_loading);
812   DCHECK(committed_error_page_info_->needs_load_navigation_corrections);
813   DCHECK(committed_error_page_info_->navigation_correction_params);
814 
815   pending_error_page_info_.reset(
816       new ErrorPageInfo(committed_error_page_info_->error,
817                         committed_error_page_info_->was_failed_post));
818   pending_error_page_info_->navigation_correction_response =
819       ParseNavigationCorrectionResponse(corrections);
820 
821   std::string error_html;
822   if (pending_error_page_info_->navigation_correction_response) {
823     // Copy navigation correction parameters used for the request, so tracking
824     // requests can still be sent if the configuration changes.
825     pending_error_page_info_->navigation_correction_params.reset(
826         new NavigationCorrectionParams(
827             *committed_error_page_info_->navigation_correction_params));
828     std::unique_ptr<error_page::ErrorPageParams> params = CreateErrorPageParams(
829         *pending_error_page_info_->navigation_correction_response,
830         pending_error_page_info_->error,
831         *pending_error_page_info_->navigation_correction_params, is_rtl);
832     pending_error_page_info_->page_state =
833         delegate_->GenerateLocalizedErrorPage(
834             pending_error_page_info_->error,
835             pending_error_page_info_->was_failed_post,
836             can_show_network_diagnostics_dialog_, std::move(params),
837             &error_html);
838   } else {
839     // Since |navigation_correction_params| in |pending_error_page_info_| is
840     // NULL, this won't trigger another attempt to load corrections.
841     PrepareErrorPageForMainFrame(pending_error_page_info_.get(), &error_html);
842   }
843 
844   // TODO(mmenke):  Once the new API is in place, look into replacing this
845   //                double page load by just updating the error page, like DNS
846   //                probes do.
847   delegate_->LoadErrorPage(error_html, pending_error_page_info_->error.url());
848 }
849 
GetUpdatedError(const ErrorPageInfo & error_info) const850 error_page::Error NetErrorHelperCore::GetUpdatedError(
851     const ErrorPageInfo& error_info) const {
852   // If a probe didn't run or wasn't conclusive, restore the original error.
853   const bool dns_probe_used =
854       error_info.needs_dns_updates || error_info.dns_probe_complete;
855   if (!dns_probe_used || last_probe_status_ == error_page::DNS_PROBE_NOT_RUN ||
856       last_probe_status_ == error_page::DNS_PROBE_FINISHED_INCONCLUSIVE) {
857     return error_info.error;
858   }
859 
860   return error_page::Error::DnsProbeError(
861       error_info.error.url(), last_probe_status_,
862       error_info.error.stale_copy_in_cache());
863 }
864 
Reload()865 void NetErrorHelperCore::Reload() {
866   if (!committed_error_page_info_)
867     return;
868   delegate_->ReloadFrame();
869 }
870 
MaybeStartAutoReloadTimer()871 bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
872   // Automation tools expect to be in control of reloads.
873   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
874           switches::kEnableAutomation)) {
875     return false;
876   }
877 
878   if (!committed_error_page_info_ ||
879       !committed_error_page_info_->is_finished_loading ||
880       pending_error_page_info_ || uncommitted_load_started_) {
881     return false;
882   }
883 
884   StartAutoReloadTimer();
885   return true;
886 }
887 
StartAutoReloadTimer()888 void NetErrorHelperCore::StartAutoReloadTimer() {
889   DCHECK(committed_error_page_info_);
890   DCHECK(IsReloadableError(*committed_error_page_info_));
891 
892   committed_error_page_info_->auto_reload_triggered = true;
893 
894   if (!online_ || !visible_) {
895     auto_reload_paused_ = true;
896     return;
897   }
898 
899   auto_reload_paused_ = false;
900   base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
901   auto_reload_timer_->Stop();
902   auto_reload_timer_->Start(
903       FROM_HERE, delay,
904       base::BindOnce(&NetErrorHelperCore::AutoReloadTimerFired,
905                      base::Unretained(this)));
906 }
907 
AutoReloadTimerFired()908 void NetErrorHelperCore::AutoReloadTimerFired() {
909   // AutoReloadTimerFired only runs if:
910   // 1. StartAutoReloadTimer was previously called, which requires that
911   //    committed_error_page_info_ is populated;
912   // 2. No other page load has started since (1), since OnStartLoad stops the
913   //    auto-reload timer.
914   DCHECK(committed_error_page_info_);
915 
916   auto_reload_count_++;
917   auto_reload_in_flight_ = true;
918   Reload();
919 }
920 
PauseAutoReloadTimer()921 void NetErrorHelperCore::PauseAutoReloadTimer() {
922   if (!auto_reload_timer_->IsRunning())
923     return;
924   DCHECK(committed_error_page_info_);
925   DCHECK(!auto_reload_paused_);
926   DCHECK(committed_error_page_info_->auto_reload_triggered);
927   auto_reload_timer_->Stop();
928   auto_reload_paused_ = true;
929 }
930 
NetworkStateChanged(bool online)931 void NetErrorHelperCore::NetworkStateChanged(bool online) {
932   bool was_online = online_;
933   online_ = online;
934   if (!was_online && online) {
935     // Transitioning offline -> online
936     if (auto_reload_paused_)
937       MaybeStartAutoReloadTimer();
938   } else if (was_online && !online) {
939     // Transitioning online -> offline
940     if (auto_reload_timer_->IsRunning())
941       auto_reload_count_ = 0;
942     PauseAutoReloadTimer();
943   }
944 }
945 
ShouldSuppressErrorPage(FrameType frame_type,const GURL & url)946 bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
947                                                  const GURL& url) {
948   // Don't suppress child frame errors.
949   if (frame_type != MAIN_FRAME)
950     return false;
951 
952   // If there's no auto reload attempt in flight, this error page didn't come
953   // from auto reload, so don't suppress it.
954   if (!auto_reload_in_flight_)
955     return false;
956 
957   uncommitted_load_started_ = false;
958   // This serves to terminate the auto-reload in flight attempt. If
959   // ShouldSuppressErrorPage is called, the auto-reload yielded an error, which
960   // means the request was already sent.
961   auto_reload_in_flight_ = false;
962   MaybeStartAutoReloadTimer();
963   return true;
964 }
965 
966 #if defined(OS_ANDROID)
SetPageAutoFetcherHelperForTesting(std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper)967 void NetErrorHelperCore::SetPageAutoFetcherHelperForTesting(
968     std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper) {
969   page_auto_fetcher_helper_ = std::move(page_auto_fetcher_helper);
970 }
971 #endif
972 
ExecuteButtonPress(Button button)973 void NetErrorHelperCore::ExecuteButtonPress(Button button) {
974   // If there's no committed error page, should not be invoked.
975   DCHECK(committed_error_page_info_);
976 
977   switch (button) {
978     case RELOAD_BUTTON:
979       RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED);
980       navigation_from_button_ = RELOAD_BUTTON;
981       Reload();
982       return;
983     case MORE_BUTTON:
984       // Visual effects on page are handled in Javascript code.
985       RecordEvent(error_page::NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED);
986       return;
987     case EASTER_EGG:
988       RecordEvent(error_page::NETWORK_ERROR_EASTER_EGG_ACTIVATED);
989       delegate_->RequestEasterEggHighScore();
990       return;
991     case SHOW_CACHED_COPY_BUTTON:
992       RecordEvent(error_page::NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_CLICKED);
993       return;
994     case DIAGNOSE_ERROR:
995       RecordEvent(error_page::NETWORK_ERROR_DIAGNOSE_BUTTON_CLICKED);
996       delegate_->DiagnoseError(committed_error_page_info_->error.url());
997       return;
998     case DOWNLOAD_BUTTON:
999       RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_CLICKED);
1000       delegate_->DownloadPageLater();
1001       return;
1002     case NO_BUTTON:
1003       NOTREACHED();
1004       return;
1005   }
1006 }
1007 
TrackClick(int tracking_id)1008 void NetErrorHelperCore::TrackClick(int tracking_id) {
1009   // It's technically possible for |navigation_correction_params| to be NULL but
1010   // for |navigation_correction_response| not to be NULL, if the paramters
1011   // changed between loading the original error page and loading the error page
1012   if (!committed_error_page_info_ ||
1013       !committed_error_page_info_->navigation_correction_response) {
1014     return;
1015   }
1016 
1017   NavigationCorrectionResponse* response =
1018       committed_error_page_info_->navigation_correction_response.get();
1019 
1020   // |tracking_id| is less than 0 when the error page was not generated by the
1021   // navigation correction service.  |tracking_id| should never be greater than
1022   // the array size, but best to be safe, since it contains data from a remote
1023   // site, though none of that data should make it into Javascript callbacks.
1024   if (tracking_id < 0 ||
1025       static_cast<size_t>(tracking_id) >= response->corrections.size()) {
1026     return;
1027   }
1028 
1029   // Only report a clicked link once.
1030   if (committed_error_page_info_->clicked_corrections.count(tracking_id))
1031     return;
1032 
1033   TrackClickUMA(response->corrections[tracking_id]->correction_type);
1034 
1035   committed_error_page_info_->clicked_corrections.insert(tracking_id);
1036   std::string request_body = CreateClickTrackingUrlRequestBody(
1037       committed_error_page_info_->error,
1038       *committed_error_page_info_->navigation_correction_params, *response,
1039       *response->corrections[tracking_id]);
1040   delegate_->SendTrackingRequest(
1041       committed_error_page_info_->navigation_correction_params->url,
1042       request_body);
1043 }
1044 
LaunchOfflineItem(const std::string & id,const std::string & name_space)1045 void NetErrorHelperCore::LaunchOfflineItem(const std::string& id,
1046                                            const std::string& name_space) {
1047 #if defined(OS_ANDROID)
1048   available_content_helper_.LaunchItem(id, name_space);
1049 #endif
1050 }
1051 
LaunchDownloadsPage()1052 void NetErrorHelperCore::LaunchDownloadsPage() {
1053 #if defined(OS_ANDROID)
1054   available_content_helper_.LaunchDownloadsPage();
1055 #endif
1056 }
1057 
SavePageForLater()1058 void NetErrorHelperCore::SavePageForLater() {
1059 #if defined(OS_ANDROID)
1060   page_auto_fetcher_helper_->TrySchedule(
1061       /*user_requested=*/true, base::BindOnce(&Delegate::SetAutoFetchState,
1062                                               base::Unretained(delegate_)));
1063 #endif
1064 }
1065 
CancelSavePage()1066 void NetErrorHelperCore::CancelSavePage() {
1067 #if defined(OS_ANDROID)
1068   page_auto_fetcher_helper_->CancelSchedule();
1069 #endif
1070 }
1071 
ListVisibilityChanged(bool is_visible)1072 void NetErrorHelperCore::ListVisibilityChanged(bool is_visible) {
1073 #if defined(OS_ANDROID)
1074   available_content_helper_.ListVisibilityChanged(is_visible);
1075 #endif
1076 }
1077