Exemple #1
0
bool
document_resolve(JSContext *cx, JS::HandleObject obj, JS::HandleId id, unsigned flags,
                 JS::MutableHandleObject objp)
{
    // If id is "all", resolve document.all=true.
    JS::RootedValue v(cx);
    if (!JS_IdToValue(cx, id, &v))
        return false;
    if (JSVAL_IS_STRING(v)) {
        JSString *str = JSVAL_TO_STRING(v);
        JSFlatString *flatStr = JS_FlattenString(cx, str);
        if (!flatStr)
            return false;
        if (JS_FlatStringEqualsAscii(flatStr, "all")) {
            JS::Rooted<JSObject*> docAll(cx,
                                         JS_NewObject(cx, &DocumentAllClass, JS::NullPtr(), JS::NullPtr()));
            if (!docAll)
                return false;
            JS::Rooted<JS::Value> allValue(cx, ObjectValue(*docAll));
            bool ok = JS_DefinePropertyById(cx, obj, id, allValue, nullptr, nullptr, 0);
            objp.set(ok ? obj.get() : nullptr);
            return ok;
        }
    }
    objp.set(nullptr);
    return true;
}
JSStringWrapper::JSStringWrapper(JSContext* cx, JSString* str) : _isSet(true) {
    if (!str)
        throwCurrentJSException(cx, ErrorCodes::InternalError, "Cannot encode null JSString");

    // We have to do this flatstring thing because no public api tells us
    // how long the utf8 strings we get out are.
    //
    // Well, at least js/CharacterEncoding's GetDeflatedUTF8StringLength
    // and JS_flattenString are all in the public headers...
    JSFlatString* flat = JS_FlattenString(cx, str);
    if (!flat)
        throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to flatten JSString");

    _length = JS::GetDeflatedUTF8StringLength(flat);

    char* out;
    if (_length < sizeof(_buf)) {
        out = _buf;
    } else {
        _str.reset(new char[_length + 1]);
        out = _str.get();
    }

    JS::DeflateStringToUTF8Buffer(flat, mozilla::RangedPtr<char>(out, _length));
    out[_length] = '\0';
}
FRAGMENT(JSString, subclasses) {
  js::Rooted<JSFlatString *> flat(cx, JS_FlattenString(cx, JS_NewStringCopyZ(cx, "Hi!")));

  breakpoint();

  (void) flat;
}
Exemple #4
0
bool
document_resolve(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *resolvedp)
{
    // If id is "all", resolve document.all=true.
    JS::RootedValue v(cx);
    if (!JS_IdToValue(cx, id, &v))
        return false;

    if (v.isString()) {
        JSString *str = v.toString();
        JSFlatString *flatStr = JS_FlattenString(cx, str);
        if (!flatStr)
            return false;
        if (JS_FlatStringEqualsAscii(flatStr, "all")) {
            JS::Rooted<JSObject*> docAll(cx,
                                         JS_NewObject(cx, &DocumentAllClass, JS::NullPtr(), JS::NullPtr()));
            if (!docAll)
                return false;

            JS::Rooted<JS::Value> allValue(cx, JS::ObjectValue(*docAll));
            if (!JS_DefinePropertyById(cx, obj, id, allValue, 0))
                return false;

            *resolvedp = true;
            return true;
        }
    }

    *resolvedp = false;
    return true;
}
Exemple #5
0
FRAGMENT(JSString, subclasses) {
  JS::Rooted<JSFlatString*> flat(
      cx, JS_FlattenString(cx, JS_NewStringCopyZ(cx, "Hi!")));

  breakpoint();

  use(flat);
}
bool
doResolve(JSObject *obj, jsid id, uintN flags, JSObject **objp)
{
    CHECK_EQUAL(resolveExitCount, 0);
    AutoIncrCounters incr(this);
    CHECK_EQUAL(obj, obj1 || obj == obj2);
    
    CHECK(JSID_IS_STRING(id));
    
    JSFlatString *str = JS_FlattenString(cx, JSID_TO_STRING(id));
    CHECK(str);
    jsval v;
    if (JS_FlatStringEqualsAscii(str, "x")) {
        if (obj == obj1) {
            /* First resolve hook invocation. */
            CHECK_EQUAL(resolveEntryCount, 1);
            EVAL("obj2.y = true", &v);
            CHECK_SAME(v, JSVAL_TRUE);
            CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_FALSE, NULL, NULL, 0));
            *objp = obj;
            return true;
        }
        if (obj == obj2) {
            CHECK_EQUAL(resolveEntryCount, 4);
            *objp = NULL;
            return true;
        }
    } else if (JS_FlatStringEqualsAscii(str, "y")) {
        if (obj == obj2) {
            CHECK_EQUAL(resolveEntryCount, 2);
            CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_NULL, NULL, NULL, 0));
            EVAL("obj1.x", &v);
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj1.y", &v);
            CHECK_SAME(v, JSVAL_ZERO);
            *objp = obj;
            return true;
        }
        if (obj == obj1) {
            CHECK_EQUAL(resolveEntryCount, 3);
            EVAL("obj1.x", &v);
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj1.y", &v);
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj2.y", &v);
            CHECK(JSVAL_IS_NULL(v));
            EVAL("obj2.x", &v);
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj1.y = 0", &v);
            CHECK_SAME(v, JSVAL_ZERO);
            *objp = obj;
            return true;
        }
    }
    CHECK(false);
    return false;
}
Exemple #7
0
bool
doResolve(JSHandleObject obj, JSHandleId id, unsigned flags, JSMutableHandleObject objp)
{
    CHECK_EQUAL(resolveExitCount, 0);
    AutoIncrCounters incr(this);
    CHECK_EQUAL(obj, obj1 || obj == obj2);

    CHECK(JSID_IS_STRING(id));

    JSFlatString *str = JS_FlattenString(cx, JSID_TO_STRING(id));
    CHECK(str);
    JS::RootedValue v(cx);
    if (JS_FlatStringEqualsAscii(str, "x")) {
        if (obj == obj1) {
            /* First resolve hook invocation. */
            CHECK_EQUAL(resolveEntryCount, 1);
            EVAL("obj2.y = true", v.address());
            CHECK_SAME(v, JSVAL_TRUE);
            CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_FALSE, NULL, NULL, 0));
            objp.set(obj);
            return true;
        }
        if (obj == obj2) {
            CHECK_EQUAL(resolveEntryCount, 4);
            objp.set(NULL);
            return true;
        }
    } else if (JS_FlatStringEqualsAscii(str, "y")) {
        if (obj == obj2) {
            CHECK_EQUAL(resolveEntryCount, 2);
            CHECK(JS_DefinePropertyById(cx, obj, id, JSVAL_NULL, NULL, NULL, 0));
            EVAL("obj1.x", v.address());
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj1.y", v.address());
            CHECK_SAME(v, JSVAL_ZERO);
            objp.set(obj);
            return true;
        }
        if (obj == obj1) {
            CHECK_EQUAL(resolveEntryCount, 3);
            EVAL("obj1.x", v.address());
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj1.y", v.address());
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj2.y", v.address());
            CHECK(JSVAL_IS_NULL(v));
            EVAL("obj2.x", v.address());
            CHECK(JSVAL_IS_VOID(v));
            EVAL("obj1.y = 0", v.address());
            CHECK_SAME(v, JSVAL_ZERO);
            objp.set(obj);
            return true;
        }
    }
    CHECK(false);
    return false;
}
bool
doResolve(JS::HandleObject obj, JS::HandleId id, bool* resolvedp)
{
    CHECK_EQUAL(resolveExitCount, 0);
    AutoIncrCounters incr(this);
    CHECK(obj == obj1 || obj == obj2);

    CHECK(JSID_IS_STRING(id));

    JSFlatString* str = JS_FlattenString(cx, JSID_TO_STRING(id));
    CHECK(str);
    JS::RootedValue v(cx);
    if (JS_FlatStringEqualsAscii(str, "x")) {
        if (obj == obj1) {
            /* First resolve hook invocation. */
            CHECK_EQUAL(resolveEntryCount, 1);
            EVAL("obj2.y = true", &v);
            CHECK(v.isTrue());
            CHECK(JS_DefinePropertyById(cx, obj, id, JS::FalseHandleValue, JSPROP_RESOLVING));
            *resolvedp = true;
            return true;
        }
        if (obj == obj2) {
            CHECK_EQUAL(resolveEntryCount, 4);
            *resolvedp = false;
            return true;
        }
    } else if (JS_FlatStringEqualsAscii(str, "y")) {
        if (obj == obj2) {
            CHECK_EQUAL(resolveEntryCount, 2);
            CHECK(JS_DefinePropertyById(cx, obj, id, JS::NullHandleValue, JSPROP_RESOLVING));
            EVAL("obj1.x", &v);
            CHECK(v.isUndefined());
            EVAL("obj1.y", &v);
            CHECK(v.isInt32(0));
            *resolvedp = true;
            return true;
        }
        if (obj == obj1) {
            CHECK_EQUAL(resolveEntryCount, 3);
            EVAL("obj1.x", &v);
            CHECK(v.isUndefined());
            EVAL("obj1.y", &v);
            CHECK(v.isUndefined());
            EVAL("obj2.y", &v);
            CHECK(v.isNull());
            EVAL("obj2.x", &v);
            CHECK(v.isUndefined());
            EVAL("obj1.y = 0", &v);
            CHECK(v.isInt32(0));
            *resolvedp = true;
            return true;
        }
    }
    CHECK(false);
    return false;
}
Exemple #9
0
static JSBool js_flatten(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);

	if(!JSVAL_IS_STRING(argv[0])) {
		JS_ReportError(cx, "get_size() error!  Parameter is not a string.");
		return(JS_FALSE);
	}
	JS_FlattenString(cx, JSVAL_TO_STRING(argv[0]));
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
	return(JS_TRUE);
}
Exemple #10
0
JSBool
document_resolve(JSContext *cx, JSHandleObject obj, JSHandleId id, unsigned flags, JSObject **objp)
{
    // If id is "all", and we're not detecting, resolve document.all=true.
    jsvalRoot v(cx);
    if (!JS_IdToValue(cx, id, v.addr()))
        return false;
    if (JSVAL_IS_STRING(v.value())) {
        JSString *str = JSVAL_TO_STRING(v.value());
        JSFlatString *flatStr = JS_FlattenString(cx, str);
        if (!flatStr)
            return false;
        if (JS_FlatStringEqualsAscii(flatStr, "all") && !(flags & JSRESOLVE_DETECTING)) {
            JSBool ok = JS_DefinePropertyById(cx, obj, id, JSVAL_TRUE, NULL, NULL, 0);
            *objp = ok ? obj.value() : NULL;
            return ok;
        }
    }
    *objp = NULL;
    return true;
}
Exemple #11
0
JSObject*
Library::Create(JSContext* cx, HandleValue path, const JSCTypesCallbacks* callbacks)
{
  RootedObject libraryObj(cx, JS_NewObject(cx, &sLibraryClass));
  if (!libraryObj)
    return nullptr;

  // initialize the library
  JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PrivateValue(nullptr));

  // attach API functions
  if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions))
    return nullptr;

  if (!path.isString()) {
    JS_ReportErrorASCII(cx, "open takes a string argument");
    return nullptr;
  }

  PRLibSpec libSpec;
  RootedFlatString pathStr(cx, JS_FlattenString(cx, path.toString()));
  if (!pathStr)
    return nullptr;
  AutoStableStringChars pathStrChars(cx);
  if (!pathStrChars.initTwoByte(cx, pathStr))
    return nullptr;
#ifdef XP_WIN
  // On Windows, converting to native charset may corrupt path string.
  // So, we have to use Unicode path directly.
  char16ptr_t pathChars = pathStrChars.twoByteChars();
  libSpec.value.pathname_u = pathChars;
  libSpec.type = PR_LibSpec_PathnameU;
#else
  // Convert to platform native charset if the appropriate callback has been
  // provided.
  char* pathBytes;
  if (callbacks && callbacks->unicodeToNative) {
    pathBytes =
      callbacks->unicodeToNative(cx, pathStrChars.twoByteChars(), pathStr->length());
    if (!pathBytes)
      return nullptr;

  } else {
    // Fallback: assume the platform native charset is UTF-8. This is true
    // for Mac OS X, Android, and probably Linux.
    size_t nbytes =
      GetDeflatedUTF8StringLength(cx, pathStrChars.twoByteChars(), pathStr->length());
    if (nbytes == (size_t) -1)
      return nullptr;

    pathBytes = static_cast<char*>(JS_malloc(cx, nbytes + 1));
    if (!pathBytes)
      return nullptr;

    ASSERT_OK(DeflateStringToUTF8Buffer(cx, pathStrChars.twoByteChars(),
                pathStr->length(), pathBytes, &nbytes));
    pathBytes[nbytes] = 0;
  }

  libSpec.value.pathname = pathBytes;
  libSpec.type = PR_LibSpec_Pathname;
#endif

  PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW);

#ifndef XP_WIN
  JS_free(cx, pathBytes);
#endif

  if (!library) {
#define MAX_ERROR_LEN 1024
    char error[MAX_ERROR_LEN] = "Cannot get error from NSPR.";
    uint32_t errorLen = PR_GetErrorTextLength();
    if (errorLen && errorLen < MAX_ERROR_LEN)
      PR_GetErrorText(error);
#undef MAX_ERROR_LEN

    if (JS::StringIsASCII(error)) {
      JSAutoByteString pathCharsUTF8;
      if (pathCharsUTF8.encodeUtf8(cx, pathStr))
        JS_ReportErrorUTF8(cx, "couldn't open library %s: %s", pathCharsUTF8.ptr(), error);
    } else {
      JSAutoByteString pathCharsLatin1;
      if (pathCharsLatin1.encodeLatin1(cx, pathStr))
        JS_ReportErrorLatin1(cx, "couldn't open library %s: %s", pathCharsLatin1.ptr(), error);
    }
    return nullptr;
  }

  // stash the library
  JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PrivateValue(library));

  return libraryObj;
}
Exemple #12
0
JSObject*
Library::Create(JSContext* cx, jsval path, JSCTypesCallbacks* callbacks)
{
  JSObject* libraryObj = JS_NewObject(cx, &sLibraryClass, NULL, NULL);
  if (!libraryObj)
    return NULL;
  js::AutoObjectRooter root(cx, libraryObj);

  // initialize the library
  if (!JS_SetReservedSlot(cx, libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(NULL)))
    return NULL;

  // attach API functions
  if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions))
    return NULL;

  if (!JSVAL_IS_STRING(path)) {
    JS_ReportError(cx, "open takes a string argument");
    return NULL;
  }

  PRLibSpec libSpec;
  JSFlatString* pathStr = JS_FlattenString(cx, JSVAL_TO_STRING(path));
  if (!pathStr)
    return NULL;
#ifdef XP_WIN
  // On Windows, converting to native charset may corrupt path string.
  // So, we have to use Unicode path directly.
  const PRUnichar* pathChars = JS_GetFlatStringChars(pathStr);
  if (!pathChars)
    return NULL;

  libSpec.value.pathname_u = pathChars;
  libSpec.type = PR_LibSpec_PathnameU;
#else
  // Convert to platform native charset if the appropriate callback has been
  // provided.
  char* pathBytes;
  if (callbacks && callbacks->unicodeToNative) {
    pathBytes = 
      callbacks->unicodeToNative(cx, pathStr->chars(), pathStr->length());
    if (!pathBytes)
      return NULL;

  } else {
    // Fallback: assume the platform native charset is UTF-8. This is true
    // for Mac OS X, Android, and probably Linux.
    size_t nbytes =
      js_GetDeflatedUTF8StringLength(cx, pathStr->chars(), pathStr->length());
    if (nbytes == (size_t) -1)
      return NULL;

    pathBytes = static_cast<char*>(JS_malloc(cx, nbytes + 1));
    if (!pathBytes)
      return NULL;

    ASSERT_OK(js_DeflateStringToUTF8Buffer(cx, pathStr->chars(),
                pathStr->length(), pathBytes, &nbytes));
    pathBytes[nbytes] = 0;
  }

  libSpec.value.pathname = pathBytes;
  libSpec.type = PR_LibSpec_Pathname;
#endif

  PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, 0);
#ifndef XP_WIN
  JS_free(cx, pathBytes);
#endif
  if (!library) {
    JS_ReportError(cx, "couldn't open library");
    return NULL;
  }

  // stash the library
  if (!JS_SetReservedSlot(cx, libraryObj, SLOT_LIBRARY,
         PRIVATE_TO_JSVAL(library)))
    return NULL;

  return libraryObj;
}
Exemple #13
0
bool
ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act)
{
    RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));

    if (act == Wrapper::CALL)
        return false;

    // For the case of getting a property descriptor, we allow if either GET or SET
    // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
    if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
        return check(cx, wrapper, id, Wrapper::GET) ||
               check(cx, wrapper, id, Wrapper::SET);
    }

    RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS));

    // We need to enter the wrappee's compartment to look at __exposedProps__,
    // but we want to be in the wrapper's compartment if we call Deny().
    //
    // Unfortunately, |cx| can be in either compartment when we call ::check. :-(
    JSAutoCompartment ac(cx, wrappedObject);

    bool found = false;
    if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
        return false;

    // If no __exposedProps__ existed, deny access.
    if (!found) {
        // Previously we automatically granted access to indexed properties and
        // .length for Array COWs. We're not doing that anymore, so make sure to
        // let people know what's going on.
        bool isArray;
        if (!JS_IsArrayObject(cx, wrappedObject, &isArray))
            return false;
        if (!isArray)
            isArray = JS_IsTypedArrayObject(wrappedObject);
        bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0;
        bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) &&
                                     JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length");
        if (isIndexedAccessOnArray || isLengthAccessOnArray) {
            JSAutoCompartment ac2(cx, wrapper);
            ReportWrapperDenial(cx, id, WrapperDenialForCOW,
                                "Access to elements and length of privileged Array not permitted");
        }

        return false;
    }

    if (id == JSID_VOID)
        return true;

    Rooted<JSPropertyDescriptor> desc(cx);
    if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc))
        return false;

    if (!desc.object())
        return false;

    if (desc.hasGetterOrSetter()) {
        EnterAndThrow(cx, wrapper, "__exposedProps__ must be a value property");
        return false;
    }

    RootedValue exposedProps(cx, desc.value());
    if (exposedProps.isNullOrUndefined())
        return false;

    if (!exposedProps.isObject()) {
        EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
        return false;
    }

    RootedObject hallpass(cx, &exposedProps.toObject());

    if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
        EnterAndThrow(cx, wrapper, "Invalid __exposedProps__");
        return false;
    }

    Access access = NO_ACCESS;

    if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
        return false; // Error
    }
    if (!desc.object() || !desc.enumerable())
        return false;

    if (!desc.value().isString()) {
        EnterAndThrow(cx, wrapper, "property must be a string");
        return false;
    }

    JSFlatString* flat = JS_FlattenString(cx, desc.value().toString());
    if (!flat)
        return false;

    size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat));

    for (size_t i = 0; i < length; ++i) {
        char16_t ch = JS_GetFlatStringCharAt(flat, i);
        switch (ch) {
        case 'r':
            if (access & READ) {
                EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag");
                return false;
            }
            access = Access(access | READ);
            break;

        case 'w':
            if (access & WRITE) {
                EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag");
                return false;
            }
            access = Access(access | WRITE);
            break;

        default:
            EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable");
            return false;
        }
    }

    if (access == NO_ACCESS) {
        EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set");
        return false;
    }

    if ((act == Wrapper::SET && !(access & WRITE)) ||
        (act != Wrapper::SET && !(access & READ))) {
        return false;
    }

    // Inspect the property on the underlying object to check for red flags.
    if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc))
        return false;

    // Reject accessor properties.
    if (desc.hasGetterOrSetter()) {
        EnterAndThrow(cx, wrapper, "Exposing privileged accessor properties is prohibited");
        return false;
    }

    // Reject privileged or cross-origin callables.
    if (desc.value().isObject()) {
        RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject()));
        if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) {
            EnterAndThrow(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited");
            return false;
        }
    }

    return true;
}
Exemple #14
0
bool
ExposedPropertiesOnly::check(JSContext *cx, HandleObject wrapper, HandleId id, Wrapper::Action act)
{
    RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));

    if (act == Wrapper::CALL)
        return true;


    // For the case of getting a property descriptor, we allow if either GET or SET
    // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
    if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
        return check(cx, wrapper, id, Wrapper::GET) ||
               check(cx, wrapper, id, Wrapper::SET);
    }

    RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS));

    // We need to enter the wrappee's compartment to look at __exposedProps__,
    // but we want to be in the wrapper's compartment if we call Deny().
    //
    // Unfortunately, |cx| can be in either compartment when we call ::check. :-(
    JSAutoCompartment ac(cx, wrappedObject);

    bool found = false;
    if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
        return false;

    // Always permit access to "length" and indexed properties of arrays.
    if ((JS_IsArrayObject(cx, wrappedObject) ||
         JS_IsTypedArrayObject(wrappedObject)) &&
        ((JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) ||
         (JSID_IS_STRING(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length")))) {
        return true; // Allow
    }

    // If no __exposedProps__ existed, deny access.
    if (!found) {
        return false;
    }

    if (id == JSID_VOID)
        return true;

    RootedValue exposedProps(cx);
    if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps))
        return false;

    if (exposedProps.isNullOrUndefined())
        return false;

    if (!exposedProps.isObject()) {
        EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
        return false;
    }

    RootedObject hallpass(cx, &exposedProps.toObject());

    if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
        EnterAndThrow(cx, wrapper, "Invalid __exposedProps__");
        return false;
    }

    Access access = NO_ACCESS;

    Rooted<JSPropertyDescriptor> desc(cx);
    if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
        return false; // Error
    }
    if (!desc.object() || !desc.isEnumerable())
        return false;

    if (!desc.value().isString()) {
        EnterAndThrow(cx, wrapper, "property must be a string");
        return false;
    }

    JSFlatString *flat = JS_FlattenString(cx, desc.value().toString());
    if (!flat)
        return false;

    size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat));

    for (size_t i = 0; i < length; ++i) {
        char16_t ch = JS_GetFlatStringCharAt(flat, i);
        switch (ch) {
        case 'r':
            if (access & READ) {
                EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag");
                return false;
            }
            access = Access(access | READ);
            break;

        case 'w':
            if (access & WRITE) {
                EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag");
                return false;
            }
            access = Access(access | WRITE);
            break;

        default:
            EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable");
            return false;
        }
    }

    if (access == NO_ACCESS) {
        EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set");
        return false;
    }

    if ((act == Wrapper::SET && !(access & WRITE)) ||
        (act != Wrapper::SET && !(access & READ))) {
        return false;
    }

    return true;
}