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 "chrome/renderer/extensions/extension_hooks_delegate.h"
6 
7 #include "content/public/renderer/v8_value_converter.h"
8 #include "extensions/common/api/messaging/message.h"
9 #include "extensions/common/constants.h"
10 #include "extensions/common/extension.h"
11 #include "extensions/common/manifest.h"
12 #include "extensions/common/view_type.h"
13 #include "extensions/renderer/bindings/api_signature.h"
14 #include "extensions/renderer/extension_frame_helper.h"
15 #include "extensions/renderer/extensions_renderer_client.h"
16 #include "extensions/renderer/get_script_context.h"
17 #include "extensions/renderer/message_target.h"
18 #include "extensions/renderer/messaging_util.h"
19 #include "extensions/renderer/native_renderer_messaging_service.h"
20 #include "extensions/renderer/runtime_hooks_delegate.h"
21 #include "extensions/renderer/script_context.h"
22 #include "gin/converter.h"
23 #include "gin/dictionary.h"
24 
25 namespace extensions {
26 
27 namespace {
28 
29 using RequestResult = APIBindingHooks::RequestResult;
30 
31 constexpr char kSendExtensionRequest[] = "extension.sendRequest";
32 constexpr char kGetURL[] = "extension.getURL";
33 constexpr char kGetBackgroundPage[] = "extension.getBackgroundPage";
34 constexpr char kGetViews[] = "extension.getViews";
35 constexpr char kGetExtensionTabs[] = "extension.getExtensionTabs";
36 
37 // We alias a bunch of chrome.extension APIs to their chrome.runtime
38 // counterparts.
39 // NOTE(devlin): This is a very simple alias, in which we just return the
40 // runtime version from the chrome.runtime object. This is important to note
41 // for a few reasons:
42 // - Modifications to the chrome.runtime object will affect the return result
43 //   here. i.e., if script does chrome.runtime.sendMessage = 'some string',
44 //   then chrome.extension.sendMessage will also be 'some string'.
45 // - Events will share listeners. i.e., a listener added to
46 //   chrome.extension.onMessage will fire from a runtime.onMessage event, and
47 //   vice versa.
48 // All of these APIs have been deprecated, and are no longer even documented,
49 // but still have usage. This is the cheap workaround that JS bindings have been
50 // using, and, while not robust, it should be secure, so use it native bindings,
51 // too.
GetAliasedFeature(v8::Local<v8::Name> property_name,const v8::PropertyCallbackInfo<v8::Value> & info)52 void GetAliasedFeature(v8::Local<v8::Name> property_name,
53                        const v8::PropertyCallbackInfo<v8::Value>& info) {
54   v8::Isolate* isolate = info.GetIsolate();
55   v8::HandleScope handle_scope(isolate);
56   v8::Local<v8::Context> context = info.Holder()->CreationContext();
57 
58   v8::TryCatch try_catch(isolate);
59   v8::Local<v8::Value> chrome;
60   if (!context->Global()
61            ->Get(context, gin::StringToSymbol(isolate, "chrome"))
62            .ToLocal(&chrome) ||
63       !chrome->IsObject()) {
64     return;
65   }
66 
67   v8::Local<v8::Value> runtime;
68   if (!chrome.As<v8::Object>()
69            ->Get(context, gin::StringToSymbol(isolate, "runtime"))
70            .ToLocal(&runtime) ||
71       !runtime->IsObject()) {
72     return;
73   }
74 
75   v8::Local<v8::Object> runtime_obj = runtime.As<v8::Object>();
76   v8::Maybe<bool> has_property =
77       runtime_obj->HasRealNamedProperty(context, property_name);
78   if (!has_property.IsJust() || !has_property.FromJust())
79     return;
80 
81   v8::Local<v8::Value> property_value;
82   // Try and grab the chrome.runtime version. It's possible this has been
83   // tampered with, so early-out if an exception is thrown.
84   if (!runtime_obj->Get(context, property_name).ToLocal(&property_value))
85     return;
86 
87   info.GetReturnValue().Set(property_value);
88 }
89 
90 // A helper method to throw a deprecation error on access.
ThrowDeprecatedAccessError(v8::Local<v8::Name> name,const v8::PropertyCallbackInfo<v8::Value> & info)91 void ThrowDeprecatedAccessError(
92     v8::Local<v8::Name> name,
93     const v8::PropertyCallbackInfo<v8::Value>& info) {
94   static constexpr char kError[] =
95       "extension.sendRequest, extension.onRequest, and "
96       "extension.onRequestExternal are deprecated. Please use "
97       "runtime.sendMessage, runtime.onMessage, and runtime.onMessageExternal "
98       "instead.";
99   v8::Isolate* isolate = info.GetIsolate();
100   isolate->ThrowException(
101       v8::Exception::Error(gin::StringToV8(isolate, kError)));
102 }
103 
104 }  // namespace
105 
ExtensionHooksDelegate(NativeRendererMessagingService * messaging_service)106 ExtensionHooksDelegate::ExtensionHooksDelegate(
107     NativeRendererMessagingService* messaging_service)
108     : messaging_service_(messaging_service) {}
~ExtensionHooksDelegate()109 ExtensionHooksDelegate::~ExtensionHooksDelegate() {}
110 
HandleRequest(const std::string & method_name,const APISignature * signature,v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * arguments,const APITypeReferenceMap & refs)111 RequestResult ExtensionHooksDelegate::HandleRequest(
112     const std::string& method_name,
113     const APISignature* signature,
114     v8::Local<v8::Context> context,
115     std::vector<v8::Local<v8::Value>>* arguments,
116     const APITypeReferenceMap& refs) {
117   // TODO(devlin): This logic is the same in the RuntimeCustomHooksDelegate -
118   // would it make sense to share it?
119   using Handler = RequestResult (ExtensionHooksDelegate::*)(
120       ScriptContext*, const std::vector<v8::Local<v8::Value>>&);
121   static struct {
122     Handler handler;
123     base::StringPiece method;
124   } kHandlers[] = {
125       {&ExtensionHooksDelegate::HandleSendRequest, kSendExtensionRequest},
126       {&ExtensionHooksDelegate::HandleGetURL, kGetURL},
127       {&ExtensionHooksDelegate::HandleGetBackgroundPage, kGetBackgroundPage},
128       {&ExtensionHooksDelegate::HandleGetExtensionTabs, kGetExtensionTabs},
129       {&ExtensionHooksDelegate::HandleGetViews, kGetViews},
130   };
131 
132   ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
133 
134   Handler handler = nullptr;
135   for (const auto& handler_entry : kHandlers) {
136     if (handler_entry.method == method_name) {
137       handler = handler_entry.handler;
138       break;
139     }
140   }
141 
142   if (!handler)
143     return RequestResult(RequestResult::NOT_HANDLED);
144 
145   if (method_name == kSendExtensionRequest) {
146     messaging_util::MassageSendMessageArguments(context->GetIsolate(), false,
147                                                 arguments);
148   }
149 
150   APISignature::V8ParseResult parse_result =
151       signature->ParseArgumentsToV8(context, *arguments, refs);
152   if (!parse_result.succeeded()) {
153     RequestResult result(RequestResult::INVALID_INVOCATION);
154     result.error = std::move(*parse_result.error);
155     return result;
156   }
157 
158   return (this->*handler)(script_context, *parse_result.arguments);
159 }
160 
InitializeTemplate(v8::Isolate * isolate,v8::Local<v8::ObjectTemplate> object_template,const APITypeReferenceMap & type_refs)161 void ExtensionHooksDelegate::InitializeTemplate(
162     v8::Isolate* isolate,
163     v8::Local<v8::ObjectTemplate> object_template,
164     const APITypeReferenceMap& type_refs) {
165   static constexpr const char* kAliases[] = {
166       "connect",   "connectNative",     "sendMessage", "sendNativeMessage",
167       "onConnect", "onConnectExternal", "onMessage",   "onMessageExternal",
168   };
169 
170   for (const auto* alias : kAliases) {
171     object_template->SetAccessor(gin::StringToSymbol(isolate, alias),
172                                  &GetAliasedFeature);
173   }
174 
175   bool is_incognito = ExtensionsRendererClient::Get()->IsIncognitoProcess();
176   object_template->Set(isolate, "inIncognitoContext",
177                        v8::Boolean::New(isolate, is_incognito));
178 }
179 
InitializeInstance(v8::Local<v8::Context> context,v8::Local<v8::Object> instance)180 void ExtensionHooksDelegate::InitializeInstance(
181     v8::Local<v8::Context> context,
182     v8::Local<v8::Object> instance) {
183   // Throw access errors for deprecated sendRequest-related properties. This
184   // isn't terribly efficient, but is only done for certain unpacked extensions
185   // and only if they access the chrome.extension module.
186   if (messaging_util::IsSendRequestDisabled(
187           GetScriptContextFromV8ContextChecked(context))) {
188     static constexpr const char* kDeprecatedSendRequestProperties[] = {
189         "sendRequest", "onRequest", "onRequestExternal"};
190     for (const char* property : kDeprecatedSendRequestProperties) {
191       v8::Maybe<bool> success = instance->SetAccessor(
192           context, gin::StringToV8(context->GetIsolate(), property),
193           &ThrowDeprecatedAccessError);
194       DCHECK(success.IsJust());
195       DCHECK(success.FromJust());
196     }
197   }
198 }
199 
HandleSendRequest(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)200 RequestResult ExtensionHooksDelegate::HandleSendRequest(
201     ScriptContext* script_context,
202     const std::vector<v8::Local<v8::Value>>& arguments) {
203   DCHECK_EQ(3u, arguments.size());
204   // This DCHECK() is correct because no context with sendRequest-related
205   // APIs disabled should have scriptable access to a context with them
206   // enabled.
207   DCHECK(!messaging_util::IsSendRequestDisabled(script_context));
208 
209   std::string target_id;
210   std::string error;
211   if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
212                                             "extension.sendRequest", &target_id,
213                                             &error)) {
214     RequestResult result(RequestResult::INVALID_INVOCATION);
215     result.error = std::move(error);
216     return result;
217   }
218 
219   v8::Local<v8::Value> v8_message = arguments[1];
220   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
221       script_context->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   v8::Local<v8::Function> response_callback;
229   if (!arguments[2]->IsNull())
230     response_callback = arguments[2].As<v8::Function>();
231 
232   messaging_service_->SendOneTimeMessage(
233       script_context, MessageTarget::ForExtension(target_id),
234       messaging_util::kSendRequestChannel, *message, response_callback);
235 
236   return RequestResult(RequestResult::HANDLED);
237 }
238 
HandleGetURL(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)239 RequestResult ExtensionHooksDelegate::HandleGetURL(
240     ScriptContext* script_context,
241     const std::vector<v8::Local<v8::Value>>& arguments) {
242   // We call a static implementation here rather using an alias due to not being
243   // able to remove the extension.json GetURL entry, as it is used for generated
244   // documentation and api feature lists some other methods refer to.
245   return RuntimeHooksDelegate::GetURL(script_context, arguments);
246 }
247 
HandleGetViews(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)248 APIBindingHooks::RequestResult ExtensionHooksDelegate::HandleGetViews(
249     ScriptContext* script_context,
250     const std::vector<v8::Local<v8::Value>>& arguments) {
251   const Extension* extension = script_context->extension();
252   DCHECK(extension);
253 
254   ViewType view_type = VIEW_TYPE_INVALID;
255   int window_id = extension_misc::kUnknownWindowId;
256   int tab_id = extension_misc::kUnknownTabId;
257 
258   if (!arguments[0]->IsNull()) {
259     gin::Dictionary options_dict(script_context->isolate(),
260                                  arguments[0].As<v8::Object>());
261     v8::Local<v8::Value> v8_window_id;
262     v8::Local<v8::Value> v8_tab_id;
263     v8::Local<v8::Value> v8_view_type;
264     if (!options_dict.Get("windowId", &v8_window_id) ||
265         !options_dict.Get("tabId", &v8_tab_id) ||
266         !options_dict.Get("type", &v8_view_type)) {
267       NOTREACHED()
268           << "Unexpected exception: argument parsing produces plain objects";
269       return RequestResult(RequestResult::THROWN);
270     }
271 
272     if (!v8_window_id->IsUndefined()) {
273       DCHECK(v8_window_id->IsInt32());
274       window_id = v8_window_id.As<v8::Int32>()->Value();
275     }
276 
277     if (!v8_tab_id->IsUndefined()) {
278       DCHECK(v8_tab_id->IsInt32());
279       tab_id = v8_tab_id.As<v8::Int32>()->Value();
280     }
281 
282     if (!v8_view_type->IsUndefined()) {
283       DCHECK(v8_view_type->IsString());
284       std::string view_type_string = base::ToUpperASCII(
285           gin::V8ToString(script_context->isolate(), v8_view_type));
286       if (view_type_string != "ALL") {
287         bool success = GetViewTypeFromString(view_type_string, &view_type);
288         DCHECK(success);
289       }
290     }
291   }
292 
293   RequestResult result(RequestResult::HANDLED);
294   result.return_value = ExtensionFrameHelper::GetV8MainFrames(
295       script_context->v8_context(), extension->id(), window_id, tab_id,
296       view_type);
297   return result;
298 }
299 
HandleGetExtensionTabs(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)300 RequestResult ExtensionHooksDelegate::HandleGetExtensionTabs(
301     ScriptContext* script_context,
302     const std::vector<v8::Local<v8::Value>>& arguments) {
303   const Extension* extension = script_context->extension();
304   DCHECK(extension);
305 
306   ViewType view_type = VIEW_TYPE_TAB_CONTENTS;
307   int window_id = extension_misc::kUnknownWindowId;
308   int tab_id = extension_misc::kUnknownTabId;
309 
310   if (!arguments[0]->IsNull())
311     window_id = arguments[0].As<v8::Int32>()->Value();
312 
313   RequestResult result(RequestResult::HANDLED);
314   result.return_value = ExtensionFrameHelper::GetV8MainFrames(
315       script_context->v8_context(), extension->id(), window_id, tab_id,
316       view_type);
317   return result;
318 }
319 
HandleGetBackgroundPage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)320 RequestResult ExtensionHooksDelegate::HandleGetBackgroundPage(
321     ScriptContext* script_context,
322     const std::vector<v8::Local<v8::Value>>& arguments) {
323   const Extension* extension = script_context->extension();
324   DCHECK(extension);
325 
326   RequestResult result(RequestResult::HANDLED);
327   result.return_value = ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
328       script_context->isolate(), extension->id());
329   return result;
330 }
331 
332 }  // namespace extensions
333