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 | |
30 | namespace WebCore { |
31 | |
32 | template <typename T> |
33 | class URLUtils { |
34 | public: |
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 | |
71 | template <typename T> |
72 | String URLUtils<T>::toString() const |
73 | { |
74 | return href().string(); |
75 | } |
76 | |
77 | template <typename T> |
78 | String URLUtils<T>::toJSON() const |
79 | { |
80 | return href().string(); |
81 | } |
82 | |
83 | template <typename T> |
84 | String URLUtils<T>::origin() const |
85 | { |
86 | auto origin = SecurityOrigin::create(href()); |
87 | return origin->toString(); |
88 | } |
89 | |
90 | template <typename T> |
91 | String URLUtils<T>::protocol() const |
92 | { |
93 | if (WTF::protocolIsJavaScript(href())) |
94 | return "javascript:"_s ; |
95 | return makeString(href().protocol(), ':'); |
96 | } |
97 | |
98 | template <typename T> |
99 | void URLUtils<T>::setProtocol(const String& value) |
100 | { |
101 | URL url = href(); |
102 | url.setProtocol(value); |
103 | setHref(url.string()); |
104 | } |
105 | |
106 | template <typename T> |
107 | String URLUtils<T>::username() const |
108 | { |
109 | return href().encodedUser(); |
110 | } |
111 | |
112 | template <typename T> |
113 | void 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 | |
122 | template <typename T> |
123 | String URLUtils<T>::password() const |
124 | { |
125 | return href().encodedPass(); |
126 | } |
127 | |
128 | template <typename T> |
129 | void 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 | |
138 | template <typename T> |
139 | String URLUtils<T>::host() const |
140 | { |
141 | return href().hostAndPort(); |
142 | } |
143 | |
144 | // This function does not allow leading spaces before the port number. |
145 | static 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 | |
153 | template <typename T> |
154 | void 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 | |
188 | template <typename T> |
189 | String URLUtils<T>::hostname() const |
190 | { |
191 | return href().host().toString(); |
192 | } |
193 | |
194 | template <typename T> |
195 | void 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 | |
217 | template <typename T> |
218 | String URLUtils<T>::port() const |
219 | { |
220 | if (href().port()) |
221 | return String::number(href().port().value()); |
222 | |
223 | return emptyString(); |
224 | } |
225 | |
226 | template <typename T> |
227 | void 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 | |
248 | template <typename T> |
249 | String URLUtils<T>::pathname() const |
250 | { |
251 | return href().path(); |
252 | } |
253 | |
254 | template <typename T> |
255 | void 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 | |
271 | template <typename T> |
272 | String URLUtils<T>::search() const |
273 | { |
274 | String query = href().query(); |
275 | return query.isEmpty() ? emptyString() : "?" + query; |
276 | } |
277 | |
278 | template <typename T> |
279 | void 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 | |
294 | template <typename T> |
295 | String URLUtils<T>::hash() const |
296 | { |
297 | String fragmentIdentifier = href().fragmentIdentifier(); |
298 | if (fragmentIdentifier.isEmpty()) |
299 | return emptyString(); |
300 | return AtomicString(String("#" + fragmentIdentifier)); |
301 | } |
302 | |
303 | template <typename T> |
304 | void 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 | |