1/*
2 * Copyright (C) 2008, 2010 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 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 "Location.h"
31
32#include "DOMWindow.h"
33#include "Document.h"
34#include "Frame.h"
35#include "FrameLoader.h"
36#include "NavigationScheduler.h"
37#include <wtf/IsoMallocInlines.h>
38#include <wtf/URL.h>
39#include "SecurityOrigin.h"
40
41namespace WebCore {
42
43WTF_MAKE_ISO_ALLOCATED_IMPL(Location);
44
45Location::Location(DOMWindow& window)
46 : DOMWindowProperty(&window)
47{
48}
49
50inline const URL& Location::url() const
51{
52 if (!frame())
53 return WTF::blankURL();
54
55 const URL& url = frame()->document()->url();
56 if (!url.isValid())
57 return WTF::blankURL(); // Use "about:blank" while the page is still loading (before we have a frame).
58
59 return url;
60}
61
62String Location::href() const
63{
64 auto& url = this->url();
65
66 if (!url.hasUsername() && !url.hasPassword())
67 return url.string();
68
69 URL urlWithoutCredentials(url);
70 urlWithoutCredentials.setUser(WTF::emptyString());
71 urlWithoutCredentials.setPass(WTF::emptyString());
72 return urlWithoutCredentials.string();
73}
74
75String Location::protocol() const
76{
77 return makeString(url().protocol(), ":");
78}
79
80String Location::host() const
81{
82 // Note: this is the IE spec. The NS spec swaps the two, it says
83 // "The hostname property is the concatenation of the host and port properties, separated by a colon."
84 return url().hostAndPort();
85}
86
87String Location::hostname() const
88{
89 return url().host().toString();
90}
91
92String Location::port() const
93{
94 const URL& url = this->url();
95 return url.port() ? String::number(url.port().value()) : emptyString();
96}
97
98String Location::pathname() const
99{
100 const URL& url = this->url();
101 return url.path().isEmpty() ? "/" : url.path();
102}
103
104String Location::search() const
105{
106 const URL& url = this->url();
107 return url.query().isEmpty() ? emptyString() : "?" + url.query();
108}
109
110String Location::origin() const
111{
112 return SecurityOrigin::create(url())->toString();
113}
114
115Ref<DOMStringList> Location::ancestorOrigins() const
116{
117 auto origins = DOMStringList::create();
118 auto* frame = this->frame();
119 if (!frame)
120 return origins;
121 for (auto* ancestor = frame->tree().parent(); ancestor; ancestor = ancestor->tree().parent())
122 origins->append(ancestor->document()->securityOrigin().toString());
123 return origins;
124}
125
126String Location::hash() const
127{
128 const String& fragmentIdentifier = url().fragmentIdentifier();
129 return fragmentIdentifier.isEmpty() ? emptyString() : "#" + fragmentIdentifier;
130}
131
132ExceptionOr<void> Location::setHref(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& url)
133{
134 if (!frame())
135 return { };
136 return setLocation(activeWindow, firstWindow, url);
137}
138
139ExceptionOr<void> Location::setProtocol(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& protocol)
140{
141 auto* frame = this->frame();
142 if (!frame)
143 return { };
144 URL url = frame->document()->url();
145 if (!url.setProtocol(protocol))
146 return Exception { SyntaxError };
147 return setLocation(activeWindow, firstWindow, url.string());
148}
149
150ExceptionOr<void> Location::setHost(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& host)
151{
152 auto* frame = this->frame();
153 if (!frame)
154 return { };
155 URL url = frame->document()->url();
156 url.setHostAndPort(host);
157 return setLocation(activeWindow, firstWindow, url.string());
158}
159
160ExceptionOr<void> Location::setHostname(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& hostname)
161{
162 auto* frame = this->frame();
163 if (!frame)
164 return { };
165 URL url = frame->document()->url();
166 url.setHost(hostname);
167 return setLocation(activeWindow, firstWindow, url.string());
168}
169
170ExceptionOr<void> Location::setPort(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& portString)
171{
172 auto* frame = this->frame();
173 if (!frame)
174 return { };
175 URL url = frame->document()->url();
176 int port = portString.toInt();
177 if (port < 0 || port > 0xFFFF || portString.isEmpty())
178 url.removePort();
179 else
180 url.setPort(port);
181 return setLocation(activeWindow, firstWindow, url.string());
182}
183
184ExceptionOr<void> Location::setPathname(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& pathname)
185{
186 auto* frame = this->frame();
187 if (!frame)
188 return { };
189 URL url = frame->document()->url();
190 url.setPath(pathname);
191 return setLocation(activeWindow, firstWindow, url.string());
192}
193
194ExceptionOr<void> Location::setSearch(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& search)
195{
196 auto* frame = this->frame();
197 if (!frame)
198 return { };
199 URL url = frame->document()->url();
200 url.setQuery(search);
201 return setLocation(activeWindow, firstWindow, url.string());
202}
203
204ExceptionOr<void> Location::setHash(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& hash)
205{
206 auto* frame = this->frame();
207 if (!frame)
208 return { };
209 ASSERT(frame->document());
210 auto url = frame->document()->url();
211 auto oldFragmentIdentifier = url.fragmentIdentifier();
212 auto newFragmentIdentifier = hash;
213 if (hash[0] == '#')
214 newFragmentIdentifier = hash.substring(1);
215 url.setFragmentIdentifier(newFragmentIdentifier);
216 // Note that by parsing the URL and *then* comparing fragments, we are
217 // comparing fragments post-canonicalization, and so this handles the
218 // cases where fragment identifiers are ignored or invalid.
219 if (equalIgnoringNullity(oldFragmentIdentifier, url.fragmentIdentifier()))
220 return { };
221 return setLocation(activeWindow, firstWindow, url.string());
222}
223
224ExceptionOr<void> Location::assign(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& url)
225{
226 if (!frame())
227 return { };
228 return setLocation(activeWindow, firstWindow, url);
229}
230
231void Location::replace(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& urlString)
232{
233 auto* frame = this->frame();
234 if (!frame)
235 return;
236 ASSERT(frame->document());
237 ASSERT(frame->document()->domWindow());
238
239 Frame* firstFrame = firstWindow.frame();
240 if (!firstFrame || !firstFrame->document())
241 return;
242
243 URL completedURL = firstFrame->document()->completeURL(urlString);
244 // FIXME: The specification says to throw a SyntaxError if the URL is not valid.
245 if (completedURL.isNull())
246 return;
247
248 // We call DOMWindow::setLocation directly here because replace() always operates on the current frame.
249 frame->document()->domWindow()->setLocation(activeWindow, completedURL, LockHistoryAndBackForwardList);
250}
251
252void Location::reload(DOMWindow& activeWindow)
253{
254 auto* frame = this->frame();
255 if (!frame)
256 return;
257
258 ASSERT(activeWindow.document());
259 ASSERT(frame->document());
260 ASSERT(frame->document()->domWindow());
261
262 auto& activeDocument = *activeWindow.document();
263 auto& targetDocument = *frame->document();
264
265 // FIXME: It's not clear this cross-origin security check is valuable.
266 // We allow one page to change the location of another. Why block attempts to reload?
267 // Other location operations simply block use of JavaScript URLs cross origin.
268 if (!activeDocument.securityOrigin().canAccess(targetDocument.securityOrigin())) {
269 auto& targetWindow = *targetDocument.domWindow();
270 targetWindow.printErrorMessage(targetWindow.crossDomainAccessErrorMessage(activeWindow, IncludeTargetOrigin::Yes));
271 return;
272 }
273
274 if (WTF::protocolIsJavaScript(targetDocument.url()))
275 return;
276
277 frame->navigationScheduler().scheduleRefresh(activeDocument);
278}
279
280ExceptionOr<void> Location::setLocation(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& urlString)
281{
282 auto* frame = this->frame();
283 ASSERT(frame);
284
285 Frame* firstFrame = firstWindow.frame();
286 if (!firstFrame || !firstFrame->document())
287 return { };
288
289 URL completedURL = firstFrame->document()->completeURL(urlString);
290 // FIXME: The specification says to throw a SyntaxError if the URL is not valid.
291 if (completedURL.isNull())
292 return { };
293
294 if (!activeWindow.document()->canNavigate(frame, completedURL))
295 return Exception { SecurityError };
296
297 ASSERT(frame->document());
298 ASSERT(frame->document()->domWindow());
299 frame->document()->domWindow()->setLocation(activeWindow, completedURL);
300 return { };
301}
302
303} // namespace WebCore
304