1// Copyright (c) 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.h"
6
7#include <string>
8
9#include "base/json/json_writer.h"
10#include "base/metrics/histogram.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/values.h"
13#include "chrome/common/localized_error.h"
14#include "chrome/common/net/net_error_info.h"
15#include "chrome/common/render_messages.h"
16#include "content/public/common/content_client.h"
17#include "content/public/common/url_constants.h"
18#include "content/public/renderer/content_renderer_client.h"
19#include "content/public/renderer/render_thread.h"
20#include "content/public/renderer/render_view.h"
21#include "ipc/ipc_message.h"
22#include "ipc/ipc_message_macros.h"
23#include "net/base/net_errors.h"
24#include "third_party/WebKit/public/platform/WebURL.h"
25#include "third_party/WebKit/public/platform/WebURLRequest.h"
26#include "third_party/WebKit/public/web/WebDataSource.h"
27#include "third_party/WebKit/public/web/WebFrame.h"
28#include "url/gurl.h"
29
30using base::JSONWriter;
31using chrome_common_net::DnsProbeStatus;
32using chrome_common_net::DnsProbeStatusIsFinished;
33using chrome_common_net::DnsProbeStatusToString;
34using content::RenderThread;
35using content::RenderView;
36using content::RenderViewObserver;
37using content::kUnreachableWebDataURL;
38
39namespace {
40
41bool IsLoadingErrorPage(blink::WebFrame* frame) {
42  GURL url = frame->provisionalDataSource()->request().url();
43  if (!url.is_valid())
44    return false;
45  return url.spec() == kUnreachableWebDataURL;
46}
47
48bool IsMainFrame(const blink::WebFrame* frame) {
49  return !frame->parent();
50}
51
52// Returns whether |net_error| is a DNS-related error (and therefore whether
53// the tab helper should start a DNS probe after receiving it.)
54bool IsDnsError(const blink::WebURLError& error) {
55  return std::string(error.domain.utf8()) == net::kErrorDomain &&
56         (error.reason == net::ERR_NAME_NOT_RESOLVED ||
57          error.reason == net::ERR_NAME_RESOLUTION_FAILED);
58}
59
60}  // namespace
61
62NetErrorHelper::NetErrorHelper(RenderView* render_view)
63    : RenderViewObserver(render_view),
64      last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
65      last_start_was_error_page_(false),
66      last_fail_was_dns_error_(false),
67      forwarding_probe_results_(false),
68      is_failed_post_(false) {
69}
70
71NetErrorHelper::~NetErrorHelper() {
72}
73
74void NetErrorHelper::DidStartProvisionalLoad(blink::WebFrame* frame) {
75  OnStartLoad(IsMainFrame(frame), IsLoadingErrorPage(frame));
76}
77
78void NetErrorHelper::DidFailProvisionalLoad(blink::WebFrame* frame,
79                                            const blink::WebURLError& error) {
80  const bool main_frame = IsMainFrame(frame);
81  const bool dns_error = IsDnsError(error);
82
83  OnFailLoad(main_frame, dns_error);
84
85  if (main_frame && dns_error) {
86    last_error_ = error;
87
88    blink::WebDataSource* data_source = frame->provisionalDataSource();
89    const blink::WebURLRequest& failed_request = data_source->request();
90    is_failed_post_ = EqualsASCII(failed_request.httpMethod(), "POST");
91  }
92}
93
94void NetErrorHelper::DidCommitProvisionalLoad(blink::WebFrame* frame,
95                                              bool is_new_navigation) {
96  OnCommitLoad(IsMainFrame(frame));
97}
98
99void NetErrorHelper::DidFinishLoad(blink::WebFrame* frame) {
100  OnFinishLoad(IsMainFrame(frame));
101}
102
103void NetErrorHelper::OnStartLoad(bool is_main_frame, bool is_error_page) {
104  DVLOG(1) << "OnStartLoad(is_main_frame=" << is_main_frame
105           << ", is_error_page=" << is_error_page << ")";
106  if (!is_main_frame)
107    return;
108
109  last_start_was_error_page_ = is_error_page;
110}
111
112void NetErrorHelper::OnFailLoad(bool is_main_frame, bool is_dns_error) {
113  DVLOG(1) << "OnFailLoad(is_main_frame=" << is_main_frame
114           << ", is_dns_error=" << is_dns_error << ")";
115
116  if (!is_main_frame)
117    return;
118
119  last_fail_was_dns_error_ = is_dns_error;
120
121  if (is_dns_error) {
122    last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
123    // If the helper was forwarding probe results and another DNS error has
124    // occurred, stop forwarding probe results until the corresponding (new)
125    // error page loads.
126    forwarding_probe_results_ = false;
127  }
128}
129
130void NetErrorHelper::OnCommitLoad(bool is_main_frame) {
131  DVLOG(1) << "OnCommitLoad(is_main_frame=" << is_main_frame << ")";
132
133  if (!is_main_frame)
134    return;
135
136  // Stop forwarding results.  If the page is a DNS error page, forwarding
137  // will resume once the page is loaded; if not, it should stay stopped until
138  // the next DNS error page.
139  forwarding_probe_results_ = false;
140}
141
142void NetErrorHelper::OnFinishLoad(bool is_main_frame) {
143  DVLOG(1) << "OnFinishLoad(is_main_frame=" << is_main_frame << ")";
144
145  if (!is_main_frame)
146    return;
147
148  // If a DNS error page just finished loading, start forwarding probe results
149  // to it.
150  forwarding_probe_results_ =
151      last_fail_was_dns_error_ && last_start_was_error_page_;
152
153  if (forwarding_probe_results_ &&
154      last_probe_status_ != chrome_common_net::DNS_PROBE_POSSIBLE) {
155    DVLOG(1) << "Error page finished loading; sending saved status.";
156    UpdateErrorPage();
157  }
158}
159
160bool NetErrorHelper::OnMessageReceived(const IPC::Message& message) {
161  bool handled = true;
162
163  IPC_BEGIN_MESSAGE_MAP(NetErrorHelper, message)
164    IPC_MESSAGE_HANDLER(ChromeViewMsg_NetErrorInfo, OnNetErrorInfo)
165    IPC_MESSAGE_UNHANDLED(handled = false)
166  IPC_END_MESSAGE_MAP()
167
168  return handled;
169}
170
171// static
172bool NetErrorHelper::GetErrorStringsForDnsProbe(
173    blink::WebFrame* frame,
174    const blink::WebURLError& error,
175    bool is_failed_post,
176    const std::string& locale,
177    const std::string& accept_languages,
178    base::DictionaryValue* error_strings) {
179  if (!IsMainFrame(frame))
180    return false;
181
182  if (!IsDnsError(error))
183    return false;
184
185  // Get the strings for a fake "DNS probe possible" error.
186  LocalizedError::GetStrings(
187      chrome_common_net::DNS_PROBE_POSSIBLE,
188      chrome_common_net::kDnsProbeErrorDomain,
189      error.unreachableURL,
190      is_failed_post, locale, accept_languages, error_strings);
191  return true;
192}
193
194void NetErrorHelper::OnNetErrorInfo(int status_num) {
195  DCHECK(status_num >= 0 && status_num < chrome_common_net::DNS_PROBE_MAX);
196
197  DVLOG(1) << "Received status " << DnsProbeStatusToString(status_num);
198
199  DnsProbeStatus status = static_cast<DnsProbeStatus>(status_num);
200  DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status);
201
202  if (!(last_fail_was_dns_error_ || forwarding_probe_results_)) {
203    DVLOG(1) << "Ignoring NetErrorInfo: no DNS error";
204    return;
205  }
206
207  last_probe_status_ = status;
208
209  if (forwarding_probe_results_)
210    UpdateErrorPage();
211}
212
213void NetErrorHelper::UpdateErrorPage() {
214  DCHECK(forwarding_probe_results_);
215
216  blink::WebURLError error = GetUpdatedError();
217  base::DictionaryValue error_strings;
218  LocalizedError::GetStrings(error.reason,
219                             error.domain.utf8(),
220                             error.unreachableURL,
221                             is_failed_post_,
222                             RenderThread::Get()->GetLocale(),
223                             render_view()->GetAcceptLanguages(),
224                             &error_strings);
225
226  std::string json;
227  JSONWriter::Write(&error_strings, &json);
228
229  std::string js = "if (window.updateForDnsProbe) "
230                   "updateForDnsProbe(" + json + ");";
231  base::string16 js16;
232  if (!UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
233    NOTREACHED();
234    return;
235  }
236
237  DVLOG(1) << "Updating error page with status "
238           << chrome_common_net::DnsProbeStatusToString(last_probe_status_);
239  DVLOG(2) << "New strings: " << js;
240
241  base::string16 frame_xpath;
242  render_view()->EvaluateScript(frame_xpath, js16, 0, false);
243
244  UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
245                            last_probe_status_,
246                            chrome_common_net::DNS_PROBE_MAX);
247}
248
249blink::WebURLError NetErrorHelper::GetUpdatedError() const {
250  // If a probe didn't run or wasn't conclusive, restore the original error.
251  if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN ||
252      last_probe_status_ ==
253          chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) {
254    return last_error_;
255  }
256
257  blink::WebURLError error;
258  error.domain = blink::WebString::fromUTF8(
259      chrome_common_net::kDnsProbeErrorDomain);
260  error.reason = last_probe_status_;
261  error.unreachableURL = last_error_.unreachableURL;
262
263  return error;
264}
265