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