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