diff options
Diffstat (limited to 'Source/WebCore/page/History.cpp')
-rw-r--r-- | Source/WebCore/page/History.cpp | 141 |
1 files changed, 101 insertions, 40 deletions
diff --git a/Source/WebCore/page/History.cpp b/Source/WebCore/page/History.cpp index 5b5539cb5..fbc52f3c9 100644 --- a/Source/WebCore/page/History.cpp +++ b/Source/WebCore/page/History.cpp @@ -10,10 +10,10 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR @@ -34,16 +34,18 @@ #include "FrameLoaderClient.h" #include "HistoryController.h" #include "HistoryItem.h" +#include "Logging.h" +#include "MainFrame.h" #include "Page.h" +#include "ScriptController.h" #include "SecurityOrigin.h" -#include "SerializedScriptValue.h" +#include <wtf/CheckedArithmetic.h> #include <wtf/MainThread.h> namespace WebCore { -History::History(Frame* frame) - : DOMWindowProperty(frame) - , m_lastStateObjectRequested(0) +History::History(Frame& frame) + : DOMWindowProperty(&frame) { } @@ -51,26 +53,26 @@ unsigned History::length() const { if (!m_frame) return 0; - if (!m_frame->page()) + auto* page = m_frame->page(); + if (!page) return 0; - return m_frame->page()->backForward().count(); + return page->backForward().count(); } -PassRefPtr<SerializedScriptValue> History::state() +SerializedScriptValue* History::state() { m_lastStateObjectRequested = stateInternal(); - return m_lastStateObjectRequested; + return m_lastStateObjectRequested.get(); } -PassRefPtr<SerializedScriptValue> History::stateInternal() const +SerializedScriptValue* History::stateInternal() const { if (!m_frame) - return 0; - - if (HistoryItem* historyItem = m_frame->loader().history().currentItem()) - return historyItem->stateObject(); - - return 0; + return nullptr; + auto* historyItem = m_frame->loader().history().currentItem(); + if (!historyItem) + return nullptr; + return historyItem->stateObject(); } bool History::stateChanged() const @@ -80,7 +82,7 @@ bool History::stateChanged() const bool History::isSameAsCurrentState(SerializedScriptValue* state) const { - return state == stateInternal().get(); + return state == stateInternal(); } void History::back() @@ -88,9 +90,9 @@ void History::back() go(-1); } -void History::back(ScriptExecutionContext* context) +void History::back(Document& document) { - go(context, -1); + go(document, -1); } void History::forward() @@ -98,30 +100,31 @@ void History::forward() go(1); } -void History::forward(ScriptExecutionContext* context) +void History::forward(Document& document) { - go(context, 1); + go(document, 1); } void History::go(int distance) { + LOG(History, "History %p go(%d) frame %p (main frame %d)", this, distance, m_frame, m_frame ? m_frame->isMainFrame() : false); + if (!m_frame) return; m_frame->navigationScheduler().scheduleHistoryNavigation(distance); } -void History::go(ScriptExecutionContext* context, int distance) +void History::go(Document& document, int distance) { + 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); + if (!m_frame) return; ASSERT(isMainThread()); - Document* activeDocument = toDocument(context); - if (!activeDocument) - return; - if (!activeDocument->canNavigate(m_frame)) + if (!document.canNavigate(m_frame)) return; m_frame->navigationScheduler().scheduleHistoryNavigation(distance); @@ -136,29 +139,87 @@ URL History::urlForState(const String& urlString) return URL(baseURL, urlString); } -void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const String& title, const String& urlString, StateObjectType stateObjectType, ExceptionCode& ec) +ExceptionOr<void> History::stateObjectAdded(RefPtr<SerializedScriptValue>&& data, const String& title, const String& urlString, StateObjectType stateObjectType) { + // Each unique main-frame document is only allowed to send 64MB of state object payload to the UI client/process. + static uint32_t totalStateObjectPayloadLimit = 0x4000000; + static double stateObjectTimeSpan = 30.0; + static unsigned perStateObjectTimeSpanLimit = 100; + if (!m_frame || !m_frame->page()) - return; - + return { }; + URL fullURL = urlForState(urlString); - if (!fullURL.isValid() || !m_frame->document()->securityOrigin()->canRequest(fullURL)) { - ec = SECURITY_ERR; - return; + if (!fullURL.isValid() || !m_frame->document()->securityOrigin().canRequest(fullURL)) + return Exception { SECURITY_ERR }; + + if (fullURL.hasUsername() || fullURL.hasPassword()) { + if (stateObjectType == StateObjectType::Replace) + return Exception { SECURITY_ERR, "Attempt to use history.replaceState() to change session history URL to " + fullURL.string() + " is insecure; Username/passwords aren't allowed in state object URLs" }; + return Exception { SECURITY_ERR, "Attempt to use history.pushState() to add URL " + fullURL.string() + " to session history is insecure; Username/passwords aren't allowed in state object URLs" }; + } + + Document* mainDocument = m_frame->page()->mainFrame().document(); + History* mainHistory = nullptr; + if (mainDocument) { + if (auto* mainDOMWindow = mainDocument->domWindow()) + mainHistory = mainDOMWindow->history(); + } + + if (!mainHistory) + return { }; + + double currentTimestamp = currentTime(); + if (currentTimestamp - mainHistory->m_currentStateObjectTimeSpanStart > stateObjectTimeSpan) { + mainHistory->m_currentStateObjectTimeSpanStart = currentTimestamp; + mainHistory->m_currentStateObjectTimeSpanObjectsAdded = 0; + } + + if (mainHistory->m_currentStateObjectTimeSpanObjectsAdded >= perStateObjectTimeSpanLimit) { + if (stateObjectType == StateObjectType::Replace) + return Exception { SECURITY_ERR, String::format("Attempt to use history.replaceState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan) }; + return Exception { SECURITY_ERR, String::format("Attempt to use history.pushState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan) }; } - if (stateObjectType == StateObjectType::Push) - m_frame->loader().history().pushState(data, title, fullURL.string()); - else if (stateObjectType == StateObjectType::Replace) - m_frame->loader().history().replaceState(data, title, fullURL.string()); - + Checked<unsigned> titleSize = title.length(); + titleSize *= 2; + + Checked<unsigned> urlSize = fullURL.string().length(); + urlSize *= 2; + + Checked<uint64_t> payloadSize = titleSize; + payloadSize += urlSize; + payloadSize += data ? data->data().size() : 0; + + Checked<uint64_t> newTotalUsage = mainHistory->m_totalStateObjectUsage; + + if (stateObjectType == StateObjectType::Replace) + newTotalUsage -= m_mostRecentStateObjectUsage; + newTotalUsage += payloadSize; + + if (newTotalUsage > totalStateObjectPayloadLimit) { + if (stateObjectType == StateObjectType::Replace) + return Exception { QUOTA_EXCEEDED_ERR, ASCIILiteral("Attempt to store more data than allowed using history.replaceState()") }; + return Exception { QUOTA_EXCEEDED_ERR, ASCIILiteral("Attempt to store more data than allowed using history.pushState()") }; + } + + m_mostRecentStateObjectUsage = payloadSize.unsafeGet(); + + mainHistory->m_totalStateObjectUsage = newTotalUsage.unsafeGet(); + ++mainHistory->m_currentStateObjectTimeSpanObjectsAdded; + if (!urlString.isEmpty()) m_frame->document()->updateURLForPushOrReplaceState(fullURL); - if (stateObjectType == StateObjectType::Push) + if (stateObjectType == StateObjectType::Push) { + m_frame->loader().history().pushState(WTFMove(data), title, fullURL.string()); m_frame->loader().client().dispatchDidPushStateWithinPage(); - else if (stateObjectType == StateObjectType::Replace) + } else if (stateObjectType == StateObjectType::Replace) { + m_frame->loader().history().replaceState(WTFMove(data), title, fullURL.string()); m_frame->loader().client().dispatchDidReplaceStateWithinPage(); + } + + return { }; } } // namespace WebCore |