1// Copyright 2014 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 "components/nacl/renderer/nexe_load_manager.h"
6
7#include "base/command_line.h"
8#include "base/logging.h"
9#include "base/metrics/histogram.h"
10#include "base/strings/string_tokenizer.h"
11#include "base/strings/string_util.h"
12#include "components/nacl/common/nacl_host_messages.h"
13#include "components/nacl/common/nacl_types.h"
14#include "components/nacl/renderer/histogram.h"
15#include "components/nacl/renderer/manifest_service_channel.h"
16#include "components/nacl/renderer/pnacl_translation_resource_host.h"
17#include "components/nacl/renderer/progress_event.h"
18#include "components/nacl/renderer/sandbox_arch.h"
19#include "components/nacl/renderer/trusted_plugin_channel.h"
20#include "content/public/common/content_client.h"
21#include "content/public/common/content_switches.h"
22#include "content/public/common/sandbox_init.h"
23#include "content/public/renderer/pepper_plugin_instance.h"
24#include "content/public/renderer/render_thread.h"
25#include "content/public/renderer/render_view.h"
26#include "content/public/renderer/renderer_ppapi_host.h"
27#include "ppapi/c/pp_bool.h"
28#include "ppapi/c/private/pp_file_handle.h"
29#include "ppapi/shared_impl/ppapi_globals.h"
30#include "ppapi/shared_impl/ppapi_permissions.h"
31#include "ppapi/shared_impl/ppapi_preferences.h"
32#include "ppapi/shared_impl/scoped_pp_var.h"
33#include "ppapi/shared_impl/var.h"
34#include "ppapi/shared_impl/var_tracker.h"
35#include "ppapi/thunk/enter.h"
36#include "third_party/WebKit/public/web/WebDocument.h"
37#include "third_party/WebKit/public/web/WebElement.h"
38#include "third_party/WebKit/public/web/WebPluginContainer.h"
39#include "third_party/WebKit/public/web/WebView.h"
40#include "v8/include/v8.h"
41
42namespace {
43
44const char* const kTypeAttribute = "type";
45// The "src" attribute of the <embed> tag.  The value is expected to be either
46// a URL or URI pointing to the manifest file (which is expected to contain
47// JSON matching ISAs with .nexe URLs).
48const char* const kSrcManifestAttribute = "src";
49// The "nacl" attribute of the <embed> tag.  We use the value of this attribute
50// to find the manifest file when NaCl is registered as a plug-in for another
51// MIME type because the "src" attribute is used to supply us with the resource
52// of that MIME type that we're supposed to display.
53const char* const kNaClManifestAttribute = "nacl";
54// Define an argument name to enable 'dev' interfaces. To make sure it doesn't
55// collide with any user-defined HTML attribute, make the first character '@'.
56const char* const kDevAttribute = "@dev";
57
58const char* const kNaClMIMEType = "application/x-nacl";
59const char* const kPNaClMIMEType = "application/x-pnacl";
60
61static int GetRoutingID(PP_Instance instance) {
62  // Check that we are on the main renderer thread.
63  DCHECK(content::RenderThread::Get());
64  content::RendererPpapiHost *host =
65      content::RendererPpapiHost::GetForPPInstance(instance);
66  if (!host)
67    return 0;
68  return host->GetRoutingIDForWidget(instance);
69}
70
71std::string LookupAttribute(const std::map<std::string, std::string>& args,
72                            const std::string& key) {
73  std::map<std::string, std::string>::const_iterator it = args.find(key);
74  if (it != args.end())
75    return it->second;
76  return std::string();
77}
78
79}  // namespace
80
81namespace nacl {
82
83NexeLoadManager::NexeLoadManager(
84    PP_Instance pp_instance)
85    : pp_instance_(pp_instance),
86      nacl_ready_state_(PP_NACL_READY_STATE_UNSENT),
87      nexe_error_reported_(false),
88      is_installed_(false),
89      exit_status_(-1),
90      nexe_size_(0),
91      plugin_instance_(content::PepperPluginInstance::Get(pp_instance)),
92      weak_factory_(this) {
93  SetLastError("");
94  HistogramEnumerateOsArch(GetSandboxArch());
95  if (plugin_instance_) {
96    plugin_base_url_ =
97        plugin_instance_->GetContainer()->element().document().url();
98  }
99}
100
101NexeLoadManager::~NexeLoadManager() {
102  if (!nexe_error_reported_) {
103    base::TimeDelta uptime = base::Time::Now() - ready_time_;
104    HistogramTimeLarge("NaCl.ModuleUptime.Normal", uptime.InMilliseconds());
105  }
106}
107
108void NexeLoadManager::NexeFileDidOpen(int32_t pp_error,
109                                      const base::File& file,
110                                      int32_t http_status,
111                                      int64_t nexe_bytes_read,
112                                      const std::string& url,
113                                      base::TimeDelta time_since_open) {
114  // Check that we are on the main renderer thread.
115  DCHECK(content::RenderThread::Get());
116  VLOG(1) << "Plugin::NexeFileDidOpen (pp_error=" << pp_error << ")";
117  HistogramHTTPStatusCode(
118      is_installed_ ? "NaCl.HttpStatusCodeClass.Nexe.InstalledApp" :
119                      "NaCl.HttpStatusCodeClass.Nexe.NotInstalledApp",
120      http_status);
121
122  if (pp_error != PP_OK || !file.IsValid()) {
123    if (pp_error == PP_ERROR_ABORTED) {
124      ReportLoadAbort();
125    } else if (pp_error == PP_ERROR_NOACCESS) {
126      ReportLoadError(PP_NACL_ERROR_NEXE_NOACCESS_URL,
127                      "access to nexe url was denied.");
128    } else {
129      ReportLoadError(PP_NACL_ERROR_NEXE_LOAD_URL,
130                      "could not load nexe url.");
131    }
132  } else if (nexe_bytes_read == -1) {
133    ReportLoadError(PP_NACL_ERROR_NEXE_STAT, "could not stat nexe file.");
134  } else {
135    // TODO(dmichael): Can we avoid stashing away so much state?
136    nexe_size_ = nexe_bytes_read;
137    HistogramSizeKB("NaCl.Perf.Size.Nexe",
138                    static_cast<int32_t>(nexe_size_ / 1024));
139    HistogramStartupTimeMedium(
140        "NaCl.Perf.StartupTime.NexeDownload", time_since_open, nexe_size_);
141
142    // Inform JavaScript that we successfully downloaded the nacl module.
143    ProgressEvent progress_event(PP_NACL_EVENT_PROGRESS, url, true, nexe_size_,
144                                 nexe_size_);
145    DispatchProgressEvent(pp_instance_, progress_event);
146    load_start_ = base::Time::Now();
147  }
148}
149
150void NexeLoadManager::ReportLoadSuccess(const std::string& url,
151                                        uint64_t loaded_bytes,
152                                        uint64_t total_bytes) {
153  ready_time_ = base::Time::Now();
154  if (!IsPNaCl()) {
155    base::TimeDelta load_module_time = ready_time_ - load_start_;
156    HistogramStartupTimeSmall(
157        "NaCl.Perf.StartupTime.LoadModule", load_module_time, nexe_size_);
158    HistogramStartupTimeMedium(
159        "NaCl.Perf.StartupTime.Total", ready_time_ - init_time_, nexe_size_);
160  }
161
162  // Check that we are on the main renderer thread.
163  DCHECK(content::RenderThread::Get());
164  set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
165
166  // Inform JavaScript that loading was successful and is complete.
167  ProgressEvent load_event(PP_NACL_EVENT_LOAD, url, true, loaded_bytes,
168                           total_bytes);
169  DispatchProgressEvent(pp_instance_, load_event);
170
171  ProgressEvent loadend_event(PP_NACL_EVENT_LOADEND, url, true, loaded_bytes,
172                              total_bytes);
173  DispatchProgressEvent(pp_instance_, loadend_event);
174
175  // UMA
176  HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_SUCCESS, is_installed_);
177}
178
179void NexeLoadManager::ReportLoadError(PP_NaClError error,
180                                      const std::string& error_message) {
181  ReportLoadError(error, error_message, error_message);
182}
183
184void NexeLoadManager::ReportLoadError(PP_NaClError error,
185                                      const std::string& error_message,
186                                      const std::string& console_message) {
187  // Check that we are on the main renderer thread.
188  DCHECK(content::RenderThread::Get());
189
190  if (error == PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH) {
191    // A special case: the manifest may otherwise be valid but is missing
192    // a program/file compatible with the user's sandbox.
193    IPC::Sender* sender = content::RenderThread::Get();
194    sender->Send(
195        new NaClHostMsg_MissingArchError(GetRoutingID(pp_instance_)));
196  }
197  set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
198  nexe_error_reported_ = true;
199
200  // We must set all properties before calling DispatchEvent so that when an
201  // event handler runs, the properties reflect the current load state.
202  std::string error_string = std::string("NaCl module load failed: ") +
203      std::string(error_message);
204  SetLastError(error_string);
205
206  // Inform JavaScript that loading encountered an error and is complete.
207  DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ERROR));
208  DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND));
209
210  HistogramEnumerateLoadStatus(error, is_installed_);
211  LogToConsole(console_message);
212}
213
214void NexeLoadManager::ReportLoadAbort() {
215  // Check that we are on the main renderer thread.
216  DCHECK(content::RenderThread::Get());
217
218  // Set the readyState attribute to indicate we need to start over.
219  set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
220  nexe_error_reported_ = true;
221
222  // Report an error in lastError and on the JavaScript console.
223  std::string error_string("NaCl module load failed: user aborted");
224  SetLastError(error_string);
225
226  // Inform JavaScript that loading was aborted and is complete.
227  DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ABORT));
228  DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND));
229
230  HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_ABORTED, is_installed_);
231  LogToConsole(error_string);
232}
233
234void NexeLoadManager::NexeDidCrash(const char* crash_log) {
235  VLOG(1) << "Plugin::NexeDidCrash: crash event!";
236    // The NaCl module voluntarily exited.  However, this is still a
237    // crash from the point of view of Pepper, since PPAPI plugins are
238    // event handlers and should never exit.
239  VLOG_IF(1, exit_status_ != -1)
240      << "Plugin::NexeDidCrash: nexe exited with status " << exit_status_
241      << " so this is a \"controlled crash\".";
242  // If the crash occurs during load, we just want to report an error
243  // that fits into our load progress event grammar.  If the crash
244  // occurs after loaded/loadend, then we use ReportDeadNexe to send a
245  // "crash" event.
246  if (nexe_error_reported_) {
247    VLOG(1) << "Plugin::NexeDidCrash: error already reported; suppressing";
248  } else {
249    if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE) {
250      ReportDeadNexe();
251    } else {
252      ReportLoadError(PP_NACL_ERROR_START_PROXY_CRASH,
253                      "Nexe crashed during startup");
254    }
255  }
256  // In all cases, try to grab the crash log.  The first error
257  // reported may have come from the start_module RPC reply indicating
258  // a validation error or something similar, which wouldn't grab the
259  // crash log.  In the event that this is called twice, the second
260  // invocation will just be a no-op, since the entire crash log will
261  // have been received and we'll just get an EOF indication.
262  CopyCrashLogToJsConsole(crash_log);
263}
264
265void NexeLoadManager::set_trusted_plugin_channel(
266    scoped_ptr<TrustedPluginChannel> channel) {
267  trusted_plugin_channel_ = channel.Pass();
268}
269
270void NexeLoadManager::set_manifest_service_channel(
271    scoped_ptr<ManifestServiceChannel> channel) {
272  manifest_service_channel_ = channel.Pass();
273}
274
275PP_NaClReadyState NexeLoadManager::nacl_ready_state() {
276  return nacl_ready_state_;
277}
278
279void NexeLoadManager::set_nacl_ready_state(PP_NaClReadyState ready_state) {
280  nacl_ready_state_ = ready_state;
281  ppapi::ScopedPPVar ready_state_name(
282      ppapi::ScopedPPVar::PassRef(),
283      ppapi::StringVar::StringToPPVar("readyState"));
284  SetReadOnlyProperty(ready_state_name.get(), PP_MakeInt32(ready_state));
285}
286
287void NexeLoadManager::SetLastError(const std::string& error) {
288  ppapi::ScopedPPVar error_name_var(
289      ppapi::ScopedPPVar::PassRef(),
290      ppapi::StringVar::StringToPPVar("lastError"));
291  ppapi::ScopedPPVar error_var(
292      ppapi::ScopedPPVar::PassRef(),
293      ppapi::StringVar::StringToPPVar(error));
294  SetReadOnlyProperty(error_name_var.get(), error_var.get());
295}
296
297void NexeLoadManager::SetReadOnlyProperty(PP_Var key, PP_Var value) {
298  plugin_instance_->SetEmbedProperty(key, value);
299}
300
301void NexeLoadManager::LogToConsole(const std::string& message) {
302  ppapi::PpapiGlobals::Get()->LogWithSource(
303      pp_instance_, PP_LOGLEVEL_LOG, std::string("NativeClient"), message);
304}
305
306void NexeLoadManager::set_exit_status(int exit_status) {
307  exit_status_ = exit_status;
308  ppapi::ScopedPPVar exit_status_name_var(
309      ppapi::ScopedPPVar::PassRef(),
310      ppapi::StringVar::StringToPPVar("exitStatus"));
311  SetReadOnlyProperty(exit_status_name_var.get(), PP_MakeInt32(exit_status));
312}
313
314void NexeLoadManager::InitializePlugin(
315    uint32_t argc, const char* argn[], const char* argv[]) {
316  init_time_ = base::Time::Now();
317
318  for (size_t i = 0; i < argc; ++i) {
319    std::string name(argn[i]);
320    std::string value(argv[i]);
321    args_[name] = value;
322  }
323
324  // Store mime_type_ at initialization time since we make it lowercase.
325  mime_type_ = StringToLowerASCII(LookupAttribute(args_, kTypeAttribute));
326}
327
328void NexeLoadManager::ReportStartupOverhead() const {
329  base::TimeDelta overhead = base::Time::Now() - init_time_;
330  HistogramStartupTimeMedium(
331      "NaCl.Perf.StartupTime.NaClOverhead", overhead, nexe_size_);
332}
333
334bool NexeLoadManager::RequestNaClManifest(const std::string& url) {
335  if (plugin_base_url_.is_valid()) {
336    const GURL& resolved_url = plugin_base_url_.Resolve(url);
337    if (resolved_url.is_valid()) {
338      manifest_base_url_ = resolved_url;
339      is_installed_ = manifest_base_url_.SchemeIs("chrome-extension");
340      HistogramEnumerateManifestIsDataURI(
341          manifest_base_url_.SchemeIs("data"));
342      set_nacl_ready_state(PP_NACL_READY_STATE_OPENED);
343      DispatchProgressEvent(pp_instance_,
344                            ProgressEvent(PP_NACL_EVENT_LOADSTART));
345      return true;
346    }
347  }
348  ReportLoadError(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
349                  std::string("could not resolve URL \"") + url +
350                  "\" relative to \"" +
351                  plugin_base_url_.possibly_invalid_spec() + "\".");
352  return false;
353}
354
355void NexeLoadManager::ProcessNaClManifest(const std::string& program_url) {
356  GURL gurl(program_url);
357  DCHECK(gurl.is_valid());
358  if (gurl.is_valid())
359    is_installed_ = gurl.SchemeIs("chrome-extension");
360  set_nacl_ready_state(PP_NACL_READY_STATE_LOADING);
361  DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_PROGRESS));
362}
363
364std::string NexeLoadManager::GetManifestURLArgument() const {
365  std::string manifest_url;
366
367  // If the MIME type is foreign, then this NEXE is being used as a content
368  // type handler rather than directly by an HTML document.
369  bool nexe_is_content_handler =
370      !mime_type_.empty() &&
371      mime_type_ != kNaClMIMEType &&
372      mime_type_ != kPNaClMIMEType;
373
374  if (nexe_is_content_handler) {
375    // For content handlers 'src' will be the URL for the content
376    // and 'nacl' will be the URL for the manifest.
377    manifest_url = LookupAttribute(args_, kNaClManifestAttribute);
378  } else {
379    manifest_url = LookupAttribute(args_, kSrcManifestAttribute);
380  }
381
382  if (manifest_url.empty()) {
383    VLOG(1) << "WARNING: no 'src' property, so no manifest loaded.";
384    if (args_.find(kNaClManifestAttribute) != args_.end())
385      VLOG(1) << "WARNING: 'nacl' property is incorrect. Use 'src'.";
386  }
387  return manifest_url;
388}
389
390bool NexeLoadManager::IsPNaCl() const {
391  return mime_type_ == kPNaClMIMEType;
392}
393
394bool NexeLoadManager::DevInterfacesEnabled() const {
395  // Look for the developer attribute; if it's present, enable 'dev'
396  // interfaces.
397  return args_.find(kDevAttribute) != args_.end();
398}
399
400void NexeLoadManager::ReportDeadNexe() {
401  if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE &&  // After loadEnd
402      !nexe_error_reported_) {
403    // Crashes will be more likely near startup, so use a medium histogram
404    // instead of a large one.
405    base::TimeDelta uptime = base::Time::Now() - ready_time_;
406    HistogramTimeMedium("NaCl.ModuleUptime.Crash", uptime.InMilliseconds());
407
408    std::string message("NaCl module crashed");
409    SetLastError(message);
410    LogToConsole(message);
411
412    DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_CRASH));
413    nexe_error_reported_ = true;
414  }
415  // else ReportLoadError() and ReportLoadAbort() will be used by loading code
416  // to provide error handling.
417}
418
419void NexeLoadManager::CopyCrashLogToJsConsole(const std::string& crash_log) {
420  base::StringTokenizer t(crash_log, "\n");
421  while (t.GetNext())
422    LogToConsole(t.token());
423}
424
425}  // namespace nacl
426