1 // Copyright 2017 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 "extensions/renderer/runtime_hooks_delegate.h"
6 
7 #include "base/containers/span.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string_piece.h"
10 #include "base/strings/stringprintf.h"
11 #include "content/public/renderer/render_frame.h"
12 #include "content/public/renderer/v8_value_converter.h"
13 #include "extensions/common/api/messaging/message.h"
14 #include "extensions/common/constants.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/manifest.h"
17 #include "extensions/renderer/bindings/api_signature.h"
18 #include "extensions/renderer/bindings/js_runner.h"
19 #include "extensions/renderer/extension_frame_helper.h"
20 #include "extensions/renderer/get_script_context.h"
21 #include "extensions/renderer/message_target.h"
22 #include "extensions/renderer/messaging_util.h"
23 #include "extensions/renderer/native_renderer_messaging_service.h"
24 #include "extensions/renderer/script_context.h"
25 #include "gin/converter.h"
26 #include "third_party/blink/public/web/web_local_frame.h"
27 
28 namespace extensions {
29 
30 namespace {
31 using RequestResult = APIBindingHooks::RequestResult;
32 
33 // Handler for the extensionId property on chrome.runtime.
GetExtensionId(v8::Local<v8::Name> property_name,const v8::PropertyCallbackInfo<v8::Value> & info)34 void GetExtensionId(v8::Local<v8::Name> property_name,
35                     const v8::PropertyCallbackInfo<v8::Value>& info) {
36   v8::Isolate* isolate = info.GetIsolate();
37   v8::HandleScope handle_scope(isolate);
38   v8::Local<v8::Context> context = info.Holder()->CreationContext();
39 
40   ScriptContext* script_context = GetScriptContextFromV8Context(context);
41   // This could potentially be invoked after the script context is removed
42   // (unlike the handler calls, which should only be invoked for valid
43   // contexts).
44   if (script_context && script_context->extension()) {
45     info.GetReturnValue().Set(
46         gin::StringToSymbol(isolate, script_context->extension()->id()));
47   }
48 }
49 
50 constexpr char kGetManifest[] = "runtime.getManifest";
51 constexpr char kGetURL[] = "runtime.getURL";
52 constexpr char kConnect[] = "runtime.connect";
53 constexpr char kConnectNative[] = "runtime.connectNative";
54 constexpr char kSendMessage[] = "runtime.sendMessage";
55 constexpr char kSendNativeMessage[] = "runtime.sendNativeMessage";
56 constexpr char kGetBackgroundPage[] = "runtime.getBackgroundPage";
57 constexpr char kGetPackageDirectoryEntry[] = "runtime.getPackageDirectoryEntry";
58 
59 // The custom callback supplied to runtime.getBackgroundPage to find and return
60 // the background page to the original callback. The original callback is
61 // curried in through the Data.
GetBackgroundPageCallback(const v8::FunctionCallbackInfo<v8::Value> & info)62 void GetBackgroundPageCallback(
63     const v8::FunctionCallbackInfo<v8::Value>& info) {
64   v8::Isolate* isolate = info.GetIsolate();
65   v8::HandleScope handle_scope(isolate);
66   v8::Local<v8::Context> context = info.Holder()->CreationContext();
67 
68   DCHECK(!info.Data().IsEmpty());
69   if (info.Data()->IsNull())
70     return;
71 
72   // The ScriptContext should always be valid, because otherwise the
73   // getBackgroundPage() request should have been invalidated (and this should
74   // never run).
75   ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
76 
77   v8::Local<v8::Value> background_page =
78       ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
79           isolate, script_context->extension()->id());
80   v8::Local<v8::Value> args[] = {background_page};
81   script_context->SafeCallFunction(info.Data().As<v8::Function>(),
82                                    base::size(args), args);
83 }
84 
85 }  // namespace
86 
RuntimeHooksDelegate(NativeRendererMessagingService * messaging_service)87 RuntimeHooksDelegate::RuntimeHooksDelegate(
88     NativeRendererMessagingService* messaging_service)
89     : messaging_service_(messaging_service) {}
~RuntimeHooksDelegate()90 RuntimeHooksDelegate::~RuntimeHooksDelegate() {}
91 
92 // static
GetURL(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)93 RequestResult RuntimeHooksDelegate::GetURL(
94     ScriptContext* script_context,
95     const std::vector<v8::Local<v8::Value>>& arguments) {
96   DCHECK_EQ(1u, arguments.size());
97   DCHECK(arguments[0]->IsString());
98   DCHECK(script_context->extension());
99 
100   v8::Isolate* isolate = script_context->isolate();
101   std::string path = gin::V8ToString(isolate, arguments[0]);
102 
103   RequestResult result(RequestResult::HANDLED);
104   std::string url = base::StringPrintf(
105       "chrome-extension://%s%s%s", script_context->extension()->id().c_str(),
106       !path.empty() && path[0] == '/' ? "" : "/", path.c_str());
107   // GURL considers any possible path valid. Since the argument is only appended
108   // as part of the path, there should be no way this could conceivably fail.
109   DCHECK(GURL(url).is_valid());
110   result.return_value = gin::StringToV8(isolate, url);
111   return result;
112 }
113 
HandleRequest(const std::string & method_name,const APISignature * signature,v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * arguments,const APITypeReferenceMap & refs)114 RequestResult RuntimeHooksDelegate::HandleRequest(
115     const std::string& method_name,
116     const APISignature* signature,
117     v8::Local<v8::Context> context,
118     std::vector<v8::Local<v8::Value>>* arguments,
119     const APITypeReferenceMap& refs) {
120   using Handler = RequestResult (RuntimeHooksDelegate::*)(
121       ScriptContext*, const std::vector<v8::Local<v8::Value>>&);
122   static const struct {
123     Handler handler;
124     base::StringPiece method;
125   } kHandlers[] = {
126       {&RuntimeHooksDelegate::HandleSendMessage, kSendMessage},
127       {&RuntimeHooksDelegate::HandleConnect, kConnect},
128       {&RuntimeHooksDelegate::HandleGetURL, kGetURL},
129       {&RuntimeHooksDelegate::HandleGetManifest, kGetManifest},
130       {&RuntimeHooksDelegate::HandleConnectNative, kConnectNative},
131       {&RuntimeHooksDelegate::HandleSendNativeMessage, kSendNativeMessage},
132       {&RuntimeHooksDelegate::HandleGetBackgroundPage, kGetBackgroundPage},
133       {&RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback,
134        kGetPackageDirectoryEntry},
135   };
136 
137   ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
138 
139   Handler handler = nullptr;
140   for (const auto& handler_entry : kHandlers) {
141     if (handler_entry.method == method_name) {
142       handler = handler_entry.handler;
143       break;
144     }
145   }
146 
147   if (!handler)
148     return RequestResult(RequestResult::NOT_HANDLED);
149 
150   bool should_massage = false;
151   bool allow_options = false;
152   if (method_name == kSendMessage) {
153     should_massage = true;
154     allow_options = true;
155   } else if (method_name == kSendNativeMessage) {
156     should_massage = true;
157   }
158 
159   if (should_massage) {
160     messaging_util::MassageSendMessageArguments(context->GetIsolate(),
161                                                 allow_options, arguments);
162   }
163 
164   APISignature::V8ParseResult parse_result =
165       signature->ParseArgumentsToV8(context, *arguments, refs);
166   if (!parse_result.succeeded()) {
167     RequestResult result(RequestResult::INVALID_INVOCATION);
168     result.error = std::move(*parse_result.error);
169     return result;
170   }
171 
172   return (this->*handler)(script_context, *parse_result.arguments);
173 }
174 
InitializeTemplate(v8::Isolate * isolate,v8::Local<v8::ObjectTemplate> object_template,const APITypeReferenceMap & type_refs)175 void RuntimeHooksDelegate::InitializeTemplate(
176     v8::Isolate* isolate,
177     v8::Local<v8::ObjectTemplate> object_template,
178     const APITypeReferenceMap& type_refs) {
179   object_template->SetAccessor(gin::StringToSymbol(isolate, "id"),
180                                &GetExtensionId);
181 }
182 
HandleGetManifest(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & parsed_arguments)183 RequestResult RuntimeHooksDelegate::HandleGetManifest(
184     ScriptContext* script_context,
185     const std::vector<v8::Local<v8::Value>>& parsed_arguments) {
186   DCHECK(script_context->extension());
187 
188   RequestResult result(RequestResult::HANDLED);
189   result.return_value = content::V8ValueConverter::Create()->ToV8Value(
190       script_context->extension()->manifest()->value(),
191       script_context->v8_context());
192 
193   return result;
194 }
195 
HandleGetURL(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)196 RequestResult RuntimeHooksDelegate::HandleGetURL(
197     ScriptContext* script_context,
198     const std::vector<v8::Local<v8::Value>>& arguments) {
199   return GetURL(script_context, arguments);
200 }
201 
HandleSendMessage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)202 RequestResult RuntimeHooksDelegate::HandleSendMessage(
203     ScriptContext* script_context,
204     const std::vector<v8::Local<v8::Value>>& arguments) {
205   DCHECK_EQ(4u, arguments.size());
206 
207   std::string target_id;
208   std::string error;
209   if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
210                                             "runtime.sendMessage", &target_id,
211                                             &error)) {
212     RequestResult result(RequestResult::INVALID_INVOCATION);
213     result.error = std::move(error);
214     return result;
215   }
216 
217   v8::Local<v8::Context> v8_context = script_context->v8_context();
218 
219   v8::Local<v8::Value> v8_message = arguments[1];
220   std::unique_ptr<Message> message =
221       messaging_util::MessageFromV8(v8_context, v8_message, &error);
222   if (!message) {
223     RequestResult result(RequestResult::INVALID_INVOCATION);
224     result.error = std::move(error);
225     return result;
226   }
227 
228   // Note: arguments[2] is the options argument. However, the only available
229   // option for sendMessage() is includeTlsChannelId. That option has no effect
230   // since M72, but it is still part of the public spec for compatibility and is
231   // parsed into |arguments|. See crbug.com/1045232.
232 
233   v8::Local<v8::Function> response_callback;
234   if (!arguments[3]->IsNull())
235     response_callback = arguments[3].As<v8::Function>();
236 
237   messaging_service_->SendOneTimeMessage(
238       script_context, MessageTarget::ForExtension(target_id),
239       messaging_util::kSendMessageChannel, *message, response_callback);
240 
241   return RequestResult(RequestResult::HANDLED);
242 }
243 
HandleSendNativeMessage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)244 RequestResult RuntimeHooksDelegate::HandleSendNativeMessage(
245     ScriptContext* script_context,
246     const std::vector<v8::Local<v8::Value>>& arguments) {
247   DCHECK_EQ(3u, arguments.size());
248 
249   std::string application_name =
250       gin::V8ToString(script_context->isolate(), arguments[0]);
251 
252   v8::Local<v8::Value> v8_message = arguments[1];
253   DCHECK(!v8_message.IsEmpty());
254   std::string error;
255   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
256       script_context->v8_context(), v8_message, &error);
257   if (!message) {
258     RequestResult result(RequestResult::INVALID_INVOCATION);
259     result.error = std::move(error);
260     return result;
261   }
262 
263   v8::Local<v8::Function> response_callback;
264   if (!arguments[2]->IsNull())
265     response_callback = arguments[2].As<v8::Function>();
266 
267   messaging_service_->SendOneTimeMessage(
268       script_context, MessageTarget::ForNativeApp(application_name),
269       std::string(), *message, response_callback);
270 
271   return RequestResult(RequestResult::HANDLED);
272 }
273 
HandleConnect(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)274 RequestResult RuntimeHooksDelegate::HandleConnect(
275     ScriptContext* script_context,
276     const std::vector<v8::Local<v8::Value>>& arguments) {
277   DCHECK_EQ(2u, arguments.size());
278 
279   std::string target_id;
280   std::string error;
281   if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
282                                             "runtime.connect", &target_id,
283                                             &error)) {
284     RequestResult result(RequestResult::INVALID_INVOCATION);
285     result.error = std::move(error);
286     return result;
287   }
288 
289   messaging_util::MessageOptions options;
290   if (!arguments[1]->IsNull()) {
291     options = messaging_util::ParseMessageOptions(
292         script_context->v8_context(), arguments[1].As<v8::Object>(),
293             messaging_util::PARSE_CHANNEL_NAME);
294   }
295 
296   gin::Handle<GinPort> port = messaging_service_->Connect(
297       script_context, MessageTarget::ForExtension(target_id),
298       options.channel_name);
299   DCHECK(!port.IsEmpty());
300 
301   RequestResult result(RequestResult::HANDLED);
302   result.return_value = port.ToV8();
303   return result;
304 }
305 
HandleConnectNative(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)306 RequestResult RuntimeHooksDelegate::HandleConnectNative(
307     ScriptContext* script_context,
308     const std::vector<v8::Local<v8::Value>>& arguments) {
309   DCHECK_EQ(1u, arguments.size());
310   DCHECK(arguments[0]->IsString());
311 
312   std::string application_name =
313       gin::V8ToString(script_context->isolate(), arguments[0]);
314   gin::Handle<GinPort> port = messaging_service_->Connect(
315       script_context, MessageTarget::ForNativeApp(application_name),
316       std::string());
317 
318   RequestResult result(RequestResult::HANDLED);
319   result.return_value = port.ToV8();
320   return result;
321 }
322 
HandleGetBackgroundPage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)323 RequestResult RuntimeHooksDelegate::HandleGetBackgroundPage(
324     ScriptContext* script_context,
325     const std::vector<v8::Local<v8::Value>>& arguments) {
326   DCHECK(script_context->extension());
327 
328   RequestResult result(RequestResult::NOT_HANDLED);
329   if (!v8::Function::New(script_context->v8_context(),
330                          &GetBackgroundPageCallback, arguments[0])
331            .ToLocal(&result.custom_callback)) {
332     return RequestResult(RequestResult::THROWN);
333   }
334 
335   return result;
336 }
337 
HandleGetPackageDirectoryEntryCallback(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)338 RequestResult RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback(
339     ScriptContext* script_context,
340     const std::vector<v8::Local<v8::Value>>& arguments) {
341   // TODO(devlin): This is basically just copied and translated from
342   // the JS bindings, and still relies on the custom JS bindings for
343   // getBindDirectoryEntryCallback. This entire API is a bit crazy, and needs
344   // some help.
345   v8::Isolate* isolate = script_context->isolate();
346   v8::Local<v8::Context> v8_context = script_context->v8_context();
347 
348   v8::MaybeLocal<v8::Value> maybe_custom_callback;
349   {  // Begin natives enabled scope (for requiring the module).
350     ModuleSystem::NativesEnabledScope enable_natives(
351         script_context->module_system());
352     content::RenderFrame* background_page =
353         ExtensionFrameHelper::GetBackgroundPageFrame(
354             script_context->extension()->id());
355 
356     // The JS function will sometimes use the background page's context to do
357     // some work (see also
358     // extensions/renderer/resources/file_entry_binding_util.js).  In order to
359     // allow native code to run in the background page, we'll also need a
360     // NativesEnabledScope for that context.
361     DCHECK(v8_context == isolate->GetCurrentContext());
362     base::Optional<ModuleSystem::NativesEnabledScope> background_page_natives;
363     if (background_page &&
364         background_page != script_context->GetRenderFrame() &&
365         blink::WebFrame::ScriptCanAccess(background_page->GetWebFrame())) {
366       ScriptContext* background_page_script_context =
367           GetScriptContextFromV8Context(
368               background_page->GetWebFrame()->MainWorldScriptContext());
369       if (background_page_script_context) {
370         background_page_natives.emplace(
371             background_page_script_context->module_system());
372       }
373     }
374 
375     v8::Local<v8::Object> file_entry_binding_util;
376     // ModuleSystem::Require can return an empty Maybe when it fails for any
377     // number of reasons. It *shouldn't* ever throw, but it is technically
378     // possible. This makes the handling the failure result complicated. Since
379     // this shouldn't happen at all, bail and consider it handled if it fails.
380     if (!script_context->module_system()
381              ->Require("fileEntryBindingUtil")
382              .ToLocal(&file_entry_binding_util)) {
383       NOTREACHED();
384       // Abort, and consider the request handled.
385       return RequestResult(RequestResult::HANDLED);
386     }
387 
388     v8::Local<v8::Value> get_bind_directory_entry_callback_value;
389     if (!file_entry_binding_util
390              ->Get(v8_context, gin::StringToSymbol(
391                                    isolate, "getBindDirectoryEntryCallback"))
392              .ToLocal(&get_bind_directory_entry_callback_value)) {
393       NOTREACHED();
394       return RequestResult(RequestResult::THROWN);
395     }
396 
397     if (!get_bind_directory_entry_callback_value->IsFunction()) {
398       NOTREACHED();
399       // Abort, and consider the request handled.
400       return RequestResult(RequestResult::HANDLED);
401     }
402 
403     v8::Local<v8::Function> get_bind_directory_entry_callback =
404         get_bind_directory_entry_callback_value.As<v8::Function>();
405 
406     maybe_custom_callback =
407         JSRunner::Get(v8_context)
408             ->RunJSFunctionSync(get_bind_directory_entry_callback, v8_context,
409                                 0, nullptr);
410   }  // End modules enabled scope.
411   v8::Local<v8::Value> callback;
412   if (!maybe_custom_callback.ToLocal(&callback)) {
413     NOTREACHED();
414     return RequestResult(RequestResult::THROWN);
415   }
416 
417   if (!callback->IsFunction()) {
418     NOTREACHED();
419     // Abort, and consider the request handled.
420     return RequestResult(RequestResult::HANDLED);
421   }
422 
423   RequestResult result(RequestResult::NOT_HANDLED);
424   result.custom_callback = callback.As<v8::Function>();
425   return result;
426 }
427 
428 }  // namespace extensions
429