9622bba6e71a8c4b4c94f2db9682cf4e0143c9f6
[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 #if PLATFORM(IOS)
668
669 static bool shouldCanonicalizeScheme = true;
670
671 void enableURLSchemeCanonicalization(bool enableSchemeCanonicalization)
672 {
673     shouldCanonicalizeScheme = enableSchemeCanonicalization;
674 }
675
676 #endif
677
678 template<size_t length>
679 static inline bool equal(const char* a, const char (&b)[length])
680 {
681 #if PLATFORM(IOS)
682     if (!shouldCanonicalizeScheme) {
683         for (size_t i = 0; i < length; ++i) {
684             if (toASCIILower(a[i]) != b[i])
685                 return false;
686         }
687         return true;
688     }
689 #endif
690     for (size_t i = 0; i < length; ++i) {
691         if (a[i] != b[i])
692             return false;
693     }
694     return true;
695 }
696
697 template<size_t lengthB>
698 static inline bool equal(const char* stringA, size_t lengthA, const char (&stringB)[lengthB])
699 {
700     return lengthA == lengthB && equal(stringA, stringB);
701 }
702
703 bool equalIgnoringFragmentIdentifier(const URL& a, const URL& b)
704 {
705     if (a.m_queryEnd != b.m_queryEnd)
706         return false;
707     unsigned queryLength = a.m_queryEnd;
708     for (unsigned i = 0; i < queryLength; ++i)
709         if (a.string()[i] != b.string()[i])
710             return false;
711     return true;
712 }
713
714 bool equalIgnoringQueryAndFragment(const URL& a, const URL& b)
715 {
716     if (a.pathEnd() != b.pathEnd())
717         return false;
718     unsigned pathEnd = a.pathEnd();
719     for (unsigned i = 0; i < pathEnd; ++i) {
720         if (a.string()[i] != b.string()[i])
721             return false;
722     }
723     return true;
724 }
725
726 bool protocolHostAndPortAreEqual(const URL& a, const URL& b)
727 {
728     if (a.m_schemeEnd != b.m_schemeEnd)
729         return false;
730
731     unsigned hostStartA = a.hostStart();
732     unsigned hostLengthA = a.m_hostEnd - hostStartA;
733     unsigned hostStartB = b.hostStart();
734     unsigned hostLengthB = b.m_hostEnd - b.hostStart();
735     if (hostLengthA != hostLengthB)
736         return false;
737
738     // Check the scheme
739     for (unsigned i = 0; i < a.m_schemeEnd; ++i) {
740         if (a.string()[i] != b.string()[i])
741             return false;
742     }
743
744     // And the host
745     for (unsigned i = 0; i < hostLengthA; ++i) {
746         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
747             return false;
748     }
749
750     if (a.port() != b.port())
751         return false;
752
753     return true;
754 }
755
756 bool hostsAreEqual(const URL& a, const URL& b)
757 {
758     unsigned hostStartA = a.hostStart();
759     unsigned hostLengthA = a.m_hostEnd - hostStartA;
760     unsigned hostStartB = b.hostStart();
761     unsigned hostLengthB = b.m_hostEnd - hostStartB;
762     if (hostLengthA != hostLengthB)
763         return false;
764
765     for (unsigned i = 0; i < hostLengthA; ++i) {
766         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
767             return false;
768     }
769
770     return true;
771 }
772
773 bool URL::isMatchingDomain(const String& domain) const
774 {
775     if (isNull())
776         return false;
777
778     if (domain.isEmpty())
779         return true;
780
781     if (!protocolIsInHTTPFamily())
782         return false;
783
784     auto host = this->host();
785     if (!host.endsWith(domain))
786         return false;
787
788     return host.length() == domain.length() || host[host.length() - domain.length() - 1] == '.';
789 }
790
791 String encodeWithURLEscapeSequences(const String& input)
792 {
793     return percentEncodeCharacters(input, URLParser::isInUserInfoEncodeSet);
794 }
795
796 bool URL::isHierarchical() const
797 {
798     if (!m_isValid)
799         return false;
800     ASSERT(m_string[m_schemeEnd] == ':');
801     return m_string[m_schemeEnd + 1] == '/';
802 }
803
804 void URL::copyToBuffer(Vector<char, 512>& buffer) const
805 {
806     // FIXME: This throws away the high bytes of all the characters in the string!
807     // That's fine for a valid URL, which is all ASCII, but not for invalid URLs.
808     buffer.resize(m_string.length());
809     copyASCII(m_string, buffer.data());
810 }
811
812 template<typename StringClass>
813 bool protocolIsInternal(const StringClass& url, const char* protocol)
814 {
815     // Do the comparison without making a new string object.
816     assertProtocolIsGood(StringView(reinterpret_cast<const LChar*>(protocol), strlen(protocol)));
817     bool isLeading = true;
818     for (unsigned i = 0, j = 0; url[i]; ++i) {
819         // Skip leading whitespace and control characters.
820         if (isLeading && shouldTrimFromURL(url[i]))
821             continue;
822         isLeading = false;
823
824         // Skip any tabs and newlines.
825         if (url[i] == '\t' || url[i] == '\r' || url[i] == '\n')
826             continue;
827
828         if (!protocol[j])
829             return url[i] == ':';
830         if (!isASCIIAlphaCaselessEqual(url[i], protocol[j]))
831             return false;
832
833         ++j;
834     }
835     
836     return false;
837 }
838
839 bool protocolIs(const String& url, const char* protocol)
840 {
841     return protocolIsInternal(url, protocol);
842 }
843
844 inline bool URL::protocolIs(const String& string, const char* protocol)
845 {
846     return WebCore::protocolIsInternal(string, protocol);
847 }
848
849 #ifndef NDEBUG
850
851 void URL::print() const
852 {
853     printf("%s\n", m_string.utf8().data());
854 }
855
856 #endif
857
858 String URL::strippedForUseAsReferrer() const
859 {
860     URL referrer(*this);
861     referrer.setUser(String());
862     referrer.setPass(String());
863     referrer.removeFragmentIdentifier();
864     return referrer.string();
865 }
866
867 bool URL::isLocalFile() const
868 {
869     // Including feed here might be a bad idea since drag and drop uses this check
870     // and including feed would allow feeds to potentially let someone's blog
871     // read the contents of the clipboard on a drag, even without a drop.
872     // Likewise with using the FrameLoader::shouldTreatURLAsLocal() function.
873     return protocolIs("file");
874 }
875
876 bool protocolIsJavaScript(const String& url)
877 {
878     return protocolIsInternal(url, "javascript");
879 }
880
881 bool protocolIsJavaScript(StringView url)
882 {
883     return protocolIsInternal(url, "javascript");
884 }
885
886 bool protocolIsInHTTPFamily(const String& url)
887 {
888     auto length = url.length();
889     // Do the comparison without making a new string object.
890     return length >= 5
891         && isASCIIAlphaCaselessEqual(url[0], 'h')
892         && isASCIIAlphaCaselessEqual(url[1], 't')
893         && isASCIIAlphaCaselessEqual(url[2], 't')
894         && isASCIIAlphaCaselessEqual(url[3], 'p')
895         && (url[4] == ':' || (isASCIIAlphaCaselessEqual(url[4], 's') && length >= 6 && url[5] == ':'));
896 }
897
898 const URL& blankURL()
899 {
900     static NeverDestroyed<URL> staticBlankURL(URL(), "about:blank");
901     return staticBlankURL;
902 }
903
904 bool URL::isBlankURL() const
905 {
906     return protocolIs("about");
907 }
908
909 bool portAllowed(const URL& url)
910 {
911     std::optional<uint16_t> port = url.port();
912
913     // Since most URLs don't have a port, return early for the "no port" case.
914     if (!port)
915         return true;
916
917     // This blocked port list matches the port blocking that Mozilla implements.
918     // See http://www.mozilla.org/projects/netlib/PortBanning.html for more information.
919     static const uint16_t blockedPortList[] = {
920         1,    // tcpmux
921         7,    // echo
922         9,    // discard
923         11,   // systat
924         13,   // daytime
925         15,   // netstat
926         17,   // qotd
927         19,   // chargen
928         20,   // FTP-data
929         21,   // FTP-control
930         22,   // SSH
931         23,   // telnet
932         25,   // SMTP
933         37,   // time
934         42,   // name
935         43,   // nicname
936         53,   // domain
937         77,   // priv-rjs
938         79,   // finger
939         87,   // ttylink
940         95,   // supdup
941         101,  // hostriame
942         102,  // iso-tsap
943         103,  // gppitnp
944         104,  // acr-nema
945         109,  // POP2
946         110,  // POP3
947         111,  // sunrpc
948         113,  // auth
949         115,  // SFTP
950         117,  // uucp-path
951         119,  // nntp
952         123,  // NTP
953         135,  // loc-srv / epmap
954         139,  // netbios
955         143,  // IMAP2
956         179,  // BGP
957         389,  // LDAP
958         465,  // SMTP+SSL
959         512,  // print / exec
960         513,  // login
961         514,  // shell
962         515,  // printer
963         526,  // tempo
964         530,  // courier
965         531,  // Chat
966         532,  // netnews
967         540,  // UUCP
968         548,  // afpovertcp [Apple addition]
969         556,  // remotefs
970         563,  // NNTP+SSL
971         587,  // ESMTP
972         601,  // syslog-conn
973         636,  // LDAP+SSL
974         993,  // IMAP+SSL
975         995,  // POP3+SSL
976         2049, // NFS
977         3659, // apple-sasl / PasswordServer [Apple addition]
978         4045, // lockd
979         4190, // ManageSieve [Apple addition]
980         6000, // X11
981         6665, // Alternate IRC [Apple addition]
982         6666, // Alternate IRC [Apple addition]
983         6667, // Standard IRC [Apple addition]
984         6668, // Alternate IRC [Apple addition]
985         6669, // Alternate IRC [Apple addition]
986         6679, // Alternate IRC SSL [Apple addition]
987         6697, // IRC+SSL [Apple addition]
988         invalidPortNumber, // Used to block all invalid port numbers
989     };
990
991     // If the port is not in the blocked port list, allow it.
992     ASSERT(std::is_sorted(std::begin(blockedPortList), std::end(blockedPortList)));
993     if (!std::binary_search(std::begin(blockedPortList), std::end(blockedPortList), port.value()))
994         return true;
995
996     // Allow ports 21 and 22 for FTP URLs, as Mozilla does.
997     if ((port.value() == 21 || port.value() == 22) && url.protocolIs("ftp"))
998         return true;
999
1000     // Allow any port number in a file URL, since the port number is ignored.
1001     if (url.protocolIs("file"))
1002         return true;
1003
1004     return false;
1005 }
1006
1007 String mimeTypeFromDataURL(const String& url)
1008 {
1009     ASSERT(protocolIsInternal(url, "data"));
1010
1011     // FIXME: What's the right behavior when the URL has a comma first, but a semicolon later?
1012     // Currently this code will break at the semicolon in that case. Not sure that's correct.
1013     auto index = url.find(';', 5);
1014     if (index == notFound)
1015         index = url.find(',', 5);
1016     if (index == notFound) {
1017         // FIXME: There was an old comment here that made it sound like this should be returning text/plain.
1018         // But we have been returning empty string here for some time, so not changing its behavior at this time.
1019         return emptyString();
1020     }
1021     if (index == 5)
1022         return "text/plain"_s;
1023     ASSERT(index >= 5);
1024     return url.substring(5, index - 5).convertToASCIILowercase();
1025 }
1026
1027 String URL::stringCenterEllipsizedToLength(unsigned length) const
1028 {
1029     if (string().length() <= length)
1030         return string();
1031
1032     return string().left(length / 2 - 1) + "..." + string().right(length / 2 - 2);
1033 }
1034
1035 URL URL::fakeURLWithRelativePart(const String& relativePart)
1036 {
1037     return URL(URL(), "webkit-fake-url://" + createCanonicalUUIDString() + '/' + relativePart);
1038 }
1039
1040 URL URL::fileURLWithFileSystemPath(const String& filePath)
1041 {
1042     return URL(URL(), "file:///" + filePath);
1043 }
1044
1045 TextStream& operator<<(TextStream& ts, const URL& url)
1046 {
1047     ts << url.string();
1048     return ts;
1049 }
1050
1051 #if !PLATFORM(COCOA) && !USE(SOUP)
1052 static bool isIPv4Address(StringView string)
1053 {
1054     auto count = 0;
1055
1056     for (const auto octet : string.splitAllowingEmptyEntries('.')) {
1057         if (count >= 4)
1058             return false;
1059
1060         const auto length = octet.length();
1061         if (!length || length > 3)
1062             return false;
1063
1064         auto value = 0;
1065         for (auto i = 0u; i < length; ++i) {
1066             const auto digit = octet[i];
1067
1068             // Prohibit leading zeroes.
1069             if (digit > '9' || digit < (!i && length > 1 ? '1' : '0'))
1070                 return false;
1071
1072             value = 10 * value + (digit - '0');
1073         }
1074
1075         if (value > 255)
1076             return false;
1077
1078         count++;
1079     }
1080
1081     return (count == 4);
1082 }
1083
1084 static bool isIPv6Address(StringView string)
1085 {
1086     enum SkipState { None, WillSkip, Skipping, Skipped, Final };
1087     auto skipState = None;
1088     auto count = 0;
1089
1090     for (const auto hextet : string.splitAllowingEmptyEntries(':')) {
1091         if (count >= 8 || skipState == Final)
1092             return false;
1093
1094         const auto length = hextet.length();
1095         if (!length) {
1096             // :: may be used anywhere to skip 1 to 8 hextets, but only once.
1097             if (skipState == Skipped)
1098                 return false;
1099
1100             if (skipState == None)
1101                 skipState = !count ? WillSkip : Skipping;
1102             else if (skipState == WillSkip)
1103                 skipState = Skipping;
1104             else
1105                 skipState = Final;
1106             continue;
1107         }
1108
1109         if (skipState == WillSkip)
1110             return false;
1111
1112         if (skipState == Skipping)
1113             skipState = Skipped;
1114
1115         if (length > 4) {
1116             // An IPv4 address may be used in place of the final two hextets.
1117             if ((skipState == None && count != 6) || (skipState == Skipped && count >= 6) || !isIPv4Address(hextet))
1118                 return false;
1119
1120             skipState = Final;
1121             continue;
1122         }
1123
1124         for (const auto codeUnit : hextet.codeUnits()) {
1125             // IPv6 allows leading zeroes.
1126             if (!isASCIIHexDigit(codeUnit))
1127                 return false;
1128         }
1129
1130         count++;
1131     }
1132
1133     return (count == 8 && skipState == None) || skipState == Skipped || skipState == Final;
1134 }
1135
1136 bool URL::hostIsIPAddress(StringView host)
1137 {
1138     if (host.find(':') == notFound)
1139         return isIPv4Address(host);
1140
1141     return isIPv6Address(host);
1142 }
1143 #endif
1144
1145 } // namespace WebCore