blob: 389b019c6ba18ca6a57c57bf48f0f203a6b36ba0 [file] [log] [blame]
Devlin Cronin0b875672017-10-06 00:49:211// 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/messaging_util.h"
6
7#include <string>
8
9#include "base/logging.h"
Devlin Croninc4b07fb2017-11-14 20:26:3410#include "base/strings/stringprintf.h"
Devlin Cronin0b875672017-10-06 00:49:2111#include "extensions/common/api/messaging/message.h"
Devlin Croninc4b07fb2017-11-14 20:26:3412#include "extensions/common/extension.h"
13#include "extensions/renderer/script_context.h"
Devlin Cronin0b875672017-10-06 00:49:2114#include "gin/converter.h"
Devlin Cronin37198932017-11-07 20:15:2315#include "gin/dictionary.h"
Devlin Cronin0b875672017-10-06 00:49:2116#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
17
18namespace extensions {
19namespace messaging_util {
20
Devlin Croninc4b07fb2017-11-14 20:26:3421namespace {
22
23constexpr char kExtensionIdRequiredErrorTemplate[] =
24 "chrome.%s() called from a webpage must specify an "
25 "Extension ID (string) for its first argument.";
26
27} // namespace
28
Devlin Cronin37198932017-11-07 20:15:2329const char kSendMessageChannel[] = "chrome.runtime.sendMessage";
30const char kSendRequestChannel[] = "chrome.extension.sendRequest";
31
Devlin Cronin182b0892017-11-10 22:22:1632const char kOnMessageEvent[] = "runtime.onMessage";
33const char kOnMessageExternalEvent[] = "runtime.onMessageExternal";
34const char kOnRequestEvent[] = "extension.onRequest";
35const char kOnRequestExternalEvent[] = "extension.onRequestExternal";
36const char kOnConnectEvent[] = "runtime.onConnect";
37const char kOnConnectExternalEvent[] = "runtime.onConnectExternal";
38
Devlin Cronin37198932017-11-07 20:15:2339const int kNoFrameId = -1;
40
Devlin Cronin0b875672017-10-06 00:49:2141std::unique_ptr<Message> MessageFromV8(v8::Local<v8::Context> context,
42 v8::Local<v8::Value> value) {
43 DCHECK(!value.IsEmpty());
44 v8::Isolate* isolate = context->GetIsolate();
45 v8::Context::Scope context_scope(context);
46
47 // TODO(devlin): For some reason, we don't use the signature for
48 // Port.postMessage when evaluating the parameters. We probably should, but
49 // we don't know how many extensions that may break. It would be good to
50 // investigate, and, ideally, use the signature.
51
52 if (value->IsUndefined()) {
53 // JSON.stringify won't serialized undefined (it returns undefined), but it
54 // will serialized null. We've always converted undefined to null in JS
55 // bindings, so preserve this behavior for now.
56 value = v8::Null(isolate);
57 }
58
59 bool success = false;
60 v8::Local<v8::String> stringified;
61 {
62 v8::TryCatch try_catch(isolate);
63 success = v8::JSON::Stringify(context, value).ToLocal(&stringified);
64 }
65
66 std::string message;
67 if (success) {
68 message = gin::V8ToString(stringified);
69 // JSON.stringify can fail to produce a string value in one of two ways: it
70 // can throw an exception (as with unserializable objects), or it can return
71 // `undefined` (as with e.g. passing a function). If JSON.stringify returns
72 // `undefined`, the v8 API then coerces it to the string value "undefined".
73 // Check for this, and consider it a failure (since we didn't properly
74 // serialize a value).
75 success = message != "undefined";
76 }
77
78 if (!success)
79 return nullptr;
80
81 return std::make_unique<Message>(
82 message, blink::WebUserGestureIndicator::IsProcessingUserGesture());
83}
84
85v8::Local<v8::Value> MessageToV8(v8::Local<v8::Context> context,
86 const Message& message) {
87 v8::Isolate* isolate = context->GetIsolate();
88 v8::Context::Scope context_scope(context);
89
90 v8::Local<v8::String> v8_message_string =
91 gin::StringToV8(isolate, message.data);
92 v8::Local<v8::Value> parsed_message;
93 v8::TryCatch try_catch(isolate);
94 if (!v8::JSON::Parse(context, v8_message_string).ToLocal(&parsed_message)) {
95 NOTREACHED();
96 return v8::Local<v8::Value>();
97 }
98 return parsed_message;
99}
100
Devlin Cronin37198932017-11-07 20:15:23101int ExtractIntegerId(v8::Local<v8::Value> value) {
102 // Account for -0, which is a valid integer, but is stored as a number in v8.
103 DCHECK(value->IsNumber() &&
104 (value->IsInt32() || value.As<v8::Number>()->Value() == 0.0));
105 return value->Int32Value();
106}
107
108ParseOptionsResult ParseMessageOptions(v8::Local<v8::Context> context,
109 v8::Local<v8::Object> v8_options,
110 int flags,
111 MessageOptions* options_out,
112 std::string* error_out) {
113 DCHECK(!v8_options.IsEmpty());
114 DCHECK(!v8_options->IsNull());
115
116 v8::Isolate* isolate = context->GetIsolate();
117
118 MessageOptions options;
119
120 // Theoretically, our argument matching code already checked the types of
121 // the properties on v8_connect_options. However, since we don't make an
122 // independent copy, it's possible that author script has super sneaky
123 // getters/setters that change the result each time the property is
124 // queried. Make no assumptions.
125 gin::Dictionary options_dict(isolate, v8_options);
126 if ((flags & PARSE_CHANNEL_NAME) != 0) {
127 v8::Local<v8::Value> v8_channel_name;
128 if (!options_dict.Get("name", &v8_channel_name))
129 return THROWN;
130
131 if (!v8_channel_name->IsUndefined()) {
132 if (!v8_channel_name->IsString()) {
133 *error_out = "connectInfo.name must be a string.";
134 return TYPE_ERROR;
135 }
136 options.channel_name = gin::V8ToString(v8_channel_name);
137 }
138 }
139
140 if ((flags & PARSE_INCLUDE_TLS_CHANNEL_ID) != 0) {
141 v8::Local<v8::Value> v8_include_tls_channel_id;
142 if (!options_dict.Get("includeTlsChannelId", &v8_include_tls_channel_id))
143 return THROWN;
144
145 if (!v8_include_tls_channel_id->IsUndefined()) {
146 if (!v8_include_tls_channel_id->IsBoolean()) {
147 *error_out = "connectInfo.includeTlsChannelId must be a boolean.";
148 return TYPE_ERROR;
149 }
150 options.include_tls_channel_id =
151 v8_include_tls_channel_id->BooleanValue();
152 }
153 }
154
155 if ((flags & PARSE_FRAME_ID) != 0) {
156 v8::Local<v8::Value> v8_frame_id;
157 if (!options_dict.Get("frameId", &v8_frame_id))
158 return THROWN;
159
160 if (!v8_frame_id->IsUndefined()) {
161 if (!v8_frame_id->IsInt32() &&
162 (!v8_frame_id->IsNumber() ||
163 v8_frame_id.As<v8::Number>()->Value() != 0.0)) {
164 *error_out = "connectInfo.frameId must be an integer.";
165 return TYPE_ERROR;
166 }
167 options.frame_id = v8_frame_id->Int32Value();
168 }
169 }
170
171 *options_out = std::move(options);
172 return SUCCESS;
173}
174
Devlin Croninc4b07fb2017-11-14 20:26:34175bool GetTargetExtensionId(ScriptContext* script_context,
176 v8::Local<v8::Value> v8_target_id,
177 const char* method_name,
178 std::string* target_out,
179 std::string* error_out) {
180 DCHECK(!v8_target_id.IsEmpty());
181
182 std::string target_id;
183 if (v8_target_id->IsNull()) {
184 if (!script_context->extension()) {
185 *error_out =
186 base::StringPrintf(kExtensionIdRequiredErrorTemplate, method_name);
187 return false;
188 }
189
190 *target_out = script_context->extension()->id();
191 } else {
192 DCHECK(v8_target_id->IsString());
193 *target_out = gin::V8ToString(v8_target_id);
194 }
195
196 return true;
197}
198
199void MassageSendMessageArguments(
200 v8::Isolate* isolate,
201 bool allow_options_argument,
202 std::vector<v8::Local<v8::Value>>* arguments_out) {
203 base::span<const v8::Local<v8::Value>> arguments = *arguments_out;
204 size_t max_size = allow_options_argument ? 4u : 3u;
205 if (arguments.empty() || arguments.size() > max_size)
206 return;
207
208 v8::Local<v8::Value> target_id = v8::Null(isolate);
209 v8::Local<v8::Value> message = v8::Null(isolate);
210 v8::Local<v8::Value> options;
211 if (allow_options_argument)
212 options = v8::Null(isolate);
213 v8::Local<v8::Value> response_callback = v8::Null(isolate);
214
215 // If the last argument is a function, it is the response callback.
216 // Ignore it for the purposes of further argument parsing.
217 if ((*arguments.rbegin())->IsFunction()) {
218 response_callback = *arguments.rbegin();
219 arguments = arguments.first(arguments.size() - 1);
220 }
221
222 // Re-check for too many arguments after looking for the callback. If there
223 // are, early-out and rely on normal signature parsing to report the error.
224 if (arguments.size() >= max_size)
225 return;
226
227 switch (arguments.size()) {
228 case 0:
229 // Required argument (message) is missing.
230 // Early-out and rely on normal signature parsing to report this error.
231 return;
232 case 1:
233 // Argument must be the message.
234 message = arguments[0];
235 break;
236 case 2:
237 // Assume the meaning is (id, message) if id would be a string, or if
238 // the options argument isn't expected.
239 // Otherwise the meaning is (message, options).
240 if (!allow_options_argument || arguments[0]->IsString()) {
241 target_id = arguments[0];
242 message = arguments[1];
243 } else {
244 message = arguments[0];
245 options = arguments[1];
246 }
247 break;
248 case 3:
249 DCHECK(allow_options_argument);
250 // The meaning in this case is unambiguous.
251 target_id = arguments[0];
252 message = arguments[1];
253 options = arguments[2];
254 break;
255 default:
256 NOTREACHED();
257 }
258
259 if (allow_options_argument)
260 *arguments_out = {target_id, message, options, response_callback};
261 else
262 *arguments_out = {target_id, message, response_callback};
263}
264
Devlin Cronin0b875672017-10-06 00:49:21265} // namespace messaging_util
266} // namespace extensions