1/*
2 * Copyright (C) 2014 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 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include "SecurityOrigin.h"
29
30namespace WebCore {
31
32template <typename T>
33class URLUtils {
34public:
35 URL href() const { return static_cast<const T*>(this)->href(); }
36 void setHref(const String& url) { static_cast<T*>(this)->setHref(url); }
37
38 String toString() const;
39 String toJSON() const;
40
41 String origin() const;
42
43 String protocol() const;
44 void setProtocol(const String&);
45
46 String username() const;
47 void setUsername(const String&);
48
49 String password() const;
50 void setPassword(const String&);
51
52 String host() const;
53 void setHost(const String&);
54
55 String hostname() const;
56 void setHostname(const String&);
57
58 String port() const;
59 void setPort(const String&);
60
61 String pathname() const;
62 void setPathname(const String&);
63
64 String search() const;
65 void setSearch(const String&);
66
67 String hash() const;
68 void setHash(const String&);
69};
70
71template <typename T>
72String URLUtils<T>::toString() const
73{
74 return href().string();
75}
76
77template <typename T>
78String URLUtils<T>::toJSON() const
79{
80 return href().string();
81}
82
83template <typename T>
84String URLUtils<T>::origin() const
85{
86 auto origin = SecurityOrigin::create(href());
87 return origin->toString();
88}
89
90template <typename T>
91String URLUtils<T>::protocol() const
92{
93 if (WTF::protocolIsJavaScript(href()))
94 return "javascript:"_s;
95 return makeString(href().protocol(), ':');
96}
97
98template <typename T>
99void URLUtils<T>::setProtocol(const String& value)
100{
101 URL url = href();
102 url.setProtocol(value);
103 setHref(url.string());
104}
105
106template <typename T>
107String URLUtils<T>::username() const
108{
109 return href().encodedUser();
110}
111
112template <typename T>
113void URLUtils<T>::setUsername(const String& user)
114{
115 URL url = href();
116 if (url.cannotBeABaseURL())
117 return;
118 url.setUser(user);
119 setHref(url);
120}
121
122template <typename T>
123String URLUtils<T>::password() const
124{
125 return href().encodedPass();
126}
127
128template <typename T>
129void URLUtils<T>::setPassword(const String& pass)
130{
131 URL url = href();
132 if (url.cannotBeABaseURL())
133 return;
134 url.setPass(pass);
135 setHref(url);
136}
137
138template <typename T>
139String URLUtils<T>::host() const
140{
141 return href().hostAndPort();
142}
143
144// This function does not allow leading spaces before the port number.
145static inline unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
146{
147 portEnd = portStart;
148 while (isASCIIDigit(value[portEnd]))
149 ++portEnd;
150 return value.substring(portStart, portEnd - portStart).toUInt();
151}
152
153template <typename T>
154void URLUtils<T>::setHost(const String& value)
155{
156 if (value.isEmpty())
157 return;
158 URL url = href();
159 if (url.cannotBeABaseURL())
160 return;
161 if (!url.canSetHostOrPort())
162 return;
163
164 size_t separator = value.find(':');
165 if (!separator)
166 return;
167
168 if (separator == notFound)
169 url.setHostAndPort(value);
170 else {
171 unsigned portEnd;
172 unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
173 if (!port) {
174 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
175 // specifically goes against RFC 3986 (p3.2) and
176 // requires setting the port to "0" if it is set to empty string.
177 url.setHostAndPort(value.substring(0, separator + 1) + '0');
178 } else {
179 if (WTF::isDefaultPortForProtocol(port, url.protocol()))
180 url.setHostAndPort(value.substring(0, separator));
181 else
182 url.setHostAndPort(value.substring(0, portEnd));
183 }
184 }
185 setHref(url.string());
186}
187
188template <typename T>
189String URLUtils<T>::hostname() const
190{
191 return href().host().toString();
192}
193
194template <typename T>
195void URLUtils<T>::setHostname(const String& value)
196{
197 // Before setting new value:
198 // Remove all leading U+002F SOLIDUS ("/") characters.
199 unsigned i = 0;
200 unsigned hostLength = value.length();
201 while (value[i] == '/')
202 i++;
203
204 if (i == hostLength)
205 return;
206
207 URL url = href();
208 if (url.cannotBeABaseURL())
209 return;
210 if (!url.canSetHostOrPort())
211 return;
212
213 url.setHost(value.substring(i));
214 setHref(url.string());
215}
216
217template <typename T>
218String URLUtils<T>::port() const
219{
220 if (href().port())
221 return String::number(href().port().value());
222
223 return emptyString();
224}
225
226template <typename T>
227void URLUtils<T>::setPort(const String& value)
228{
229 URL url = href();
230 if (url.cannotBeABaseURL() || url.protocolIs("file"))
231 return;
232 if (!url.canSetHostOrPort())
233 return;
234
235 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
236 // specifically goes against RFC 3986 (p3.2) and
237 // requires setting the port to "0" if it is set to empty string.
238 // FIXME: http://url.spec.whatwg.org/ doesn't appear to require this; test what browsers do
239 unsigned port = value.toUInt();
240 if (WTF::isDefaultPortForProtocol(port, url.protocol()))
241 url.removePort();
242 else
243 url.setPort(port);
244
245 setHref(url.string());
246}
247
248template <typename T>
249String URLUtils<T>::pathname() const
250{
251 return href().path();
252}
253
254template <typename T>
255void URLUtils<T>::setPathname(const String& value)
256{
257 URL url = href();
258 if (url.cannotBeABaseURL())
259 return;
260 if (!url.canSetPathname())
261 return;
262
263 if (value[0U] == '/')
264 url.setPath(value);
265 else
266 url.setPath("/" + value);
267
268 setHref(url.string());
269}
270
271template <typename T>
272String URLUtils<T>::search() const
273{
274 String query = href().query();
275 return query.isEmpty() ? emptyString() : "?" + query;
276}
277
278template <typename T>
279void URLUtils<T>::setSearch(const String& value)
280{
281 URL url = href();
282 if (value.isEmpty()) {
283 // If the given value is the empty string, set url's query to null.
284 url.setQuery({ });
285 } else {
286 String newSearch = (value[0U] == '?') ? value.substring(1) : value;
287 // Make sure that '#' in the query does not leak to the hash.
288 url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
289 }
290
291 setHref(url.string());
292}
293
294template <typename T>
295String URLUtils<T>::hash() const
296{
297 String fragmentIdentifier = href().fragmentIdentifier();
298 if (fragmentIdentifier.isEmpty())
299 return emptyString();
300 return AtomicString(String("#" + fragmentIdentifier));
301}
302
303template <typename T>
304void URLUtils<T>::setHash(const String& value)
305{
306 URL url = href();
307 String newFragment = value[0U] == '#' ? value.substring(1) : value;
308 if (newFragment.isEmpty())
309 url.removeFragmentIdentifier();
310 else
311 url.setFragmentIdentifier(newFragment);
312 setHref(url.string());
313}
314
315} // namespace WebCore
316