blob: 054d75494cfbbc9ee88c3516fc550aeb72195949 [file] [log] [blame]
/*
* Copyright (C) 2000 Harri Porten (porten@kde.org)
* Copyright (C) 2006 Jon Shier (jshier@iastate.edu)
* Copyright (C) 2003-2019 Apple Inc. All rights reseved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "config.h"
#include "JSLocation.h"
#include "JSDOMBinding.h"
#include "JSDOMBindingSecurity.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMWindowCustom.h"
#include "RuntimeApplicationChecks.h"
#include "WebCoreJSClientData.h"
#include <JavaScriptCore/JSFunction.h>
#include <JavaScriptCore/Lookup.h>
namespace WebCore {
using namespace JSC;
static bool getOwnPropertySlotCommon(JSLocation& thisObject, ExecState& state, PropertyName propertyName, PropertySlot& slot)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* window = thisObject.wrapped().window();
// When accessing Location cross-domain, functions are always the native built-in ones.
// See JSDOMWindow::getOwnPropertySlotDelegate for additional details.
// Our custom code is only needed to implement the Window cross-domain scheme, so if access is
// allowed, return false so the normal lookup will take place.
String message;
if (BindingSecurity::shouldAllowAccessToDOMWindow(state, window, message))
return false;
// https://html.spec.whatwg.org/#crossorigingetownpropertyhelper-(-o,-p-)
// We only allow access to Location.replace() cross origin.
if (propertyName == vm.propertyNames->replace) {
slot.setCustom(&thisObject, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum), nonCachingStaticFunctionGetter<jsLocationInstanceFunctionReplace, 1>);
return true;
}
// Getting location.href cross origin needs to throw. However, getOwnPropertyDescriptor() needs to return
// a descriptor that has a setter but no getter.
if (slot.internalMethodType() == PropertySlot::InternalMethodType::GetOwnProperty && propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName()) {
auto* entry = JSLocation::info()->staticPropHashTable->entry(propertyName);
CustomGetterSetter* customGetterSetter = CustomGetterSetter::create(vm, nullptr, entry->propertyPutter());
slot.setCustomGetterSetter(&thisObject, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontEnum), customGetterSetter);
return true;
}
if (handleCommonCrossOriginProperties(&thisObject, vm, propertyName, slot))
return true;
throwSecurityError(state, scope, message);
slot.setUndefined();
return false;
}
bool JSLocation::getOwnPropertySlot(JSObject* object, ExecState* state, PropertyName propertyName, PropertySlot& slot)
{
VM& vm = state->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsCast<JSLocation*>(object);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
bool result = getOwnPropertySlotCommon(*thisObject, *state, propertyName, slot);
EXCEPTION_ASSERT(!scope.exception() || !result);
RETURN_IF_EXCEPTION(scope, false);
if (result)
return true;
RELEASE_AND_RETURN(scope, JSObject::getOwnPropertySlot(object, state, propertyName, slot));
}
bool JSLocation::getOwnPropertySlotByIndex(JSObject* object, ExecState* state, unsigned index, PropertySlot& slot)
{
VM& vm = state->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsCast<JSLocation*>(object);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
bool result = getOwnPropertySlotCommon(*thisObject, *state, Identifier::from(vm, index), slot);
EXCEPTION_ASSERT(!scope.exception() || !result);
RETURN_IF_EXCEPTION(scope, false);
if (result)
return true;
RELEASE_AND_RETURN(scope, JSObject::getOwnPropertySlotByIndex(object, state, index, slot));
}
static bool putCommon(JSLocation& thisObject, ExecState& state, PropertyName propertyName)
{
VM& vm = state.vm();
// Silently block access to toString and valueOf.
if (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf)
return true;
// Always allow assigning to the whole location.
// However, alllowing assigning of pieces might inadvertently disclose parts of the original location.
// So fall through to the access check for those.
if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName())
return false;
// Block access and throw if there is a security error.
if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, thisObject.wrapped().window(), ThrowSecurityError))
return true;
return false;
}
void JSLocation::doPutPropertySecurityCheck(JSObject* object, ExecState* state, PropertyName propertyName, PutPropertySlot&)
{
auto* thisObject = jsCast<JSLocation*>(object);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
VM& vm = state->vm();
// Always allow assigning to the whole location.
// However, alllowing assigning of pieces might inadvertently disclose parts of the original location.
// So fall through to the access check for those.
if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName())
return;
// Block access and throw if there is a security error.
BindingSecurity::shouldAllowAccessToDOMWindow(state, thisObject->wrapped().window(), ThrowSecurityError);
}
bool JSLocation::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& putPropertySlot)
{
auto* thisObject = jsCast<JSLocation*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
if (putCommon(*thisObject, *state, propertyName))
return false;
return JSObject::put(thisObject, state, propertyName, value, putPropertySlot);
}
bool JSLocation::putByIndex(JSCell* cell, ExecState* state, unsigned index, JSValue value, bool shouldThrow)
{
VM& vm = state->vm();
auto* thisObject = jsCast<JSLocation*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
if (putCommon(*thisObject, *state, Identifier::from(vm, index)))
return false;
return JSObject::putByIndex(cell, state, index, value, shouldThrow);
}
bool JSLocation::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
{
JSLocation* thisObject = jsCast<JSLocation*>(cell);
// Only allow deleting by frames in the same origin.
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), ThrowSecurityError))
return false;
return Base::deleteProperty(thisObject, exec, propertyName);
}
bool JSLocation::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
{
JSLocation* thisObject = jsCast<JSLocation*>(cell);
// Only allow deleting by frames in the same origin.
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), ThrowSecurityError))
return false;
return Base::deletePropertyByIndex(thisObject, exec, propertyName);
}
void JSLocation::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
JSLocation* thisObject = jsCast<JSLocation*>(object);
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), DoNotReportSecurityError)) {
if (mode.includeDontEnumProperties())
addCrossOriginOwnPropertyNames<CrossOriginObject::Location>(*exec, propertyNames);
return;
}
Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
}
bool JSLocation::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
JSLocation* thisObject = jsCast<JSLocation*>(object);
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), ThrowSecurityError))
return false;
VM& vm = exec->vm();
if (descriptor.isAccessorDescriptor() && (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf))
return false;
return Base::defineOwnProperty(object, exec, propertyName, descriptor, throwException);
}
JSValue JSLocation::getPrototype(JSObject* object, ExecState* exec)
{
JSLocation* thisObject = jsCast<JSLocation*>(object);
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), DoNotReportSecurityError))
return jsNull();
return Base::getPrototype(object, exec);
}
bool JSLocation::preventExtensions(JSObject*, ExecState* exec)
{
auto scope = DECLARE_THROW_SCOPE(exec->vm());
throwTypeError(exec, scope, "Cannot prevent extensions on this object"_s);
return false;
}
String JSLocation::toStringName(const JSObject* object, ExecState* exec)
{
auto* thisObject = jsCast<const JSLocation*>(object);
if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), DoNotReportSecurityError))
return "Object"_s;
return "Location"_s;
}
bool JSLocationPrototype::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
VM& vm = state->vm();
auto* thisObject = jsCast<JSLocationPrototype*>(cell);
if (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf)
return false;
return Base::put(thisObject, state, propertyName, value, slot);
}
bool JSLocationPrototype::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
VM& vm = exec->vm();
if (descriptor.isAccessorDescriptor() && (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf))
return false;
return Base::defineOwnProperty(object, exec, propertyName, descriptor, throwException);
}
} // namespace WebCore