bdb4686958f30009333443189477a9d45f4f5d88
[WebKit-https.git] / Source / WebCore / platform / URL.cpp
1 /*
2  * Copyright (C) 2004, 2007-2008, 2011-2013, 2015-2016 Apple Inc. All rights reserved.
3  * Copyright (C) 2012 Research In Motion Limited. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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 INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "URL.h"
29
30 #include "URLParser.h"
31 #include <stdio.h>
32 #include <unicode/uidna.h>
33 #include <wtf/HashMap.h>
34 #include <wtf/HexNumber.h>
35 #include <wtf/NeverDestroyed.h>
36 #include <wtf/StdLibExtras.h>
37 #include <wtf/UUID.h>
38 #include <wtf/text/CString.h>
39 #include <wtf/text/StringBuilder.h>
40 #include <wtf/text/StringConcatenateNumbers.h>
41 #include <wtf/text/StringHash.h>
42 #include <wtf/text/TextStream.h>
43
44 // FIXME: This file makes too much use of the + operator on String.
45 // We either have to optimize that operator so it doesn't involve
46 // so many allocations, or change this to use StringBuffer instead.
47
48
49 namespace WebCore {
50 using namespace WTF;
51
52 typedef Vector<char, 512> CharBuffer;
53 typedef Vector<UChar, 512> UCharBuffer;
54
55 static const unsigned invalidPortNumber = 0xFFFF;
56
57 // Copies the source to the destination, assuming all the source characters are
58 // ASCII. The destination buffer must be large enough. Null characters are allowed
59 // in the source string, and no attempt is made to null-terminate the result.
60 static void copyASCII(const String& string, char* dest)
61 {
62     if (string.isEmpty())
63         return;
64
65     if (string.is8Bit())
66         memcpy(dest, string.characters8(), string.length());
67     else {
68         const UChar* src = string.characters16();
69         size_t length = string.length();
70         for (size_t i = 0; i < length; i++)
71             dest[i] = static_cast<char>(src[i]);
72     }
73 }
74
75 void URL::invalidate()
76 {
77     m_isValid = false;
78     m_protocolIsInHTTPFamily = false;
79     m_cannotBeABaseURL = false;
80     m_schemeEnd = 0;
81     m_userStart = 0;
82     m_userEnd = 0;
83     m_passwordEnd = 0;
84     m_hostEnd = 0;
85     m_portLength = 0;
86     m_pathEnd = 0;
87     m_pathAfterLastSlash = 0;
88     m_queryEnd = 0;
89 }
90
91 URL::URL(const URL& base, const String& relative, const URLTextEncoding* encoding)
92 {
93     URLParser parser(relative, base, encoding);
94     *this = parser.result();
95 }
96
97 static bool shouldTrimFromURL(UChar c)
98 {
99     // Browsers ignore leading/trailing whitespace and control
100     // characters from URLs.  Note that c is an *unsigned* char here
101     // so this comparison should only catch control characters.
102     return c <= ' ';
103 }
104
105 URL URL::isolatedCopy() const
106 {
107     URL result = *this;
108     result.m_string = result.m_string.isolatedCopy();
109     return result;
110 }
111
112 String URL::lastPathComponent() const
113 {
114     if (!hasPath())
115         return String();
116
117     unsigned end = m_pathEnd - 1;
118     if (m_string[end] == '/')
119         --end;
120
121     size_t start = m_string.reverseFind('/', end);
122     if (start < static_cast<unsigned>(m_hostEnd + m_portLength))
123         return String();
124     ++start;
125
126     return m_string.substring(start, end - start + 1);
127 }
128
129 StringView URL::protocol() const
130 {
131     return StringView(m_string).substring(0, m_schemeEnd);
132 }
133
134 StringView URL::host() const
135 {
136     unsigned start = hostStart();
137     return StringView(m_string).substring(start, m_hostEnd - start);
138 }
139
140 std::optional<uint16_t> URL::port() const
141 {
142     if (!m_portLength)
143         return std::nullopt;
144
145     bool ok = false;
146     unsigned number;
147     if (m_string.is8Bit())
148         number = charactersToUIntStrict(m_string.characters8() + m_hostEnd + 1, m_portLength - 1, &ok);
149     else
150         number = charactersToUIntStrict(m_string.characters16() + m_hostEnd + 1, m_portLength - 1, &ok);
151     if (!ok || number > std::numeric_limits<uint16_t>::max())
152         return std::nullopt;
153     return number;
154 }
155
156 String URL::hostAndPort() const
157 {
158     if (auto port = this->port())
159         return makeString(host(), ':', static_cast<unsigned>(port.value()));
160     return host().toString();
161 }
162
163 String URL::protocolHostAndPort() const
164 {
165     String result = m_string.substring(0, m_hostEnd + m_portLength);
166
167     if (m_passwordEnd - m_userStart > 0) {
168         const int allowForTrailingAtSign = 1;
169         result.remove(m_userStart, m_passwordEnd - m_userStart + allowForTrailingAtSign);
170     }
171
172     return result;
173 }
174
175 static String decodeEscapeSequencesFromParsedURL(StringView input)
176 {
177     auto inputLength = input.length();
178     if (!inputLength)
179         return emptyString();
180     Vector<LChar> percentDecoded;
181     percentDecoded.reserveInitialCapacity(inputLength);
182     for (unsigned i = 0; i < inputLength; ++i) {
183         if (input[i] == '%'
184             && inputLength > 2
185             && i < inputLength - 2
186             && isASCIIHexDigit(input[i + 1])
187             && isASCIIHexDigit(input[i + 2])) {
188             percentDecoded.uncheckedAppend(toASCIIHexValue(input[i + 1], input[i + 2]));
189             i += 2;
190         } else
191             percentDecoded.uncheckedAppend(input[i]);
192     }
193     return String::fromUTF8(percentDecoded.data(), percentDecoded.size());
194 }
195
196 String URL::user() const
197 {
198     return decodeEscapeSequencesFromParsedURL(StringView(m_string).substring(m_userStart, m_userEnd - m_userStart));
199 }
200
201 String URL::pass() const
202 {
203     if (m_passwordEnd == m_userEnd)
204         return String();
205
206     return decodeEscapeSequencesFromParsedURL(StringView(m_string).substring(m_userEnd + 1, m_passwordEnd - m_userEnd - 1));
207 }
208
209 String URL::encodedUser() const
210 {
211     return m_string.substring(m_userStart, m_userEnd - m_userStart);
212 }
213
214 String URL::encodedPass() const
215 {
216     if (m_passwordEnd == m_userEnd)
217         return String();
218
219     return m_string.substring(m_userEnd + 1, m_passwordEnd - m_userEnd - 1);
220 }
221
222 String URL::fragmentIdentifier() const
223 {
224     if (!hasFragmentIdentifier())
225         return String();
226
227     return m_string.substring(m_queryEnd + 1);
228 }
229
230 bool URL::hasFragmentIdentifier() const
231 {
232     return m_isValid && m_string.length() != m_queryEnd;
233 }
234
235 String URL::baseAsString() const
236 {
237     return m_string.left(m_pathAfterLastSlash);
238 }
239
240 #if !USE(CF)
241
242 String URL::fileSystemPath() const
243 {
244     if (!isValid() || !isLocalFile())
245         return String();
246
247     return decodeEscapeSequencesFromParsedURL(StringView(path()));
248 }
249
250 #endif
251
252 #ifdef NDEBUG
253
254 static inline void assertProtocolIsGood(StringView)
255 {
256 }
257
258 #else
259
260 static void assertProtocolIsGood(StringView protocol)
261 {
262     // FIXME: We probably don't need this function any more.
263     // The isASCIIAlphaCaselessEqual function asserts that passed-in characters
264     // are ones it can handle; the older code did not and relied on these checks.
265     for (auto character : protocol.codeUnits()) {
266         ASSERT(isASCII(character));
267         ASSERT(character > ' ');
268         ASSERT(!isASCIIUpper(character));
269         ASSERT(toASCIILowerUnchecked(character) == character);
270     }
271 }
272
273 #endif
274
275 static Lock defaultPortForProtocolMapForTestingLock;
276
277 using DefaultPortForProtocolMapForTesting = HashMap<String, uint16_t>;
278 static DefaultPortForProtocolMapForTesting*& defaultPortForProtocolMapForTesting()
279 {
280     static DefaultPortForProtocolMapForTesting* defaultPortForProtocolMap;
281     return defaultPortForProtocolMap;
282 }
283
284 static DefaultPortForProtocolMapForTesting& ensureDefaultPortForProtocolMapForTesting()
285 {
286     DefaultPortForProtocolMapForTesting*& defaultPortForProtocolMap = defaultPortForProtocolMapForTesting();
287     if (!defaultPortForProtocolMap)
288         defaultPortForProtocolMap = new DefaultPortForProtocolMapForTesting;
289     return *defaultPortForProtocolMap;
290 }
291
292 void registerDefaultPortForProtocolForTesting(uint16_t port, const String& protocol)
293 {
294     auto locker = holdLock(defaultPortForProtocolMapForTestingLock);
295     ensureDefaultPortForProtocolMapForTesting().add(protocol, port);
296 }
297
298 void clearDefaultPortForProtocolMapForTesting()
299 {
300     auto locker = holdLock(defaultPortForProtocolMapForTestingLock);
301     if (auto* map = defaultPortForProtocolMapForTesting())
302         map->clear();
303 }
304
305 std::optional<uint16_t> defaultPortForProtocol(StringView protocol)
306 {
307     if (auto* overrideMap = defaultPortForProtocolMapForTesting()) {
308         auto locker = holdLock(defaultPortForProtocolMapForTestingLock);
309         ASSERT(overrideMap); // No need to null check again here since overrideMap cannot become null after being non-null.
310         auto iterator = overrideMap->find(protocol.toStringWithoutCopying());
311         if (iterator != overrideMap->end())
312             return iterator->value;
313     }
314     return URLParser::defaultPortForProtocol(protocol);
315 }
316
317 bool isDefaultPortForProtocol(uint16_t port, StringView protocol)
318 {
319     return defaultPortForProtocol(protocol) == port;
320 }
321
322 bool URL::protocolIs(const char* protocol) const
323 {
324     assertProtocolIsGood(StringView(reinterpret_cast<const LChar*>(protocol), strlen(protocol)));
325
326     // JavaScript URLs are "valid" and should be executed even if URL decides they are invalid.
327     // The free function protocolIsJavaScript() should be used instead. 
328     ASSERT(!equalLettersIgnoringASCIICase(StringView(protocol), "javascript"));
329
330     if (!m_isValid)
331         return false;
332
333     // Do the comparison without making a new string object.
334     for (unsigned i = 0; i < m_schemeEnd; ++i) {
335         if (!protocol[i] || !isASCIIAlphaCaselessEqual(m_string[i], protocol[i]))
336             return false;
337     }
338     return !protocol[m_schemeEnd]; // We should have consumed all characters in the argument.
339 }
340
341 bool URL::protocolIs(StringView protocol) const
342 {
343     assertProtocolIsGood(protocol);
344
345     if (!m_isValid)
346         return false;
347     
348     if (m_schemeEnd != protocol.length())
349         return false;
350
351     // Do the comparison without making a new string object.
352     for (unsigned i = 0; i < m_schemeEnd; ++i) {
353         if (!isASCIIAlphaCaselessEqual(m_string[i], protocol[i]))
354             return false;
355     }
356     return true;
357 }
358
359 String URL::query() const
360 {
361     if (m_queryEnd == m_pathEnd)
362         return String();
363
364     return m_string.substring(m_pathEnd + 1, m_queryEnd - (m_pathEnd + 1)); 
365 }
366
367 String URL::path() const
368 {
369     unsigned portEnd = m_hostEnd + m_portLength;
370     return m_string.substring(portEnd, m_pathEnd - portEnd);
371 }
372
373 bool URL::setProtocol(const String& s)
374 {
375     // Firefox and IE remove everything after the first ':'.
376     size_t separatorPosition = s.find(':');
377     String newProtocol = s.substring(0, separatorPosition);
378     auto canonicalized = URLParser::maybeCanonicalizeScheme(newProtocol);
379     if (!canonicalized)
380         return false;
381
382     if (!m_isValid) {
383         URLParser parser(makeString(*canonicalized, ":", m_string));
384         *this = parser.result();
385         return true;
386     }
387
388     URLParser parser(makeString(*canonicalized, m_string.substring(m_schemeEnd)));
389     *this = parser.result();
390     return true;
391 }
392
393 static bool isAllASCII(StringView string)
394 {
395     if (string.is8Bit())
396         return charactersAreAllASCII(string.characters8(), string.length());
397     return charactersAreAllASCII(string.characters16(), string.length());
398 }
399     
400 // Appends the punycoded hostname identified by the given string and length to
401 // the output buffer. The result will not be null terminated.
402 // Return value of false means error in encoding.
403 static bool appendEncodedHostname(UCharBuffer& buffer, StringView string)
404 {
405     // Needs to be big enough to hold an IDN-encoded name.
406     // For host names bigger than this, we won't do IDN encoding, which is almost certainly OK.
407     const unsigned hostnameBufferLength = 2048;
408     
409     if (string.length() > hostnameBufferLength || isAllASCII(string)) {
410         append(buffer, string);
411         return true;
412     }
413     
414     UChar hostnameBuffer[hostnameBufferLength];
415     UErrorCode error = U_ZERO_ERROR;
416     UIDNAInfo processingDetails = UIDNA_INFO_INITIALIZER;
417     int32_t numCharactersConverted = uidna_nameToASCII(&URLParser::internationalDomainNameTranscoder(),
418         string.upconvertedCharacters(), string.length(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error);
419     
420     if (U_SUCCESS(error) && !processingDetails.errors) {
421         buffer.append(hostnameBuffer, numCharactersConverted);
422         return true;
423     }
424     return false;
425 }
426
427 unsigned URL::hostStart() const
428 {
429     return (m_passwordEnd == m_userStart) ? m_passwordEnd : m_passwordEnd + 1;
430 }
431
432 void URL::setHost(const String& s)
433 {
434     if (!m_isValid)
435         return;
436
437     auto colonIndex = s.find(':');
438     if (colonIndex != notFound)
439         return;
440
441     UCharBuffer encodedHostName;
442     if (!appendEncodedHostname(encodedHostName, s))
443         return;
444     
445     bool slashSlashNeeded = m_userStart == static_cast<unsigned>(m_schemeEnd + 1);
446     
447     StringBuilder builder;
448     builder.append(m_string.left(hostStart()));
449     if (slashSlashNeeded)
450         builder.appendLiteral("//");
451     builder.append(StringView(encodedHostName.data(), encodedHostName.size()));
452     builder.append(m_string.substring(m_hostEnd));
453
454     URLParser parser(builder.toString());
455     *this = parser.result();
456 }
457
458 void URL::removePort()
459 {
460     if (!m_portLength)
461         return;
462     URLParser parser(makeString(StringView(m_string).left(m_hostEnd), StringView(m_string).substring(m_hostEnd + m_portLength)));
463     *this = parser.result();
464 }
465
466 void URL::setPort(unsigned short i)
467 {
468     if (!m_isValid)
469         return;
470
471     bool colonNeeded = !m_portLength;
472     unsigned portStart = (colonNeeded ? m_hostEnd : m_hostEnd + 1);
473
474     URLParser parser(makeString(StringView(m_string).left(portStart), (colonNeeded ? ":" : ""), String::number(i), StringView(m_string).substring(m_hostEnd + m_portLength)));
475     *this = parser.result();
476 }
477
478 void URL::setHostAndPort(const String& hostAndPort)
479 {
480     if (!m_isValid)
481         return;
482
483     StringView hostName(hostAndPort);
484     StringView port;
485     
486     auto colonIndex = hostName.find(':');
487     if (colonIndex != notFound) {
488         port = hostName.substring(colonIndex + 1);
489         bool ok;
490         int portInt = port.toIntStrict(ok);
491         if (!ok || portInt < 0)
492             return;
493         hostName = hostName.substring(0, colonIndex);
494     }
495
496     if (hostName.isEmpty())
497         return;
498
499     UCharBuffer encodedHostName;
500     if (!appendEncodedHostname(encodedHostName, hostName))
501         return;
502
503     bool slashSlashNeeded = m_userStart == static_cast<unsigned>(m_schemeEnd + 1);
504
505     StringBuilder builder;
506     builder.append(m_string.left(hostStart()));
507     if (slashSlashNeeded)
508         builder.appendLiteral("//");
509     builder.append(StringView(encodedHostName.data(), encodedHostName.size()));
510     if (!port.isEmpty()) {
511         builder.appendLiteral(":");
512         builder.append(port);
513     }
514     builder.append(StringView(m_string).substring(m_hostEnd + m_portLength));
515
516     URLParser parser(builder.toString());
517     *this = parser.result();
518 }
519
520 static String percentEncodeCharacters(const String& input, bool(*shouldEncode)(UChar))
521 {
522     auto encode = [shouldEncode] (const String& input) {
523         CString utf8 = input.utf8();
524         auto* data = utf8.data();
525         StringBuilder builder;
526         auto length = utf8.length();
527         for (unsigned j = 0; j < length; j++) {
528             auto c = data[j];
529             if (shouldEncode(c)) {
530                 builder.append('%');
531                 builder.append(upperNibbleToASCIIHexDigit(c));
532                 builder.append(lowerNibbleToASCIIHexDigit(c));
533             } else
534                 builder.append(c);
535         }
536         return builder.toString();
537     };
538
539     for (size_t i = 0; i < input.length(); ++i) {
540         if (UNLIKELY(shouldEncode(input[i])))
541             return encode(input);
542     }
543     return input;
544 }
545
546 void URL::setUser(const String& user)
547 {
548     if (!m_isValid)
549         return;
550
551     // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
552     // and to avoid changing more than just the user login.
553
554     unsigned end = m_userEnd;
555     if (!user.isEmpty()) {
556         String u = percentEncodeCharacters(user, URLParser::isInUserInfoEncodeSet);
557         if (m_userStart == static_cast<unsigned>(m_schemeEnd + 1))
558             u = "//" + u;
559         // Add '@' if we didn't have one before.
560         if (end == m_hostEnd || (end == m_passwordEnd && m_string[end] != '@'))
561             u.append('@');
562         URLParser parser(makeString(StringView(m_string).left(m_userStart), u, StringView(m_string).substring(end)));
563         *this = parser.result();
564     } else {
565         // Remove '@' if we now have neither user nor password.
566         if (m_userEnd == m_passwordEnd && end != m_hostEnd && m_string[end] == '@')
567             end += 1;
568         // We don't want to parse in the extremely common case where we are not going to make a change.
569         if (m_userStart != end) {
570             URLParser parser(makeString(StringView(m_string).left(m_userStart), StringView(m_string).substring(end)));
571             *this = parser.result();
572         }
573     }
574 }
575
576 void URL::setPass(const String& password)
577 {
578     if (!m_isValid)
579         return;
580
581     unsigned end = m_passwordEnd;
582     if (!password.isEmpty()) {
583         String p = ":" + percentEncodeCharacters(password, URLParser::isInUserInfoEncodeSet) + "@";
584         if (m_userEnd == static_cast<unsigned>(m_schemeEnd + 1))
585             p = "//" + p;
586         // Eat the existing '@' since we are going to add our own.
587         if (end != m_hostEnd && m_string[end] == '@')
588             end += 1;
589         URLParser parser(makeString(StringView(m_string).left(m_userEnd), p, StringView(m_string).substring(end)));
590         *this = parser.result();
591     } else {
592         // Remove '@' if we now have neither user nor password.
593         if (m_userStart == m_userEnd && end != m_hostEnd && m_string[end] == '@')
594             end += 1;
595         // We don't want to parse in the extremely common case where we are not going to make a change.
596         if (m_userEnd != end) {
597             URLParser parser(makeString(StringView(m_string).left(m_userEnd), StringView(m_string).substring(end)));
598             *this = parser.result();
599         }
600     }
601 }
602
603 void URL::setFragmentIdentifier(StringView identifier)
604 {
605     if (!m_isValid)
606         return;
607
608     // FIXME: Optimize the case where the identifier already happens to be equal to what was passed?
609     // FIXME: Is it correct to do this without encoding and escaping non-ASCII characters?
610     *this = URLParser { makeString(StringView { m_string }.substring(0, m_queryEnd), '#', identifier) }.result();
611 }
612
613 void URL::removeFragmentIdentifier()
614 {
615     if (!m_isValid) {
616         ASSERT(!m_queryEnd);
617         return;
618     }
619     if (m_isValid && m_string.length() > m_queryEnd)
620         m_string = m_string.left(m_queryEnd);
621 }
622
623 void URL::removeQueryAndFragmentIdentifier()
624 {
625     if (!m_isValid)
626         return;
627
628     m_string = m_string.left(m_pathEnd);
629     m_queryEnd = m_pathEnd;
630 }
631
632 void URL::setQuery(const String& query)
633 {
634     if (!m_isValid)
635         return;
636
637     // FIXME: '#' and non-ASCII characters must be encoded and escaped.
638     // Usually, the query is encoded using document encoding, not UTF-8, but we don't have
639     // access to the document in this function.
640     // https://webkit.org/b/161176
641     if ((query.isEmpty() || query[0] != '?') && !query.isNull()) {
642         URLParser parser(makeString(StringView(m_string).left(m_pathEnd), "?", query, StringView(m_string).substring(m_queryEnd)));
643         *this = parser.result();
644     } else {
645         URLParser parser(makeString(StringView(m_string).left(m_pathEnd), query, StringView(m_string).substring(m_queryEnd)));
646         *this = parser.result();
647     }
648
649 }
650
651 void URL::setPath(const String& s)
652 {
653     if (!m_isValid)
654         return;
655
656     String path = s;
657     if (path.isEmpty() || path[0] != '/')
658         path = "/" + path;
659
660     auto questionMarkOrNumberSign = [] (UChar character) {
661         return character == '?' || character == '#';
662     };
663     URLParser parser(makeString(StringView(m_string).left(m_hostEnd + m_portLength), percentEncodeCharacters(path, questionMarkOrNumberSign), StringView(m_string).substring(m_pathEnd)));
664     *this = parser.result();
665 }
666
667 bool equalIgnoringFragmentIdentifier(const URL& a, const URL& b)
668 {
669     if (a.m_queryEnd != b.m_queryEnd)
670         return false;
671     unsigned queryLength = a.m_queryEnd;
672     for (unsigned i = 0; i < queryLength; ++i)
673         if (a.string()[i] != b.string()[i])
674             return false;
675     return true;
676 }
677
678 bool equalIgnoringQueryAndFragment(const URL& a, const URL& b)
679 {
680     if (a.pathEnd() != b.pathEnd())
681         return false;
682     unsigned pathEnd = a.pathEnd();
683     for (unsigned i = 0; i < pathEnd; ++i) {
684         if (a.string()[i] != b.string()[i])
685             return false;
686     }
687     return true;
688 }
689
690 bool protocolHostAndPortAreEqual(const URL& a, const URL& b)
691 {
692     if (a.m_schemeEnd != b.m_schemeEnd)
693         return false;
694
695     unsigned hostStartA = a.hostStart();
696     unsigned hostLengthA = a.m_hostEnd - hostStartA;
697     unsigned hostStartB = b.hostStart();
698     unsigned hostLengthB = b.m_hostEnd - b.hostStart();
699     if (hostLengthA != hostLengthB)
700         return false;
701
702     // Check the scheme
703     for (unsigned i = 0; i < a.m_schemeEnd; ++i) {
704         if (a.string()[i] != b.string()[i])
705             return false;
706     }
707
708     // And the host
709     for (unsigned i = 0; i < hostLengthA; ++i) {
710         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
711             return false;
712     }
713
714     if (a.port() != b.port())
715         return false;
716
717     return true;
718 }
719
720 bool hostsAreEqual(const URL& a, const URL& b)
721 {
722     unsigned hostStartA = a.hostStart();
723     unsigned hostLengthA = a.m_hostEnd - hostStartA;
724     unsigned hostStartB = b.hostStart();
725     unsigned hostLengthB = b.m_hostEnd - hostStartB;
726     if (hostLengthA != hostLengthB)
727         return false;
728
729     for (unsigned i = 0; i < hostLengthA; ++i) {
730         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
731             return false;
732     }
733
734     return true;
735 }
736
737 bool URL::isMatchingDomain(const String& domain) const
738 {
739     if (isNull())
740         return false;
741
742     if (domain.isEmpty())
743         return true;
744
745     if (!protocolIsInHTTPFamily())
746         return false;
747
748     auto host = this->host();
749     if (!host.endsWith(domain))
750         return false;
751
752     return host.length() == domain.length() || host[host.length() - domain.length() - 1] == '.';
753 }
754
755 String encodeWithURLEscapeSequences(const String& input)
756 {
757     return percentEncodeCharacters(input, URLParser::isInUserInfoEncodeSet);
758 }
759
760 bool URL::isHierarchical() const
761 {
762     if (!m_isValid)
763         return false;
764     ASSERT(m_string[m_schemeEnd] == ':');
765     return m_string[m_schemeEnd + 1] == '/';
766 }
767
768 void URL::copyToBuffer(Vector<char, 512>& buffer) const
769 {
770     // FIXME: This throws away the high bytes of all the characters in the string!
771     // That's fine for a valid URL, which is all ASCII, but not for invalid URLs.
772     buffer.resize(m_string.length());
773     copyASCII(m_string, buffer.data());
774 }
775
776 template<typename StringClass>
777 bool protocolIsInternal(const StringClass& url, const char* protocol)
778 {
779     // Do the comparison without making a new string object.
780     assertProtocolIsGood(StringView(reinterpret_cast<const LChar*>(protocol), strlen(protocol)));
781     bool isLeading = true;
782     for (unsigned i = 0, j = 0; url[i]; ++i) {
783         // Skip leading whitespace and control characters.
784         if (isLeading && shouldTrimFromURL(url[i]))
785             continue;
786         isLeading = false;
787
788         // Skip any tabs and newlines.
789         if (url[i] == '\t' || url[i] == '\r' || url[i] == '\n')
790             continue;
791
792         if (!protocol[j])
793             return url[i] == ':';
794         if (!isASCIIAlphaCaselessEqual(url[i], protocol[j]))
795             return false;
796
797         ++j;
798     }
799     
800     return false;
801 }
802
803 bool protocolIs(const String& url, const char* protocol)
804 {
805     return protocolIsInternal(url, protocol);
806 }
807
808 inline bool URL::protocolIs(const String& string, const char* protocol)
809 {
810     return WebCore::protocolIsInternal(string, protocol);
811 }
812
813 #ifndef NDEBUG
814
815 void URL::print() const
816 {
817     printf("%s\n", m_string.utf8().data());
818 }
819
820 #endif
821
822 String URL::strippedForUseAsReferrer() const
823 {
824     URL referrer(*this);
825     referrer.setUser(String());
826     referrer.setPass(String());
827     referrer.removeFragmentIdentifier();
828     return referrer.string();
829 }
830
831 bool URL::isLocalFile() const
832 {
833     // Including feed here might be a bad idea since drag and drop uses this check
834     // and including feed would allow feeds to potentially let someone's blog
835     // read the contents of the clipboard on a drag, even without a drop.
836     // Likewise with using the FrameLoader::shouldTreatURLAsLocal() function.
837     return protocolIs("file");
838 }
839
840 bool protocolIsJavaScript(const String& url)
841 {
842     return protocolIsInternal(url, "javascript");
843 }
844
845 bool protocolIsJavaScript(StringView url)
846 {
847     return protocolIsInternal(url, "javascript");
848 }
849
850 bool protocolIsInHTTPFamily(const String& url)
851 {
852     auto length = url.length();
853     // Do the comparison without making a new string object.
854     return length >= 5
855         && isASCIIAlphaCaselessEqual(url[0], 'h')
856         && isASCIIAlphaCaselessEqual(url[1], 't')
857         && isASCIIAlphaCaselessEqual(url[2], 't')
858         && isASCIIAlphaCaselessEqual(url[3], 'p')
859         && (url[4] == ':' || (isASCIIAlphaCaselessEqual(url[4], 's') && length >= 6 && url[5] == ':'));
860 }
861
862 const URL& blankURL()
863 {
864     static NeverDestroyed<URL> staticBlankURL(URL(), "about:blank");
865     return staticBlankURL;
866 }
867
868 bool URL::isBlankURL() const
869 {
870     return protocolIs("about");
871 }
872
873 bool portAllowed(const URL& url)
874 {
875     std::optional<uint16_t> port = url.port();
876
877     // Since most URLs don't have a port, return early for the "no port" case.
878     if (!port)
879         return true;
880
881     // This blocked port list matches the port blocking that Mozilla implements.
882     // See http://www.mozilla.org/projects/netlib/PortBanning.html for more information.
883     static const uint16_t blockedPortList[] = {
884         1,    // tcpmux
885         7,    // echo
886         9,    // discard
887         11,   // systat
888         13,   // daytime
889         15,   // netstat
890         17,   // qotd
891         19,   // chargen
892         20,   // FTP-data
893         21,   // FTP-control
894         22,   // SSH
895         23,   // telnet
896         25,   // SMTP
897         37,   // time
898         42,   // name
899         43,   // nicname
900         53,   // domain
901         77,   // priv-rjs
902         79,   // finger
903         87,   // ttylink
904         95,   // supdup
905         101,  // hostriame
906         102,  // iso-tsap
907         103,  // gppitnp
908         104,  // acr-nema
909         109,  // POP2
910         110,  // POP3
911         111,  // sunrpc
912         113,  // auth
913         115,  // SFTP
914         117,  // uucp-path
915         119,  // nntp
916         123,  // NTP
917         135,  // loc-srv / epmap
918         139,  // netbios
919         143,  // IMAP2
920         179,  // BGP
921         389,  // LDAP
922         465,  // SMTP+SSL
923         512,  // print / exec
924         513,  // login
925         514,  // shell
926         515,  // printer
927         526,  // tempo
928         530,  // courier
929         531,  // Chat
930         532,  // netnews
931         540,  // UUCP
932         548,  // afpovertcp [Apple addition]
933         556,  // remotefs
934         563,  // NNTP+SSL
935         587,  // ESMTP
936         601,  // syslog-conn
937         636,  // LDAP+SSL
938         993,  // IMAP+SSL
939         995,  // POP3+SSL
940         2049, // NFS
941         3659, // apple-sasl / PasswordServer [Apple addition]
942         4045, // lockd
943         4190, // ManageSieve [Apple addition]
944         6000, // X11
945         6665, // Alternate IRC [Apple addition]
946         6666, // Alternate IRC [Apple addition]
947         6667, // Standard IRC [Apple addition]
948         6668, // Alternate IRC [Apple addition]
949         6669, // Alternate IRC [Apple addition]
950         6679, // Alternate IRC SSL [Apple addition]
951         6697, // IRC+SSL [Apple addition]
952         invalidPortNumber, // Used to block all invalid port numbers
953     };
954
955     // If the port is not in the blocked port list, allow it.
956     ASSERT(std::is_sorted(std::begin(blockedPortList), std::end(blockedPortList)));
957     if (!std::binary_search(std::begin(blockedPortList), std::end(blockedPortList), port.value()))
958         return true;
959
960     // Allow ports 21 and 22 for FTP URLs, as Mozilla does.
961     if ((port.value() == 21 || port.value() == 22) && url.protocolIs("ftp"))
962         return true;
963
964     // Allow any port number in a file URL, since the port number is ignored.
965     if (url.protocolIs("file"))
966         return true;
967
968     return false;
969 }
970
971 String mimeTypeFromDataURL(const String& url)
972 {
973     ASSERT(protocolIsInternal(url, "data"));
974
975     // FIXME: What's the right behavior when the URL has a comma first, but a semicolon later?
976     // Currently this code will break at the semicolon in that case. Not sure that's correct.
977     auto index = url.find(';', 5);
978     if (index == notFound)
979         index = url.find(',', 5);
980     if (index == notFound) {
981         // FIXME: There was an old comment here that made it sound like this should be returning text/plain.
982         // But we have been returning empty string here for some time, so not changing its behavior at this time.
983         return emptyString();
984     }
985     if (index == 5)
986         return "text/plain"_s;
987     ASSERT(index >= 5);
988     return url.substring(5, index - 5).convertToASCIILowercase();
989 }
990
991 String URL::stringCenterEllipsizedToLength(unsigned length) const
992 {
993     if (string().length() <= length)
994         return string();
995
996     return string().left(length / 2 - 1) + "..." + string().right(length / 2 - 2);
997 }
998
999 URL URL::fakeURLWithRelativePart(const String& relativePart)
1000 {
1001     return URL(URL(), "webkit-fake-url://" + createCanonicalUUIDString() + '/' + relativePart);
1002 }
1003
1004 URL URL::fileURLWithFileSystemPath(const String& filePath)
1005 {
1006     return URL(URL(), "file:///" + filePath);
1007 }
1008
1009 TextStream& operator<<(TextStream& ts, const URL& url)
1010 {
1011     ts << url.string();
1012     return ts;
1013 }
1014
1015 #if !PLATFORM(COCOA) && !USE(SOUP)
1016 static bool isIPv4Address(StringView string)
1017 {
1018     auto count = 0;
1019
1020     for (const auto octet : string.splitAllowingEmptyEntries('.')) {
1021         if (count >= 4)
1022             return false;
1023
1024         const auto length = octet.length();
1025         if (!length || length > 3)
1026             return false;
1027
1028         auto value = 0;
1029         for (auto i = 0u; i < length; ++i) {
1030             const auto digit = octet[i];
1031
1032             // Prohibit leading zeroes.
1033             if (digit > '9' || digit < (!i && length > 1 ? '1' : '0'))
1034                 return false;
1035
1036             value = 10 * value + (digit - '0');
1037         }
1038
1039         if (value > 255)
1040             return false;
1041
1042         count++;
1043     }
1044
1045     return (count == 4);
1046 }
1047
1048 static bool isIPv6Address(StringView string)
1049 {
1050     enum SkipState { None, WillSkip, Skipping, Skipped, Final };
1051     auto skipState = None;
1052     auto count = 0;
1053
1054     for (const auto hextet : string.splitAllowingEmptyEntries(':')) {
1055         if (count >= 8 || skipState == Final)
1056             return false;
1057
1058         const auto length = hextet.length();
1059         if (!length) {
1060             // :: may be used anywhere to skip 1 to 8 hextets, but only once.
1061             if (skipState == Skipped)
1062                 return false;
1063
1064             if (skipState == None)
1065                 skipState = !count ? WillSkip : Skipping;
1066             else if (skipState == WillSkip)
1067                 skipState = Skipping;
1068             else
1069                 skipState = Final;
1070             continue;
1071         }
1072
1073         if (skipState == WillSkip)
1074             return false;
1075
1076         if (skipState == Skipping)
1077             skipState = Skipped;
1078
1079         if (length > 4) {
1080             // An IPv4 address may be used in place of the final two hextets.
1081             if ((skipState == None && count != 6) || (skipState == Skipped && count >= 6) || !isIPv4Address(hextet))
1082                 return false;
1083
1084             skipState = Final;
1085             continue;
1086         }
1087
1088         for (const auto codeUnit : hextet.codeUnits()) {
1089             // IPv6 allows leading zeroes.
1090             if (!isASCIIHexDigit(codeUnit))
1091                 return false;
1092         }
1093
1094         count++;
1095     }
1096
1097     return (count == 8 && skipState == None) || skipState == Skipped || skipState == Final;
1098 }
1099
1100 bool URL::hostIsIPAddress(StringView host)
1101 {
1102     if (host.find(':') == notFound)
1103         return isIPv4Address(host);
1104
1105     return isIPv6Address(host);
1106 }
1107 #endif
1108
1109 } // namespace WebCore