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