1/*
2 * Copyright (C) 2000 Harri Porten (porten@kde.org)
3 * Copyright (C) 2006 Jon Shier (jshier@iastate.edu)
4 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reseved.
5 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22
23#include "config.h"
24#include "JSLocation.h"
25
26#include "JSDOMBinding.h"
27#include "JSDOMBindingSecurity.h"
28#include "JSDOMExceptionHandling.h"
29#include "JSDOMWindowCustom.h"
30#include "RuntimeApplicationChecks.h"
31#include "WebCoreJSClientData.h"
32#include <JavaScriptCore/JSFunction.h>
33#include <JavaScriptCore/Lookup.h>
34
35namespace WebCore {
36using namespace JSC;
37
38static bool getOwnPropertySlotCommon(JSLocation& thisObject, ExecState& state, PropertyName propertyName, PropertySlot& slot)
39{
40 VM& vm = state.vm();
41 auto scope = DECLARE_THROW_SCOPE(vm);
42
43 auto* window = thisObject.wrapped().window();
44
45 // When accessing Location cross-domain, functions are always the native built-in ones.
46 // See JSDOMWindow::getOwnPropertySlotDelegate for additional details.
47
48 // Our custom code is only needed to implement the Window cross-domain scheme, so if access is
49 // allowed, return false so the normal lookup will take place.
50 String message;
51 if (BindingSecurity::shouldAllowAccessToDOMWindow(state, window, message))
52 return false;
53
54 // https://html.spec.whatwg.org/#crossorigingetownpropertyhelper-(-o,-p-)
55
56 // We only allow access to Location.replace() cross origin.
57 if (propertyName == vm.propertyNames->replace) {
58 slot.setCustom(&thisObject, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum), nonCachingStaticFunctionGetter<jsLocationInstanceFunctionReplace, 1>);
59 return true;
60 }
61
62 // Getting location.href cross origin needs to throw. However, getOwnPropertyDescriptor() needs to return
63 // a descriptor that has a setter but no getter.
64 if (slot.internalMethodType() == PropertySlot::InternalMethodType::GetOwnProperty && propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName()) {
65 auto* entry = JSLocation::info()->staticPropHashTable->entry(propertyName);
66 CustomGetterSetter* customGetterSetter = CustomGetterSetter::create(vm, nullptr, entry->propertyPutter());
67 slot.setCustomGetterSetter(&thisObject, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontEnum), customGetterSetter);
68 return true;
69 }
70
71 if (handleCommonCrossOriginProperties(&thisObject, vm, propertyName, slot))
72 return true;
73
74 throwSecurityError(state, scope, message);
75 slot.setUndefined();
76 return false;
77}
78
79bool JSLocation::getOwnPropertySlot(JSObject* object, ExecState* state, PropertyName propertyName, PropertySlot& slot)
80{
81 VM& vm = state->vm();
82 auto scope = DECLARE_THROW_SCOPE(vm);
83 auto* thisObject = jsCast<JSLocation*>(object);
84 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
85
86 bool result = getOwnPropertySlotCommon(*thisObject, *state, propertyName, slot);
87 EXCEPTION_ASSERT(!scope.exception() || !result);
88 RETURN_IF_EXCEPTION(scope, false);
89 if (result)
90 return true;
91 RELEASE_AND_RETURN(scope, JSObject::getOwnPropertySlot(object, state, propertyName, slot));
92}
93
94bool JSLocation::getOwnPropertySlotByIndex(JSObject* object, ExecState* state, unsigned index, PropertySlot& slot)
95{
96 VM& vm = state->vm();
97 auto scope = DECLARE_THROW_SCOPE(vm);
98 auto* thisObject = jsCast<JSLocation*>(object);
99 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
100
101 bool result = getOwnPropertySlotCommon(*thisObject, *state, Identifier::from(state, index), slot);
102 EXCEPTION_ASSERT(!scope.exception() || !result);
103 RETURN_IF_EXCEPTION(scope, false);
104 if (result)
105 return true;
106 RELEASE_AND_RETURN(scope, JSObject::getOwnPropertySlotByIndex(object, state, index, slot));
107}
108
109static bool putCommon(JSLocation& thisObject, ExecState& state, PropertyName propertyName)
110{
111 VM& vm = state.vm();
112 // Silently block access to toString and valueOf.
113 if (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf)
114 return true;
115
116 // Always allow assigning to the whole location.
117 // However, alllowing assigning of pieces might inadvertently disclose parts of the original location.
118 // So fall through to the access check for those.
119 if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName())
120 return false;
121
122 // Block access and throw if there is a security error.
123 if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, thisObject.wrapped().window(), ThrowSecurityError))
124 return true;
125
126 return false;
127}
128
129bool JSLocation::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& putPropertySlot)
130{
131 auto* thisObject = jsCast<JSLocation*>(cell);
132 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
133
134 if (putCommon(*thisObject, *state, propertyName))
135 return false;
136
137 return JSObject::put(thisObject, state, propertyName, value, putPropertySlot);
138}
139
140bool JSLocation::putByIndex(JSCell* cell, ExecState* state, unsigned index, JSValue value, bool shouldThrow)
141{
142 auto* thisObject = jsCast<JSLocation*>(cell);
143 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
144
145 if (putCommon(*thisObject, *state, Identifier::from(state, index)))
146 return false;
147
148 return JSObject::putByIndex(cell, state, index, value, shouldThrow);
149}
150
151bool JSLocation::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
152{
153 JSLocation* thisObject = jsCast<JSLocation*>(cell);
154 // Only allow deleting by frames in the same origin.
155 if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), ThrowSecurityError))
156 return false;
157 return Base::deleteProperty(thisObject, exec, propertyName);
158}
159
160bool JSLocation::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
161{
162 JSLocation* thisObject = jsCast<JSLocation*>(cell);
163 // Only allow deleting by frames in the same origin.
164 if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), ThrowSecurityError))
165 return false;
166 return Base::deletePropertyByIndex(thisObject, exec, propertyName);
167}
168
169void JSLocation::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
170{
171 JSLocation* thisObject = jsCast<JSLocation*>(object);
172 if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), DoNotReportSecurityError)) {
173 if (mode.includeDontEnumProperties())
174 addCrossOriginOwnPropertyNames<CrossOriginObject::Location>(*exec, propertyNames);
175 return;
176 }
177 Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
178}
179
180bool JSLocation::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
181{
182 JSLocation* thisObject = jsCast<JSLocation*>(object);
183 if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), ThrowSecurityError))
184 return false;
185
186 VM& vm = exec->vm();
187 if (descriptor.isAccessorDescriptor() && (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf))
188 return false;
189 return Base::defineOwnProperty(object, exec, propertyName, descriptor, throwException);
190}
191
192JSValue JSLocation::getPrototype(JSObject* object, ExecState* exec)
193{
194 JSLocation* thisObject = jsCast<JSLocation*>(object);
195 if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), DoNotReportSecurityError))
196 return jsNull();
197
198 return Base::getPrototype(object, exec);
199}
200
201bool JSLocation::preventExtensions(JSObject*, ExecState* exec)
202{
203 auto scope = DECLARE_THROW_SCOPE(exec->vm());
204
205 throwTypeError(exec, scope, "Cannot prevent extensions on this object"_s);
206 return false;
207}
208
209String JSLocation::toStringName(const JSObject* object, ExecState* exec)
210{
211 auto* thisObject = jsCast<const JSLocation*>(object);
212 if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped().window(), DoNotReportSecurityError))
213 return "Object"_s;
214 return "Location"_s;
215}
216
217bool JSLocationPrototype::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
218{
219 VM& vm = state->vm();
220 auto* thisObject = jsCast<JSLocationPrototype*>(cell);
221 if (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf)
222 return false;
223 return Base::put(thisObject, state, propertyName, value, slot);
224}
225
226bool JSLocationPrototype::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
227{
228 VM& vm = exec->vm();
229 if (descriptor.isAccessorDescriptor() && (propertyName == vm.propertyNames->toString || propertyName == vm.propertyNames->valueOf))
230 return false;
231 return Base::defineOwnProperty(object, exec, propertyName, descriptor, throwException);
232}
233
234} // namespace WebCore
235