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.
5 #include "extensions/renderer/safe_builtins.h"
7 #include "base/logging.h"
8 #include "base/stl_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "extensions/renderer/script_context.h"
12 namespace extensions
{
16 const char kClassName
[] = "extensions::SafeBuiltins";
18 // Documentation for makeCallback in the JavaScript, out here to reduce the
19 // (very small) amount of effort that the v8 parser needs to do:
21 // Returns a new object with every function on |obj| configured to call()\n"
22 // itself with the given arguments.\n"
24 // var result = makeCallable(Function.prototype)\n"
25 // |result| will be a object including 'bind' such that\n"
26 // result.bind(foo, 1, 2, 3);\n"
27 // is equivalent to Function.prototype.bind.call(foo, 1, 2, 3), and so on.\n"
28 // This is a convenient way to save functions that user scripts may clobber.\n"
29 const char kScript
[] =
32 "native function Apply();\n"
33 "native function Save();\n"
35 "// Used in the callback implementation, could potentially be clobbered.\n"
36 "function makeCallable(obj, target, isStatic, propertyNames) {\n"
37 " propertyNames.forEach(function(propertyName) {\n"
38 " var property = obj[propertyName];\n"
39 " target[propertyName] = function() {\n"
41 " var firstArgIndex = 0;\n"
43 " if (arguments.length == 0)\n"
44 " throw 'There must be at least one argument, the receiver';\n"
45 " recv = arguments[0];\n"
46 " firstArgIndex = 1;\n"
49 " property, recv, arguments, firstArgIndex, arguments.length);\n"
54 "function saveBuiltin(builtin, protoPropertyNames, staticPropertyNames) {\n"
55 " var safe = function() {\n"
56 " throw 'Safe objects cannot be called nor constructed. ' +\n"
57 " 'Use $Foo.self() or new $Foo.self() instead.';\n"
59 " safe.self = builtin;\n"
60 " makeCallable(builtin.prototype, safe, false, protoPropertyNames);\n"
61 " if (staticPropertyNames)\n"
62 " makeCallable(builtin, safe, true, staticPropertyNames);\n"
63 " Save(builtin.name, safe);\n"
66 "// Save only what is needed by the extension modules.\n"
67 "saveBuiltin(Object,\n"
68 " ['hasOwnProperty'],\n"
69 " ['create', 'defineProperty', 'freeze',\n"
70 " 'getOwnPropertyDescriptor', 'getPrototypeOf', 'keys']);\n"
71 "saveBuiltin(Function,\n"
72 " ['apply', 'bind', 'call']);\n"
73 "saveBuiltin(Array,\n"
74 " ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
75 " 'splice', 'map', 'filter']);\n"
76 "saveBuiltin(String,\n"
77 " ['indexOf', 'slice', 'split', 'substr', 'toUpperCase',\n"
79 "saveBuiltin(RegExp,\n"
81 "saveBuiltin(Error,\n"
83 " ['captureStackTrace']);\n"
85 "// JSON is trickier because extensions can override toJSON in\n"
86 "// incompatible ways, and we need to prevent that.\n"
87 "var builtinTypes = [\n"
88 " Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
90 "var builtinToJSONs = builtinTypes.map(function(t) {\n"
93 "var builtinArray = Array;\n"
94 "var builtinJSONStringify = JSON.stringify;\n"
96 " parse: JSON.parse,\n"
97 " stringify: function(obj) {\n"
98 " var savedToJSONs = new builtinArray(builtinTypes.length);\n"
100 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
102 " if (builtinTypes[i].prototype.toJSON !==\n"
103 " builtinToJSONs[i]) {\n"
104 " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
105 " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
111 " return builtinJSONStringify(obj);\n"
113 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
115 " if (i in savedToJSONs)\n"
116 " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
125 v8::Local
<v8::String
> MakeKey(const char* name
, v8::Isolate
* isolate
) {
126 return v8::String::NewFromUtf8(
127 isolate
, base::StringPrintf("%s::%s", kClassName
, name
).c_str());
130 void SaveImpl(const char* name
,
131 v8::Local
<v8::Value
> value
,
132 v8::Local
<v8::Context
> context
) {
133 CHECK(!value
.IsEmpty() && value
->IsObject()) << name
;
134 context
->Global()->SetHiddenValue(MakeKey(name
, context
->GetIsolate()),
138 v8::Local
<v8::Object
> Load(const char* name
, v8::Local
<v8::Context
> context
) {
139 v8::Local
<v8::Value
> value
=
140 context
->Global()->GetHiddenValue(MakeKey(name
, context
->GetIsolate()));
141 CHECK(!value
.IsEmpty() && value
->IsObject()) << name
;
142 return v8::Local
<v8::Object
>::Cast(value
);
145 class ExtensionImpl
: public v8::Extension
{
147 ExtensionImpl() : v8::Extension(kClassName
, kScript
) {}
150 v8::Local
<v8::FunctionTemplate
> GetNativeFunctionTemplate(
151 v8::Isolate
* isolate
,
152 v8::Local
<v8::String
> name
) override
{
153 if (name
->Equals(v8::String::NewFromUtf8(isolate
, "Apply")))
154 return v8::FunctionTemplate::New(isolate
, Apply
);
155 if (name
->Equals(v8::String::NewFromUtf8(isolate
, "Save")))
156 return v8::FunctionTemplate::New(isolate
, Save
);
157 NOTREACHED() << *v8::String::Utf8Value(name
);
158 return v8::Local
<v8::FunctionTemplate
>();
161 static void Apply(const v8::FunctionCallbackInfo
<v8::Value
>& info
) {
162 CHECK(info
.Length() == 5 && info
[0]->IsFunction() && // function
163 // info[1] could be an object or a string
164 info
[2]->IsObject() && // args
165 info
[3]->IsInt32() && // first_arg_index
166 info
[4]->IsInt32()); // args_length
167 v8::Local
<v8::Function
> function
= info
[0].As
<v8::Function
>();
168 v8::Local
<v8::Object
> recv
;
169 if (info
[1]->IsObject()) {
170 recv
= v8::Local
<v8::Object
>::Cast(info
[1]);
171 } else if (info
[1]->IsString()) {
172 recv
= v8::StringObject::New(v8::Local
<v8::String
>::Cast(info
[1]))
173 ->ToObject(info
.GetIsolate());
175 info
.GetIsolate()->ThrowException(
176 v8::Exception::TypeError(v8::String::NewFromUtf8(
178 "The first argument is the receiver and must be an object")));
181 v8::Local
<v8::Object
> args
= v8::Local
<v8::Object
>::Cast(info
[2]);
182 int first_arg_index
= info
[3]->ToInt32(info
.GetIsolate())->Value();
183 int args_length
= info
[4]->ToInt32(info
.GetIsolate())->Value();
185 int argc
= args_length
- first_arg_index
;
186 scoped_ptr
<v8::Local
<v8::Value
> []> argv(new v8::Local
<v8::Value
>[argc
]);
187 for (int i
= 0; i
< argc
; ++i
) {
188 CHECK(args
->Has(i
+ first_arg_index
));
189 argv
[i
] = args
->Get(i
+ first_arg_index
);
192 v8::Local
<v8::Value
> return_value
= function
->Call(recv
, argc
, argv
.get());
193 if (!return_value
.IsEmpty())
194 info
.GetReturnValue().Set(return_value
);
197 static void Save(const v8::FunctionCallbackInfo
<v8::Value
>& info
) {
198 CHECK(info
.Length() == 2 && info
[0]->IsString() && info
[1]->IsObject());
199 SaveImpl(*v8::String::Utf8Value(info
[0]),
201 info
.GetIsolate()->GetCallingContext());
208 v8::Extension
* SafeBuiltins::CreateV8Extension() { return new ExtensionImpl(); }
210 SafeBuiltins::SafeBuiltins(ScriptContext
* context
) : context_(context
) {}
212 SafeBuiltins::~SafeBuiltins() {}
214 v8::Local
<v8::Object
> SafeBuiltins::GetArray() const {
215 return Load("Array", context_
->v8_context());
218 v8::Local
<v8::Object
> SafeBuiltins::GetFunction() const {
219 return Load("Function", context_
->v8_context());
222 v8::Local
<v8::Object
> SafeBuiltins::GetJSON() const {
223 return Load("JSON", context_
->v8_context());
226 v8::Local
<v8::Object
> SafeBuiltins::GetObjekt() const {
227 return Load("Object", context_
->v8_context());
230 v8::Local
<v8::Object
> SafeBuiltins::GetRegExp() const {
231 return Load("RegExp", context_
->v8_context());
234 v8::Local
<v8::Object
> SafeBuiltins::GetString() const {
235 return Load("String", context_
->v8_context());
238 v8::Local
<v8::Object
> SafeBuiltins::GetError() const {
239 return Load("Error", context_
->v8_context());
242 } // namespace extensions