| OLD | NEW |
| (Empty) |
| 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 | |
| 12 namespace extensions { | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 const 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" | |
| 29 const 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 " ['create', 'defineProperty', 'getOwnPropertyDescriptor',\n" | |
| 70 " '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 " ['slice', 'split']);\n" | |
| 78 "saveBuiltin(RegExp,\n" | |
| 79 " ['test']);\n" | |
| 80 "\n" | |
| 81 "// JSON is trickier because extensions can override toJSON in\n" | |
| 82 "// incompatible ways, and we need to prevent that.\n" | |
| 83 "var builtinTypes = [\n" | |
| 84 " Object, Function, Array, String, Boolean, Number, Date, RegExp\n" | |
| 85 "];\n" | |
| 86 "var builtinToJSONs = builtinTypes.map(function(t) {\n" | |
| 87 " return t.toJSON;\n" | |
| 88 "});\n" | |
| 89 "var builtinArray = Array;\n" | |
| 90 "var builtinJSONStringify = JSON.stringify;\n" | |
| 91 "Save('JSON', {\n" | |
| 92 " parse: JSON.parse,\n" | |
| 93 " stringify: function(obj) {\n" | |
| 94 " var savedToJSONs = new builtinArray(builtinTypes.length);\n" | |
| 95 " try {\n" | |
| 96 " for (var i = 0; i < builtinTypes.length; ++i) {\n" | |
| 97 " try {\n" | |
| 98 " if (builtinTypes[i].prototype.toJSON !==\n" | |
| 99 " builtinToJSONs[i]) {\n" | |
| 100 " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n" | |
| 101 " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n" | |
| 102 " }\n" | |
| 103 " } catch (e) {}\n" | |
| 104 " }\n" | |
| 105 " } catch (e) {}\n" | |
| 106 " try {\n" | |
| 107 " return builtinJSONStringify(obj);\n" | |
| 108 " } finally {\n" | |
| 109 " for (var i = 0; i < builtinTypes.length; ++i) {\n" | |
| 110 " try {\n" | |
| 111 " if (i in savedToJSONs)\n" | |
| 112 " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n" | |
| 113 " } catch (e) {}\n" | |
| 114 " }\n" | |
| 115 " }\n" | |
| 116 " }\n" | |
| 117 "});\n" | |
| 118 "\n" | |
| 119 "}());\n"; | |
| 120 | |
| 121 v8::Local<v8::String> MakeKey(const char* name, v8::Isolate* isolate) { | |
| 122 return v8::String::NewFromUtf8( | |
| 123 isolate, base::StringPrintf("%s::%s", kClassName, name).c_str()); | |
| 124 } | |
| 125 | |
| 126 void SaveImpl(const char* name, | |
| 127 v8::Local<v8::Value> value, | |
| 128 v8::Local<v8::Context> context) { | |
| 129 CHECK(!value.IsEmpty() && value->IsObject()) << name; | |
| 130 context->Global() | |
| 131 ->SetHiddenValue(MakeKey(name, context->GetIsolate()), value); | |
| 132 } | |
| 133 | |
| 134 v8::Local<v8::Object> Load(const char* name, v8::Handle<v8::Context> context) { | |
| 135 v8::Local<v8::Value> value = | |
| 136 context->Global()->GetHiddenValue(MakeKey(name, context->GetIsolate())); | |
| 137 CHECK(!value.IsEmpty() && value->IsObject()) << name; | |
| 138 return value->ToObject(); | |
| 139 } | |
| 140 | |
| 141 class ExtensionImpl : public v8::Extension { | |
| 142 public: | |
| 143 ExtensionImpl() : v8::Extension(kClassName, kScript) {} | |
| 144 | |
| 145 private: | |
| 146 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate( | |
| 147 v8::Isolate* isolate, | |
| 148 v8::Handle<v8::String> name) OVERRIDE { | |
| 149 if (name->Equals(v8::String::NewFromUtf8(isolate, "Apply"))) | |
| 150 return v8::FunctionTemplate::New(isolate, Apply); | |
| 151 if (name->Equals(v8::String::NewFromUtf8(isolate, "Save"))) | |
| 152 return v8::FunctionTemplate::New(isolate, Save); | |
| 153 NOTREACHED() << *v8::String::Utf8Value(name); | |
| 154 return v8::Handle<v8::FunctionTemplate>(); | |
| 155 } | |
| 156 | |
| 157 static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) { | |
| 158 CHECK(info.Length() == 5 && | |
| 159 info[0]->IsFunction() && // function | |
| 160 // info[1] could be an object or a string | |
| 161 info[2]->IsObject() && // args | |
| 162 info[3]->IsInt32() && // first_arg_index | |
| 163 info[4]->IsInt32()); // args_length | |
| 164 v8::Local<v8::Function> function = info[0].As<v8::Function>(); | |
| 165 v8::Local<v8::Object> recv; | |
| 166 if (info[1]->IsObject()) { | |
| 167 recv = info[1]->ToObject(); | |
| 168 } else if (info[1]->IsString()) { | |
| 169 recv = v8::StringObject::New(info[1]->ToString())->ToObject(); | |
| 170 } else { | |
| 171 info.GetIsolate()->ThrowException( | |
| 172 v8::Exception::TypeError(v8::String::NewFromUtf8( | |
| 173 info.GetIsolate(), | |
| 174 "The first argument is the receiver and must be an object"))); | |
| 175 return; | |
| 176 } | |
| 177 v8::Local<v8::Object> args = info[2]->ToObject(); | |
| 178 int first_arg_index = static_cast<int>(info[3]->ToInt32()->Value()); | |
| 179 int args_length = static_cast<int>(info[4]->ToInt32()->Value()); | |
| 180 | |
| 181 int argc = args_length - first_arg_index; | |
| 182 scoped_ptr<v8::Local<v8::Value>[]> argv(new v8::Local<v8::Value>[argc]); | |
| 183 for (int i = 0; i < argc; ++i) { | |
| 184 CHECK(args->Has(i + first_arg_index)); | |
| 185 argv[i] = args->Get(i + first_arg_index); | |
| 186 } | |
| 187 | |
| 188 v8::Local<v8::Value> return_value = function->Call(recv, argc, argv.get()); | |
| 189 if (!return_value.IsEmpty()) | |
| 190 info.GetReturnValue().Set(return_value); | |
| 191 } | |
| 192 | |
| 193 static void Save(const v8::FunctionCallbackInfo<v8::Value>& info) { | |
| 194 CHECK(info.Length() == 2 && | |
| 195 info[0]->IsString() && | |
| 196 info[1]->IsObject()); | |
| 197 SaveImpl(*v8::String::Utf8Value(info[0]), | |
| 198 info[1], | |
| 199 info.GetIsolate()->GetCallingContext()); | |
| 200 } | |
| 201 }; | |
| 202 | |
| 203 } // namespace | |
| 204 | |
| 205 // static | |
| 206 v8::Extension* SafeBuiltins::CreateV8Extension() { | |
| 207 return new ExtensionImpl(); | |
| 208 } | |
| 209 | |
| 210 SafeBuiltins::SafeBuiltins(ChromeV8Context* context) : context_(context) {} | |
| 211 | |
| 212 SafeBuiltins::~SafeBuiltins() {} | |
| 213 | |
| 214 v8::Local<v8::Object> SafeBuiltins::GetArray() const { | |
| 215 return Load("Array", context_->v8_context()); | |
| 216 } | |
| 217 | |
| 218 v8::Local<v8::Object> SafeBuiltins::GetFunction() const { | |
| 219 return Load("Function", context_->v8_context()); | |
| 220 } | |
| 221 | |
| 222 v8::Local<v8::Object> SafeBuiltins::GetJSON() const { | |
| 223 return Load("JSON", context_->v8_context()); | |
| 224 } | |
| 225 | |
| 226 v8::Local<v8::Object> SafeBuiltins::GetObjekt() const { | |
| 227 return Load("Object", context_->v8_context()); | |
| 228 } | |
| 229 | |
| 230 v8::Local<v8::Object> SafeBuiltins::GetRegExp() const { | |
| 231 return Load("RegExp", context_->v8_context()); | |
| 232 } | |
| 233 | |
| 234 v8::Local<v8::Object> SafeBuiltins::GetString() const { | |
| 235 return Load("String", context_->v8_context()); | |
| 236 } | |
| 237 | |
| 238 } // namespace extensions | |
| OLD | NEW |