1// Copyright 2013 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/safe_builtins.h"
6
7#include "base/logging.h"
8#include "base/stl_util.h"
9#include "base/strings/stringprintf.h"
10#include "chrome/renderer/extensions/chrome_v8_context.h"
11
12namespace extensions {
13
14namespace {
15
16const char kClassName[] = "extensions::SafeBuiltins";
17
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:
20//
21// Returns a new object with every function on |obj| configured to call()\n"
22// itself with the given arguments.\n"
23// E.g. given\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"
29const char kScript[] =
30    "(function() {\n"
31    "'use strict';\n"
32    "native function Apply();\n"
33    "native function Save();\n"
34    "\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"
40    "      var recv = obj;\n"
41    "      var firstArgIndex = 0;\n"
42    "      if (!isStatic) {\n"
43    "        if (arguments.length == 0)\n"
44    "          throw 'There must be at least one argument, the recevier';\n"
45    "        recv = arguments[0];\n"
46    "        firstArgIndex = 1;\n"
47    "      }\n"
48    "      return Apply(\n"
49    "          property, recv, arguments, firstArgIndex, arguments.length);\n"
50    "    };\n"
51    "  });\n"
52    "}\n"
53    "\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"
58    "  };\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"
64    "}\n"
65    "\n"
66    "// Save only what is needed to make tests that override builtins pass.\n"
67    "saveBuiltin(Object,\n"
68    "            ['hasOwnProperty'],\n"
69    "            ['getPrototypeOf', 'keys']);\n"
70    "saveBuiltin(Function,\n"
71    "            ['apply', 'bind', 'call']);\n"
72    "saveBuiltin(Array,\n"
73    "            ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
74    "             'splice', 'map', 'filter']);\n"
75    "saveBuiltin(String,\n"
76    "            ['slice', 'split']);\n"
77    "saveBuiltin(RegExp,\n"
78    "            ['test']);\n"
79    "\n"
80    "// JSON is trickier because extensions can override toJSON in\n"
81    "// incompatible ways, and we need to prevent that.\n"
82    "var builtinTypes = [\n"
83    "  Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
84    "];\n"
85    "var builtinToJSONs = builtinTypes.map(function(t) {\n"
86    "  return t.toJSON;\n"
87    "});\n"
88    "var builtinArray = Array;\n"
89    "var builtinJSONStringify = JSON.stringify;\n"
90    "Save('JSON', {\n"
91    "  parse: JSON.parse,\n"
92    "  stringify: function(obj) {\n"
93    "    var savedToJSONs = new builtinArray(builtinTypes.length);\n"
94    "    try {\n"
95    "      for (var i = 0; i < builtinTypes.length; ++i) {\n"
96    "        try {\n"
97    "          if (builtinTypes[i].prototype.toJSON !==\n"
98    "              builtinToJSONs[i]) {\n"
99    "            savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
100    "            builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
101    "          }\n"
102    "        } catch (e) {}\n"
103    "      }\n"
104    "    } catch (e) {}\n"
105    "    try {\n"
106    "      return builtinJSONStringify(obj);\n"
107    "    } finally {\n"
108    "      for (var i = 0; i < builtinTypes.length; ++i) {\n"
109    "        try {\n"
110    "          if (i in savedToJSONs)\n"
111    "            builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
112    "        } catch (e) {}\n"
113    "      }\n"
114    "    }\n"
115    "  }\n"
116    "});\n"
117    "\n"
118    "}());\n";
119
120v8::Local<v8::String> MakeKey(const char* name, v8::Isolate* isolate) {
121  return v8::String::NewFromUtf8(
122      isolate, base::StringPrintf("%s::%s", kClassName, name).c_str());
123}
124
125void SaveImpl(const char* name,
126              v8::Local<v8::Value> value,
127              v8::Local<v8::Context> context) {
128  CHECK(!value.IsEmpty() && value->IsObject()) << name;
129  context->Global()
130      ->SetHiddenValue(MakeKey(name, context->GetIsolate()), value);
131}
132
133v8::Local<v8::Object> Load(const char* name, v8::Handle<v8::Context> context) {
134  v8::Local<v8::Value> value =
135      context->Global()->GetHiddenValue(MakeKey(name, context->GetIsolate()));
136  CHECK(!value.IsEmpty() && value->IsObject()) << name;
137  return value->ToObject();
138}
139
140class ExtensionImpl : public v8::Extension {
141 public:
142  ExtensionImpl() : v8::Extension(kClassName, kScript) {}
143
144 private:
145  virtual v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate(
146      v8::Isolate* isolate,
147      v8::Handle<v8::String> name) OVERRIDE {
148    if (name->Equals(v8::String::NewFromUtf8(isolate, "Apply")))
149      return v8::FunctionTemplate::New(isolate, Apply);
150    if (name->Equals(v8::String::NewFromUtf8(isolate, "Save")))
151      return v8::FunctionTemplate::New(isolate, Save);
152    NOTREACHED() << *v8::String::Utf8Value(name);
153    return v8::Handle<v8::FunctionTemplate>();
154  }
155
156  static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) {
157    CHECK(info.Length() == 5 &&
158          info[0]->IsFunction() &&  // function
159          // info[1] could be an object or a string
160          info[2]->IsObject() &&    // args
161          info[3]->IsInt32() &&     // first_arg_index
162          info[4]->IsInt32());      // args_length
163    v8::Local<v8::Function> function = info[0].As<v8::Function>();
164    v8::Local<v8::Object> recv;
165    if (info[1]->IsObject()) {
166      recv = info[1]->ToObject();
167    } else if (info[1]->IsString()) {
168      recv = v8::StringObject::New(info[1]->ToString())->ToObject();
169    } else {
170      info.GetIsolate()->ThrowException(
171          v8::Exception::TypeError(v8::String::NewFromUtf8(
172              info.GetIsolate(),
173              "The first argument is the receiver and must be an object")));
174      return;
175    }
176    v8::Local<v8::Object> args = info[2]->ToObject();
177    int first_arg_index = static_cast<int>(info[3]->ToInt32()->Value());
178    int args_length = static_cast<int>(info[4]->ToInt32()->Value());
179
180    int argc = args_length - first_arg_index;
181    scoped_ptr<v8::Local<v8::Value>[]> argv(new v8::Local<v8::Value>[argc]);
182    for (int i = 0; i < argc; ++i) {
183      CHECK(args->Has(i + first_arg_index));
184      argv[i] = args->Get(i + first_arg_index);
185    }
186
187    v8::Local<v8::Value> return_value = function->Call(recv, argc, argv.get());
188    if (!return_value.IsEmpty())
189      info.GetReturnValue().Set(return_value);
190  }
191
192  static void Save(const v8::FunctionCallbackInfo<v8::Value>& info) {
193    CHECK(info.Length() == 2 &&
194          info[0]->IsString() &&
195          info[1]->IsObject());
196    SaveImpl(*v8::String::Utf8Value(info[0]),
197             info[1],
198             info.GetIsolate()->GetCallingContext());
199  }
200};
201
202}  // namespace
203
204// static
205v8::Extension* SafeBuiltins::CreateV8Extension() {
206  return new ExtensionImpl();
207}
208
209SafeBuiltins::SafeBuiltins(ChromeV8Context* context) : context_(context) {}
210
211SafeBuiltins::~SafeBuiltins() {}
212
213v8::Local<v8::Object> SafeBuiltins::GetArray() const {
214  return Load("Array", context_->v8_context());
215}
216
217v8::Local<v8::Object> SafeBuiltins::GetFunction() const {
218  return Load("Function", context_->v8_context());
219}
220
221v8::Local<v8::Object> SafeBuiltins::GetJSON() const {
222  return Load("JSON", context_->v8_context());
223}
224
225v8::Local<v8::Object> SafeBuiltins::GetObjekt() const {
226  return Load("Object", context_->v8_context());
227}
228
229v8::Local<v8::Object> SafeBuiltins::GetRegExp() const {
230  return Load("RegExp", context_->v8_context());
231}
232
233v8::Local<v8::Object> SafeBuiltins::GetString() const {
234  return Load("String", context_->v8_context());
235}
236
237} //  namespace extensions
238