WebKit should prevent push/replace state with username in URL.
[WebKit-https.git] / Source / WebCore / page / History.cpp
1 /*
2  * Copyright (C) 2007 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 "ExceptionCode.h"
32 #include "Frame.h"
33 #include "FrameLoader.h"
34 #include "FrameLoaderClient.h"
35 #include "HistoryController.h"
36 #include "HistoryItem.h"
37 #include "MainFrame.h"
38 #include "Page.h"
39 #include "ScriptController.h"
40 #include "SecurityOrigin.h"
41 #include "SerializedScriptValue.h"
42 #include <wtf/CheckedArithmetic.h>
43 #include <wtf/MainThread.h>
44
45 namespace WebCore {
46
47 History::History(Frame* frame)
48     : DOMWindowProperty(frame)
49     , m_lastStateObjectRequested(nullptr)
50 {
51 }
52
53 unsigned History::length() const
54 {
55     if (!m_frame)
56         return 0;
57     if (!m_frame->page())
58         return 0;
59     return m_frame->page()->backForward().count();
60 }
61
62 PassRefPtr<SerializedScriptValue> History::state()
63 {
64     m_lastStateObjectRequested = stateInternal();
65     return m_lastStateObjectRequested;
66 }
67
68 PassRefPtr<SerializedScriptValue> History::stateInternal() const
69 {
70     if (!m_frame)
71         return 0;
72
73     if (HistoryItem* historyItem = m_frame->loader().history().currentItem())
74         return historyItem->stateObject();
75
76     return 0;
77 }
78
79 bool History::stateChanged() const
80 {
81     return m_lastStateObjectRequested != stateInternal();
82 }
83
84 bool History::isSameAsCurrentState(SerializedScriptValue* state) const
85 {
86     return state == stateInternal().get();
87 }
88
89 void History::back()
90 {
91     go(-1);
92 }
93
94 void History::back(Document& document)
95 {
96     go(document, -1);
97 }
98
99 void History::forward()
100 {
101     go(1);
102 }
103
104 void History::forward(Document& document)
105 {
106     go(document, 1);
107 }
108
109 void History::go(int distance)
110 {
111     if (!m_frame)
112         return;
113
114     m_frame->navigationScheduler().scheduleHistoryNavigation(distance);
115 }
116
117 void History::go(Document& document, int distance)
118 {
119     if (!m_frame)
120         return;
121
122     ASSERT(isMainThread());
123
124     if (!document.canNavigate(m_frame))
125         return;
126
127     m_frame->navigationScheduler().scheduleHistoryNavigation(distance);
128 }
129
130 URL History::urlForState(const String& urlString)
131 {
132     URL baseURL = m_frame->document()->baseURL();
133     if (urlString.isEmpty())
134         return baseURL;
135
136     return URL(baseURL, urlString);
137 }
138
139 void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const String& title, const String& urlString, StateObjectType stateObjectType, ExceptionCodeWithMessage& ec)
140 {
141     // Each unique main-frame document is only allowed to send 64mb of state object payload to the UI client/process.
142     static uint32_t totalStateObjectPayloadLimit = 0x4000000;
143     static double stateObjectTimeSpan = 30.0;
144     static unsigned perStateObjectTimeSpanLimit = 100;
145
146     if (!m_frame || !m_frame->page())
147         return;
148
149     URL fullURL = urlForState(urlString);
150     if (!fullURL.isValid() || !m_frame->document()->securityOrigin()->canRequest(fullURL)) {
151         ec.code = SECURITY_ERR;
152         return;
153     }
154
155     if (fullURL.hasUsername() || fullURL.hasPassword()) {
156         ec.code = SECURITY_ERR;
157         if (stateObjectType == StateObjectType::Replace)
158             ec.message = makeString("Attempt to use history.replaceState() to change session history URL to ", fullURL.string(), " is insecure; Username/passwords aren't allowed in state object URLs");
159         else
160             ec.message = makeString("Attempt to use history.pushState() to add URL ", fullURL.string(), " to session history is insecure; Username/passwords aren't allowed in state object URLs");
161         return;
162     }
163
164     Document* mainDocument = m_frame->page()->mainFrame().document();
165     History* mainHistory = nullptr;
166     if (mainDocument) {
167         if (auto* mainDOMWindow = mainDocument->domWindow())
168             mainHistory = mainDOMWindow->history();
169     }
170
171     if (!mainHistory)
172         return;
173
174     double currentTimestamp = currentTime();
175     if (currentTimestamp - mainHistory->m_currentStateObjectTimeSpanStart > stateObjectTimeSpan) {
176         mainHistory->m_currentStateObjectTimeSpanStart = currentTimestamp;
177         mainHistory->m_currentStateObjectTimeSpanObjectsAdded = 0;
178     }
179     
180     if (mainHistory->m_currentStateObjectTimeSpanObjectsAdded >= perStateObjectTimeSpanLimit) {
181         ec.code = SECURITY_ERR;
182         if (stateObjectType == StateObjectType::Replace)
183             ec.message = String::format("Attempt to use history.replaceState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan);
184         else
185             ec.message = String::format("Attempt to use history.pushState() more than %u times per %f seconds", perStateObjectTimeSpanLimit, stateObjectTimeSpan);
186         return;
187     }
188
189     Checked<unsigned> titleSize = title.length();
190     titleSize *= 2;
191
192     Checked<unsigned> urlSize = fullURL.string().length();
193     urlSize *= 2;
194
195     Checked<uint64_t> payloadSize = titleSize;
196     payloadSize += urlSize;
197     payloadSize += data ? data->data().size() : 0;
198
199     Checked<uint64_t> newTotalUsage = mainHistory->m_totalStateObjectUsage;
200
201     if (stateObjectType == StateObjectType::Replace)
202         newTotalUsage -= m_mostRecentStateObjectUsage;
203     newTotalUsage += payloadSize;
204
205     if (newTotalUsage > totalStateObjectPayloadLimit) {
206         ec.code = QUOTA_EXCEEDED_ERR;
207         if (stateObjectType == StateObjectType::Replace)
208             ec.message = ASCIILiteral("Attempt to store more data than allowed using history.replaceState()");
209         else
210             ec.message = ASCIILiteral("Attempt to store more data than allowed using history.pushState()");
211         return;
212     }
213
214     m_mostRecentStateObjectUsage = payloadSize.unsafeGet();
215
216     mainHistory->m_totalStateObjectUsage = newTotalUsage.unsafeGet();
217     ++mainHistory->m_currentStateObjectTimeSpanObjectsAdded;
218
219     if (!urlString.isEmpty())
220         m_frame->document()->updateURLForPushOrReplaceState(fullURL);
221
222     if (stateObjectType == StateObjectType::Push) {
223         m_frame->loader().history().pushState(data, title, fullURL.string());
224         m_frame->loader().client().dispatchDidPushStateWithinPage();
225     } else if (stateObjectType == StateObjectType::Replace) {
226         m_frame->loader().history().replaceState(data, title, fullURL.string());
227         m_frame->loader().client().dispatchDidReplaceStateWithinPage();
228     }
229 }
230
231 } // namespace WebCore