Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / page / History.cpp
1 /*
2  * Copyright (C) 2007-2018 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "History.h"
28
29 #include "BackForwardController.h"
30 #include "Document.h"
31 #include "Frame.h"
32 #include "FrameLoader.h"
33 #include "FrameLoaderClient.h"
34 #include "HistoryController.h"
35 #include "HistoryItem.h"
36 #include "Logging.h"
37 #include "NavigationScheduler.h"
38 #include "Page.h"
39 #include "ScriptController.h"
40 #include "SecurityOrigin.h"
41 #include <wtf/CheckedArithmetic.h>
42 #include <wtf/MainThread.h>
43
44 namespace WebCore {
45
46 History::History(Frame& frame)
47     : DOMWindowProperty(&frame)
48 {
49 }
50
51 unsigned History::length() const
52 {
53     if (!m_frame)
54         return 0;
55     auto* page = m_frame->page();
56     if (!page)
57         return 0;
58     return page->backForward().count();
59 }
60
61 ExceptionOr<History::ScrollRestoration> History::scrollRestoration() const
62 {
63     if (!m_frame)
64         return Exception { SecurityError };
65
66     auto* historyItem = m_frame->loader().history().currentItem();
67     if (!historyItem)
68         return ScrollRestoration::Auto;
69     
70     return historyItem->shouldRestoreScrollPosition() ? ScrollRestoration::Auto : ScrollRestoration::Manual;
71 }
72
73 ExceptionOr<void> History::setScrollRestoration(ScrollRestoration scrollRestoration)
74 {
75     if (!m_frame)
76         return Exception { SecurityError };
77
78     auto* historyItem = m_frame->loader().history().currentItem();
79     if (historyItem)
80         historyItem->setShouldRestoreScrollPosition(scrollRestoration == ScrollRestoration::Auto);
81
82     return { };
83 }
84
85 SerializedScriptValue* History::state()
86 {
87     m_lastStateObjectRequested = stateInternal();
88     return m_lastStateObjectRequested.get();
89 }
90
91 SerializedScriptValue* History::stateInternal() const
92 {
93     if (!m_frame)
94         return nullptr;
95     auto* historyItem = m_frame->loader().history().currentItem();
96     if (!historyItem)
97         return nullptr;
98     return historyItem->stateObject();
99 }
100
101 bool History::stateChanged() const
102 {
103     return m_lastStateObjectRequested != stateInternal();
104 }
105
106 JSValueInWrappedObject& History::cachedState()
107 {
108     if (m_cachedState && stateChanged())
109         m_cachedState = { };
110     return m_cachedState;
111 }
112
113 bool History::isSameAsCurrentState(SerializedScriptValue* state) const
114 {
115     return state == stateInternal();
116 }
117
118 void History::back()
119 {
120     go(-1);
121 }
122
123 void History::back(Document& document)
124 {
125     go(document, -1);
126 }
127
128 void History::forward()
129 {
130     go(1);
131 }
132
133 void History::forward(Document& document)
134 {
135     go(document, 1);
136 }
137
138 void History::go(int distance)
139 {
140     LOG(History, "History %p go(%d) frame %p (main frame %d)", this, distance, m_frame, m_frame ? m_frame->isMainFrame() : false);
141
142     if (!m_frame)
143         return;
144
145     m_frame->navigationScheduler().scheduleHistoryNavigation(distance);
146 }
147
148 void History::go(Document& document, int distance)
149 {
150     LOG(History, "History %p go(%d) in document %p frame %p (main frame %d)", this, distance, &document, m_frame, m_frame ? m_frame->isMainFrame() : false);
151
152     if (!m_frame)
153         return;
154
155     ASSERT(isMainThread());
156
157     if (!document.canNavigate(m_frame))
158         return;
159
160     m_frame->navigationScheduler().scheduleHistoryNavigation(distance);
161 }
162
163 URL History::urlForState(const String& urlString)
164 {
165     if (urlString.isNull())
166         return m_frame->document()->url();
167     return m_frame->document()->completeURL(urlString);
168 }
169
170 ExceptionOr<void> History::stateObjectAdded(RefPtr<SerializedScriptValue>&& data, const String& title, const String& urlString, StateObjectType stateObjectType)
171 {
172     m_cachedState = { };
173
174     // Each unique main-frame document is only allowed to send 64MB of state object payload to the UI client/process.
175     static uint32_t totalStateObjectPayloadLimit = 0x4000000;
176     static Seconds stateObjectTimeSpan { 30_s };
177     static unsigned perStateObjectTimeSpanLimit = 100;
178
179     if (!m_frame || !m_frame->page())
180         return { };
181
182     URL fullURL = urlForState(urlString);
183     if (!fullURL.isValid())
184         return Exception { SecurityError };
185
186     const URL& documentURL = m_frame->document()->url();
187
188     auto createBlockedURLSecurityErrorWithMessageSuffix = [&] (const char* suffix) {
189         const char* functionName = stateObjectType == StateObjectType::Replace ? "history.replaceState()" : "history.pushState()";
190         return Exception { SecurityError, makeString("Blocked attempt to use ", functionName, " to change session history URL from ", documentURL.stringCenterEllipsizedToLength(), " to ", fullURL.stringCenterEllipsizedToLength(), ". ", suffix) };
191     };
192     if (!protocolHostAndPortAreEqual(fullURL, documentURL) || fullURL.user() != documentURL.user() || fullURL.pass() != documentURL.pass())
193         return createBlockedURLSecurityErrorWithMessageSuffix("Protocols, domains, ports, usernames, and passwords must match.");
194
195     const auto& documentSecurityOrigin = m_frame->document()->securityOrigin();
196     // We allow sandboxed documents, 'data:'/'file:' URLs, etc. to use 'pushState'/'replaceState' to modify the URL query and fragments.
197     // See https://bugs.webkit.org/show_bug.cgi?id=183028 for the compatibility concerns.
198     bool allowSandboxException = (documentSecurityOrigin.isLocal() || documentSecurityOrigin.isUnique()) && equalIgnoringQueryAndFragment(documentURL, fullURL);
199
200     if (!allowSandboxException && !documentSecurityOrigin.canRequest(fullURL) && (fullURL.path() != documentURL.path() || fullURL.query() != documentURL.query()))
201         return createBlockedURLSecurityErrorWithMessageSuffix("Paths and fragments must match for a sandboxed document.");
202
203     Document* mainDocument = m_frame->page()->mainFrame().document();
204     History* mainHistory = nullptr;
205     if (mainDocument) {
206         if (auto* mainDOMWindow = mainDocument->domWindow())
207             mainHistory = mainDOMWindow->history();
208     }
209
210     if (!mainHistory)
211         return { };
212
213     WallTime currentTimestamp = WallTime::now();
214     if (currentTimestamp - mainHistory->m_currentStateObjectTimeSpanStart > stateObjectTimeSpan) {
215         mainHistory->m_currentStateObjectTimeSpanStart = currentTimestamp;
216         mainHistory->m_currentStateObjectTimeSpanObjectsAdded = 0;
217     }
218     
219     if (mainHistory->m_currentStateObjectTimeSpanObjectsAdded >= perStateObjectTimeSpanLimit) {
220         if (stateObjectType == StateObjectType::Replace)
221             return Exception { SecurityError, String::format("Attempt to use history.replaceState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan.seconds()) };
222         return Exception { SecurityError, String::format("Attempt to use history.pushState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan.seconds()) };
223     }
224
225     Checked<unsigned> titleSize = title.length();
226     titleSize *= 2;
227
228     Checked<unsigned> urlSize = fullURL.string().length();
229     urlSize *= 2;
230
231     Checked<uint64_t> payloadSize = titleSize;
232     payloadSize += urlSize;
233     payloadSize += data ? data->data().size() : 0;
234
235     Checked<uint64_t> newTotalUsage = mainHistory->m_totalStateObjectUsage;
236
237     if (stateObjectType == StateObjectType::Replace)
238         newTotalUsage -= m_mostRecentStateObjectUsage;
239     newTotalUsage += payloadSize;
240
241     if (newTotalUsage > totalStateObjectPayloadLimit) {
242         if (stateObjectType == StateObjectType::Replace)
243             return Exception { QuotaExceededError, "Attempt to store more data than allowed using history.replaceState()"_s };
244         return Exception { QuotaExceededError, "Attempt to store more data than allowed using history.pushState()"_s };
245     }
246
247     m_mostRecentStateObjectUsage = payloadSize.unsafeGet();
248
249     mainHistory->m_totalStateObjectUsage = newTotalUsage.unsafeGet();
250     ++mainHistory->m_currentStateObjectTimeSpanObjectsAdded;
251
252     if (!urlString.isEmpty())
253         m_frame->document()->updateURLForPushOrReplaceState(fullURL);
254
255     if (stateObjectType == StateObjectType::Push) {
256         m_frame->loader().history().pushState(WTFMove(data), title, fullURL.string());
257         m_frame->loader().client().dispatchDidPushStateWithinPage();
258     } else if (stateObjectType == StateObjectType::Replace) {
259         m_frame->loader().history().replaceState(WTFMove(data), title, fullURL.string());
260         m_frame->loader().client().dispatchDidReplaceStateWithinPage();
261     }
262
263     return { };
264 }
265
266 } // namespace WebCore