1/*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "JSHistoryCustom.h"
31
32#include "Frame.h"
33#include "History.h"
34#include <runtime/JSFunction.h>
35
36using namespace JSC;
37
38namespace WebCore {
39
40static JSValue nonCachingStaticBackFunctionGetter(ExecState* exec, JSValue, const Identifier& propertyName)
41{
42    return new (exec) JSFunction(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->functionStructure(), 0, propertyName, jsHistoryPrototypeFunctionBack);
43}
44
45static JSValue nonCachingStaticForwardFunctionGetter(ExecState* exec, JSValue, const Identifier& propertyName)
46{
47    return new (exec) JSFunction(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->functionStructure(), 0, propertyName, jsHistoryPrototypeFunctionForward);
48}
49
50static JSValue nonCachingStaticGoFunctionGetter(ExecState* exec, JSValue, const Identifier& propertyName)
51{
52    return new (exec) JSFunction(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->functionStructure(), 1, propertyName, jsHistoryPrototypeFunctionGo);
53}
54
55bool JSHistory::getOwnPropertySlotDelegate(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
56{
57    // When accessing History cross-domain, functions are always the native built-in ones.
58    // See JSDOMWindow::getOwnPropertySlotDelegate for additional details.
59
60    // Our custom code is only needed to implement the Window cross-domain scheme, so if access is
61    // allowed, return false so the normal lookup will take place.
62    String message;
63    if (allowsAccessFromFrame(exec, impl()->frame(), message))
64        return false;
65
66    // Check for the few functions that we allow, even when called cross-domain.
67    const HashEntry* entry = JSHistoryPrototype::s_info.propHashTable(exec)->entry(exec, propertyName);
68    if (entry) {
69        // Allow access to back(), forward() and go() from any frame.
70        if (entry->attributes() & Function) {
71            if (entry->function() == jsHistoryPrototypeFunctionBack) {
72                slot.setCustom(this, nonCachingStaticBackFunctionGetter);
73                return true;
74            } else if (entry->function() == jsHistoryPrototypeFunctionForward) {
75                slot.setCustom(this, nonCachingStaticForwardFunctionGetter);
76                return true;
77            } else if (entry->function() == jsHistoryPrototypeFunctionGo) {
78                slot.setCustom(this, nonCachingStaticGoFunctionGetter);
79                return true;
80            }
81        }
82    } else {
83        // Allow access to toString() cross-domain, but always Object.toString.
84        if (propertyName == exec->propertyNames().toString) {
85            slot.setCustom(this, objectToStringFunctionGetter);
86            return true;
87        }
88    }
89
90    printErrorMessageForFrame(impl()->frame(), message);
91    slot.setUndefined();
92    return true;
93}
94
95bool JSHistory::getOwnPropertyDescriptorDelegate(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
96{
97    if (!impl()->frame()) {
98        descriptor.setUndefined();
99        return true;
100    }
101
102    // Throw out all cross domain access
103    if (!allowsAccessFromFrame(exec, impl()->frame()))
104        return true;
105
106    // Check for the few functions that we allow, even when called cross-domain.
107    const HashEntry* entry = JSHistoryPrototype::s_info.propHashTable(exec)->entry(exec, propertyName);
108    if (entry) {
109        PropertySlot slot;
110        // Allow access to back(), forward() and go() from any frame.
111        if (entry->attributes() & Function) {
112            if (entry->function() == jsHistoryPrototypeFunctionBack) {
113                slot.setCustom(this, nonCachingStaticBackFunctionGetter);
114                descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
115                return true;
116            } else if (entry->function() == jsHistoryPrototypeFunctionForward) {
117                slot.setCustom(this, nonCachingStaticForwardFunctionGetter);
118                descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
119                return true;
120            } else if (entry->function() == jsHistoryPrototypeFunctionGo) {
121                slot.setCustom(this, nonCachingStaticGoFunctionGetter);
122                descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
123                return true;
124            }
125        }
126    } else {
127        // Allow access to toString() cross-domain, but always Object.toString.
128        if (propertyName == exec->propertyNames().toString) {
129            PropertySlot slot;
130            slot.setCustom(this, objectToStringFunctionGetter);
131            descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
132            return true;
133        }
134    }
135
136    descriptor.setUndefined();
137    return true;
138}
139
140bool JSHistory::putDelegate(ExecState* exec, const Identifier&, JSValue, PutPropertySlot&)
141{
142    // Only allow putting by frames in the same origin.
143    if (!allowsAccessFromFrame(exec, impl()->frame()))
144        return true;
145    return false;
146}
147
148bool JSHistory::deleteProperty(ExecState* exec, const Identifier& propertyName)
149{
150    // Only allow deleting by frames in the same origin.
151    if (!allowsAccessFromFrame(exec, impl()->frame()))
152        return false;
153    return Base::deleteProperty(exec, propertyName);
154}
155
156void JSHistory::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
157{
158    // Only allow the history object to enumerated by frames in the same origin.
159    if (!allowsAccessFromFrame(exec, impl()->frame()))
160        return;
161    Base::getOwnPropertyNames(exec, propertyNames, mode);
162}
163
164JSValue JSHistory::pushState(ExecState* exec)
165{
166    RefPtr<SerializedScriptValue> historyState = SerializedScriptValue::create(exec, exec->argument(0));
167    if (exec->hadException())
168        return jsUndefined();
169
170    String title = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(1));
171    if (exec->hadException())
172        return jsUndefined();
173
174    String url;
175    if (exec->argumentCount() > 2) {
176        url = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(2));
177        if (exec->hadException())
178            return jsUndefined();
179    }
180
181    ExceptionCode ec = 0;
182    impl()->stateObjectAdded(historyState.release(), title, url, History::StateObjectPush, ec);
183    setDOMException(exec, ec);
184
185    return jsUndefined();
186}
187
188JSValue JSHistory::replaceState(ExecState* exec)
189{
190    RefPtr<SerializedScriptValue> historyState = SerializedScriptValue::create(exec, exec->argument(0));
191    if (exec->hadException())
192        return jsUndefined();
193
194    String title = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(1));
195    if (exec->hadException())
196        return jsUndefined();
197
198    String url;
199    if (exec->argumentCount() > 2) {
200        url = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(2));
201        if (exec->hadException())
202            return jsUndefined();
203    }
204
205    ExceptionCode ec = 0;
206    impl()->stateObjectAdded(historyState.release(), title, url, History::StateObjectReplace, ec);
207    setDOMException(exec, ec);
208
209    return jsUndefined();
210}
211
212} // namespace WebCore
213