Line data Source code
1 : /**
2 : Copyright (c) 2020-2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPUrl.h"
25 : #include "SPSearchConfiguration.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::search {
28 :
29 : using Scheme = chars::Compose<char, chars::CharGroup<char, CharGroupId::Alphanumeric>, chars::Chars<char, '+', '-', '.'>>;
30 : using Ipv6 = chars::Compose<char, chars::CharGroup<char, CharGroupId::Hexadecimial>, chars::Chars<char, ':'>>;
31 :
32 : using Unreserved = chars::Compose<char, chars::CharGroup<char, CharGroupId::Alphanumeric>, chars::Chars<char, '-', '.', '_', '~', '%'>>;
33 : using SubDelim = chars::Chars<char, '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='>;
34 : using GenDelim = chars::Chars<char, ':', '/', '?', '#', '[', ']', '@'>;
35 :
36 : using UnreservedUni = chars::Compose<char, Unreserved, chars::UniChar>;
37 :
38 450 : static bool validateScheme(const StringView &r) {
39 450 : auto cpy = r;
40 450 : if (cpy.is<StringView::Compose<StringView::CharGroup<CharGroupId::Alphanumeric>, StringView::Chars<'.'>>>()) {
41 450 : cpy ++;
42 450 : cpy.skipChars<Scheme>();
43 450 : if (cpy.empty()) {
44 450 : return true;
45 : }
46 : }
47 0 : return false;
48 : }
49 :
50 825 : static bool validateHost(StringView &r) {
51 825 : if (r.empty()) {
52 0 : return false;
53 : }
54 825 : auto cpy = r;
55 825 : if (cpy.is('[')) {
56 : // check ipv6
57 100 : ++ cpy;
58 100 : cpy.skipChars<Ipv6>();
59 100 : if (cpy.is(']')) {
60 100 : cpy ++;
61 100 : if (cpy.empty()) {
62 100 : return true;
63 : }
64 : }
65 : } else {
66 725 : cpy.skipChars<Unreserved, SubDelim, chars::UniChar>();
67 725 : if (cpy.empty()) {
68 725 : auto c = r.sub(r.size() - 1, 1);
69 750 : while (c.is(',') || c.is('.') || c.is(';')) {
70 25 : r = r.sub(0, r.size() - 1);
71 25 : c = r.sub(r.size() - 1, 1);
72 : }
73 725 : if (c.is<StringView::Compose<StringView::CharGroup<CharGroupId::Alphanumeric>, chars::UniChar>>()) {
74 725 : StringView h(r);
75 725 : h.readUntil<chars::UniChar>();
76 725 : if (!h.empty()) {
77 50 : auto h = r;
78 50 : auto domain = h.backwardReadUntil<StringView::Chars<'.'>>();
79 50 : if (h.back() == '.' && !domain.empty()) {
80 50 : auto tmp = domain;
81 50 : tmp.skipChars<StringView::CharGroup<CharGroupId::Alphanumeric>>();
82 50 : if (!tmp.empty()) {
83 50 : if (UrlView::isValidIdnTld(domain)) {
84 50 : return true;
85 : }
86 : }
87 : }
88 : } else {
89 675 : return true;
90 : }
91 : }
92 : }
93 : }
94 0 : return false;
95 : }
96 :
97 475 : static bool validateUserOrPassword(const StringView &r) {
98 475 : auto cpy = r;
99 475 : cpy.skipChars<Unreserved, SubDelim, chars::UniChar>();
100 475 : if (cpy.empty()) {
101 475 : return true;
102 : }
103 0 : return false;
104 : }
105 :
106 950 : bool parseUrl(StringView &s, const Callback<void(StringViewUtf8, UrlToken)> &cb) {
107 950 : UrlToken state = UrlToken::Scheme;
108 :
109 950 : StringView tmp;
110 950 : if (s.is('[')) {
111 25 : state = UrlToken::Host;
112 925 : } else if (s.is("mailto:")) {
113 50 : cb(StringView(s, 6), UrlToken::Scheme);
114 50 : s += 6;
115 50 : cb(StringView(s, 1), UrlToken::Blank);
116 50 : ++ s;
117 50 : state = UrlToken::User;
118 : } else {
119 875 : tmp = s.readChars<UnreservedUni>();
120 : }
121 :
122 950 : if (state == UrlToken::Scheme) {
123 875 : if (s.is(':')) {
124 : // scheme or host+port
125 625 : if (tmp.empty()) {
126 0 : return false; // error
127 : }
128 :
129 625 : if (s.is("://")) {
130 350 : if (!validateScheme(tmp)) {
131 0 : return false;
132 : }
133 :
134 350 : cb(tmp, UrlToken::Scheme);
135 350 : cb(StringView(s, 3), UrlToken::Blank);
136 350 : s += 3;
137 :
138 350 : if (s.is('[')) {
139 25 : state = UrlToken::Host;
140 : } else {
141 325 : state = UrlToken::User;
142 : }
143 : } else {
144 : // if it's port, next chars will be numbers only
145 275 : auto tmpS = s;
146 275 : tmpS ++;
147 275 : auto port = tmpS.readChars<StringView::CharGroup<CharGroupId::Numbers>>();
148 275 : if (!port.empty() && !tmpS.is<UnreservedUni>() && !tmpS.is('@')) {
149 : // host + port
150 100 : if (!validateHost(tmp)) {
151 25 : return true;
152 : }
153 :
154 100 : cb(tmp, UrlToken::Host);
155 100 : cb(StringView(port.data() - 1, 1), UrlToken::Blank);
156 100 : cb(port, UrlToken::Port);
157 100 : s = tmpS;
158 :
159 100 : if (s.is('/')) {
160 25 : state = UrlToken::Path;
161 75 : } else if (s.is<StringView::Chars<'?'>>()) {
162 25 : state = UrlToken::Query;
163 50 : } else if (s.is('#')) {
164 25 : state = UrlToken::Fragment;
165 : } else {
166 25 : return true;
167 : }
168 : } else {
169 175 : tmpS = s;
170 175 : ++ tmpS;
171 175 : auto arg = tmpS.readChars<UnreservedUni, SubDelim>();
172 175 : if (tmpS.is('@')) {
173 : // username + password
174 75 : if (!validateUserOrPassword(tmp) || !validateUserOrPassword(arg)) {
175 0 : return false;
176 : }
177 :
178 75 : cb(tmp, UrlToken::User);
179 75 : cb(StringView(arg.data() - 1, 1), UrlToken::Blank);
180 75 : cb(arg, UrlToken::Password);
181 75 : cb(StringView(tmpS, 1), UrlToken::Blank);
182 75 : state = UrlToken::Host;
183 75 : ++ tmpS;
184 75 : s = tmpS;
185 : } else {
186 : // scheme without authority segment
187 100 : if (!validateScheme(tmp)) {
188 0 : return false;
189 : }
190 100 : cb(tmp, UrlToken::Scheme);
191 100 : state = UrlToken::Path;
192 100 : cb(s.sub(0, 1), UrlToken::Blank);
193 100 : ++ s;
194 : }
195 : }
196 : }
197 250 : } else if (s.is('@')) {
198 100 : if (tmp.empty() || !validateUserOrPassword(tmp)) {
199 0 : return false;
200 : }
201 100 : cb(tmp, UrlToken::User);
202 100 : state = UrlToken::Host;
203 100 : cb(StringView(s, 1), UrlToken::Blank);
204 100 : ++ s;
205 150 : } else if (s.is('/')) {
206 : // host + path
207 50 : if (!tmp.empty()) {
208 25 : if (!validateHost(tmp)) {
209 0 : return false;
210 : }
211 25 : cb(tmp, UrlToken::Host);
212 : }
213 50 : state = UrlToken::Path;
214 100 : } else if (s.is('?')) {
215 : // host + query
216 25 : if (tmp.empty()) {
217 0 : return false;
218 : }
219 25 : if (!validateHost(tmp)) {
220 0 : return false;
221 : }
222 25 : cb(tmp, UrlToken::Host);
223 25 : state = UrlToken::Query;
224 75 : } else if (s.is('#')) {
225 : // host + fragment
226 25 : if (tmp.empty()) {
227 0 : return false;
228 : }
229 25 : if (!validateHost(tmp)) {
230 0 : return false;
231 : }
232 25 : cb(tmp, UrlToken::Host);
233 25 : state = UrlToken::Fragment;
234 : } else {
235 : // assume host-only
236 50 : if (!tmp.empty()) {
237 50 : if (!validateHost(tmp)) {
238 0 : return false;
239 : }
240 50 : cb(tmp, UrlToken::Host);
241 50 : return true;
242 : }
243 0 : return false;
244 : }
245 : }
246 :
247 875 : if (state == UrlToken::User) {
248 375 : auto tmp_s = s;
249 375 : tmp = tmp_s.readChars<UnreservedUni, SubDelim>();
250 :
251 375 : if (tmp_s.is('@')) {
252 : // user only part
253 150 : if (!validateUserOrPassword(tmp)) {
254 25 : return false;
255 : }
256 150 : cb(tmp, UrlToken::User);
257 150 : state = UrlToken::Host;
258 150 : cb(StringView(tmp_s, 1), UrlToken::Blank);
259 150 : ++ tmp_s;
260 150 : s = tmp_s;
261 225 : } else if (tmp_s.is(':')) {
262 : // user + password or host + port
263 125 : tmp_s ++;
264 125 : auto tmpS = tmp_s;
265 :
266 : // if it's port, next chars will be numbers only
267 125 : auto port = tmpS.readChars<StringView::CharGroup<CharGroupId::Numbers>>();
268 125 : if (!port.empty() && !tmpS.is('@')) {
269 : // host + port
270 75 : if (!validateHost(tmp)) {
271 0 : return true;
272 : }
273 :
274 75 : cb(tmp, UrlToken::Host);
275 75 : cb(StringView(port.data() - 1, 1), UrlToken::Blank);
276 75 : cb(port, UrlToken::Port);
277 75 : s = tmpS;
278 :
279 75 : if (s.is('/')) {
280 25 : state = UrlToken::Path;
281 50 : } else if (s.is('?')) {
282 25 : state = UrlToken::Query;
283 25 : } else if (s.is('#')) {
284 25 : state = UrlToken::Fragment;
285 : } else {
286 0 : return true;
287 : }
288 : } else {
289 : // user + password
290 50 : if (!validateUserOrPassword(tmp)) {
291 0 : return false;
292 : }
293 50 : cb(tmp, UrlToken::User);
294 :
295 50 : if (tmpS.is('@')) {
296 25 : cb(StringView(port.data() - 1, 1), UrlToken::Blank);
297 25 : cb(port, UrlToken::Password);
298 25 : ++ tmpS;
299 25 : s = tmpS;
300 25 : state = UrlToken::Host;
301 : } else {
302 25 : tmp = tmp_s.readChars<UnreservedUni, SubDelim>();
303 25 : if (!tmp_s.is('@')) {
304 0 : return false;
305 : }
306 25 : ++ tmp_s;
307 25 : if (!validateUserOrPassword(tmp)) {
308 0 : return false;
309 : }
310 25 : cb(StringView(tmp.data() - 1, 1), UrlToken::Blank);
311 25 : cb(tmp, UrlToken::Password);
312 25 : s = tmp_s;
313 25 : cb(StringView(s.data() - 1, 1), UrlToken::Blank);
314 25 : state = UrlToken::Host;
315 : }
316 : }
317 : } else {
318 : // host
319 100 : if (!validateHost(tmp)) {
320 0 : return false;
321 : }
322 :
323 100 : cb(tmp, UrlToken::Host);
324 100 : s = tmp_s;
325 :
326 100 : if (tmp_s.is('/')) {
327 50 : state = UrlToken::Path;
328 50 : } else if (tmp_s.is('?')) {
329 25 : state = UrlToken::Query;
330 25 : } else if (tmp_s.is('#')) {
331 0 : state = UrlToken::Fragment;
332 : } else {
333 25 : return true;
334 : }
335 : }
336 : }
337 :
338 850 : if (state == UrlToken::Host) {
339 425 : bool stop = false;
340 425 : if (s.is('[')) {
341 100 : auto t = s;
342 100 : ++ t;
343 100 : tmp = t.readChars<UnreservedUni, SubDelim, StringView::Chars<':'>>();
344 100 : if (t.is(']')) {
345 100 : ++ t;
346 100 : tmp = StringView(s.data(), t.data() - s.data());
347 100 : s = t;
348 : } else {
349 0 : return false;
350 : }
351 : } else {
352 325 : tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'[', ']'>>();
353 : }
354 425 : if (!validateHost(tmp)) {
355 0 : return false;
356 : }
357 425 : cb(tmp, UrlToken::Host);
358 425 : if (s.is(':')) {
359 300 : auto tmp2 = s;
360 300 : ++ tmp2;
361 300 : auto port = tmp2.readChars<StringView::CharGroup<CharGroupId::Numbers>>();
362 300 : if (port.empty() || s.is<UnreservedUni>()) {
363 50 : state = UrlToken::Path;
364 50 : stop = true;
365 : } else {
366 250 : cb(StringView(port.data() - 1, 1), UrlToken::Blank);
367 250 : cb(port, UrlToken::Port);
368 250 : s = tmp2;
369 : }
370 : }
371 425 : if (stop) {
372 : // do nothing
373 375 : } else if (s.is('/')) {
374 100 : state = UrlToken::Path;
375 275 : } else if (s.is('?')) {
376 25 : state = UrlToken::Query;
377 250 : } else if (s.is('#')) {
378 125 : state = UrlToken::Fragment;
379 : } else {
380 125 : return true;
381 : }
382 : }
383 :
384 725 : if (state == UrlToken::Path) {
385 400 : tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'/', ':', '@'>>();
386 400 : if (!tmp.empty()) {
387 400 : cb(tmp, UrlToken::Path);
388 : }
389 :
390 400 : if (s.is('?')) {
391 150 : state = UrlToken::Query;
392 250 : } else if (s.is('#')) {
393 0 : state = UrlToken::Fragment;
394 : } else {
395 250 : return true;
396 : }
397 : }
398 :
399 475 : if (state == UrlToken::Query) {
400 275 : tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'/', ':', '@', '?', '[', ']'>>();
401 275 : if (!tmp.empty()) {
402 275 : if (tmp.is('?')) {
403 275 : cb(StringView(tmp, 1), UrlToken::Blank);
404 275 : ++ tmp;
405 : }
406 275 : if (!tmp.empty()) {
407 275 : cb(tmp, UrlToken::Query);
408 : }
409 : }
410 :
411 275 : if (s.is('#')) {
412 225 : state = UrlToken::Fragment;
413 : } else {
414 50 : return true;
415 : }
416 : }
417 :
418 425 : if (state == UrlToken::Fragment) {
419 425 : tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'/', ':', '@', '?', '#', '[', ']'>>();
420 425 : if (!tmp.empty()) {
421 425 : if (tmp.is('#')) {
422 425 : cb(StringView(tmp, 1), UrlToken::Blank);
423 425 : ++ tmp;
424 : }
425 425 : if (!tmp.empty()) {
426 425 : cb(tmp, UrlToken::Fragment);
427 : }
428 : }
429 : }
430 :
431 425 : return true;
432 : }
433 :
434 : }
|