f54e928354eae1e217615e108d703e388458259a
[WebKit-https.git] / Source / WTF / wtf / cocoa / NSURLExtras.mm
1 /*
2  * Copyright (C) 2005, 2007, 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "config.h"
30 #import <Foundation/Foundation.h>
31 #import "NSURLExtras.h"
32
33 #import <unicode/uchar.h>
34 #import <unicode/uidna.h>
35 #import <unicode/unorm.h>
36 #import <unicode/uscript.h>
37 #import <wtf/Function.h>
38 #import <wtf/HexNumber.h>
39 #import <wtf/ObjCRuntimeExtras.h>
40 #import <wtf/RetainPtr.h>
41 #import <wtf/URLParser.h>
42 #import <wtf/Vector.h>
43 #import <wtf/cf/CFURLExtras.h>
44
45 // Needs to be big enough to hold an IDN-encoded name.
46 // For host names bigger than this, we won't do IDN encoding, which is almost certainly OK.
47 #define HOST_NAME_BUFFER_LENGTH 2048
48 #define URL_BYTES_BUFFER_LENGTH 2048
49
50 typedef void (* StringRangeApplierFunction)(NSString *, NSRange, RetainPtr<NSMutableArray>&);
51
52 static uint32_t IDNScriptWhiteList[(USCRIPT_CODE_LIMIT + 31) / 32];
53
54 namespace WTF {
55
56 static bool isArmenianLookalikeCharacter(UChar32 codePoint)
57 {
58     return codePoint == 0x0548 || codePoint == 0x054D || codePoint == 0x0578 || codePoint == 0x057D;
59 }
60
61 static bool isArmenianScriptCharacter(UChar32 codePoint)
62 {
63     UErrorCode error = U_ZERO_ERROR;
64     UScriptCode script = uscript_getScript(codePoint, &error);
65     if (error != U_ZERO_ERROR) {
66         LOG_ERROR("got ICU error while trying to look at scripts: %d", error);
67         return false;
68     }
69
70     return script == USCRIPT_ARMENIAN;
71 }
72
73
74 template<typename CharacterType> inline bool isASCIIDigitOrValidHostCharacter(CharacterType charCode)
75 {
76     if (!isASCIIDigitOrPunctuation(charCode))
77         return false;
78
79     // Things the URL Parser rejects:
80     switch (charCode) {
81     case '#':
82     case '%':
83     case '/':
84     case ':':
85     case '?':
86     case '@':
87     case '[':
88     case '\\':
89     case ']':
90         return false;
91     default:
92         return true;
93     }
94 }
95
96 static BOOL isLookalikeCharacter(Optional<UChar32> previousCodePoint, UChar32 charCode)
97 {
98     // This function treats the following as unsafe, lookalike characters:
99     // any non-printable character, any character considered as whitespace,
100     // any ignorable character, and emoji characters related to locks.
101     
102     // We also considered the characters in Mozilla's blacklist <http://kb.mozillazine.org/Network.IDN.blacklist_chars>.
103
104     // Some of the characters here will never appear once ICU has encoded.
105     // For example, ICU transforms most spaces into an ASCII space and most
106     // slashes into an ASCII solidus. But one of the two callers uses this
107     // on characters that have not been processed by ICU, so they are needed here.
108     
109     if (!u_isprint(charCode) || u_isUWhiteSpace(charCode) || u_hasBinaryProperty(charCode, UCHAR_DEFAULT_IGNORABLE_CODE_POINT))
110         return YES;
111     
112     switch (charCode) {
113         case 0x00BC: /* VULGAR FRACTION ONE QUARTER */
114         case 0x00BD: /* VULGAR FRACTION ONE HALF */
115         case 0x00BE: /* VULGAR FRACTION THREE QUARTERS */
116         case 0x00ED: /* LATIN SMALL LETTER I WITH ACUTE */
117         /* 0x0131 LATIN SMALL LETTER DOTLESS I is intentionally not considered a lookalike character because it is visually distinguishable from i and it has legitimate use in the Turkish language. */
118         case 0x01C3: /* LATIN LETTER RETROFLEX CLICK */
119         case 0x0251: /* LATIN SMALL LETTER ALPHA */
120         case 0x0261: /* LATIN SMALL LETTER SCRIPT G */
121         case 0x027E: /* LATIN SMALL LETTER R WITH FISHHOOK */
122         case 0x02D0: /* MODIFIER LETTER TRIANGULAR COLON */
123         case 0x0335: /* COMBINING SHORT STROKE OVERLAY */
124         case 0x0337: /* COMBINING SHORT SOLIDUS OVERLAY */
125         case 0x0338: /* COMBINING LONG SOLIDUS OVERLAY */
126         case 0x0589: /* ARMENIAN FULL STOP */
127         case 0x05B4: /* HEBREW POINT HIRIQ */
128         case 0x05BC: /* HEBREW POINT DAGESH OR MAPIQ */
129         case 0x05C3: /* HEBREW PUNCTUATION SOF PASUQ */
130         case 0x05F4: /* HEBREW PUNCTUATION GERSHAYIM */
131         case 0x0609: /* ARABIC-INDIC PER MILLE SIGN */
132         case 0x060A: /* ARABIC-INDIC PER TEN THOUSAND SIGN */
133         case 0x0650: /* ARABIC KASRA */
134         case 0x0660: /* ARABIC INDIC DIGIT ZERO */
135         case 0x066A: /* ARABIC PERCENT SIGN */
136         case 0x06D4: /* ARABIC FULL STOP */
137         case 0x06F0: /* EXTENDED ARABIC INDIC DIGIT ZERO */
138         case 0x0701: /* SYRIAC SUPRALINEAR FULL STOP */
139         case 0x0702: /* SYRIAC SUBLINEAR FULL STOP */
140         case 0x0703: /* SYRIAC SUPRALINEAR COLON */
141         case 0x0704: /* SYRIAC SUBLINEAR COLON */
142         case 0x1735: /* PHILIPPINE SINGLE PUNCTUATION */
143         case 0x1D04: /* LATIN LETTER SMALL CAPITAL C */
144         case 0x1D0F: /* LATIN LETTER SMALL CAPITAL O */
145         case 0x1D1C: /* LATIN LETTER SMALL CAPITAL U */
146         case 0x1D20: /* LATIN LETTER SMALL CAPITAL V */
147         case 0x1D21: /* LATIN LETTER SMALL CAPITAL W */
148         case 0x1D22: /* LATIN LETTER SMALL CAPITAL Z */
149         case 0x1ECD: /* LATIN SMALL LETTER O WITH DOT BELOW */
150         case 0x2010: /* HYPHEN */
151         case 0x2011: /* NON-BREAKING HYPHEN */
152         case 0x2024: /* ONE DOT LEADER */
153         case 0x2027: /* HYPHENATION POINT */
154         case 0x2039: /* SINGLE LEFT-POINTING ANGLE QUOTATION MARK */
155         case 0x203A: /* SINGLE RIGHT-POINTING ANGLE QUOTATION MARK */
156         case 0x2041: /* CARET INSERTION POINT */
157         case 0x2044: /* FRACTION SLASH */
158         case 0x2052: /* COMMERCIAL MINUS SIGN */
159         case 0x2153: /* VULGAR FRACTION ONE THIRD */
160         case 0x2154: /* VULGAR FRACTION TWO THIRDS */
161         case 0x2155: /* VULGAR FRACTION ONE FIFTH */
162         case 0x2156: /* VULGAR FRACTION TWO FIFTHS */
163         case 0x2157: /* VULGAR FRACTION THREE FIFTHS */
164         case 0x2158: /* VULGAR FRACTION FOUR FIFTHS */
165         case 0x2159: /* VULGAR FRACTION ONE SIXTH */
166         case 0x215A: /* VULGAR FRACTION FIVE SIXTHS */
167         case 0x215B: /* VULGAR FRACTION ONE EIGHT */
168         case 0x215C: /* VULGAR FRACTION THREE EIGHTHS */
169         case 0x215D: /* VULGAR FRACTION FIVE EIGHTHS */
170         case 0x215E: /* VULGAR FRACTION SEVEN EIGHTHS */
171         case 0x215F: /* FRACTION NUMERATOR ONE */
172         case 0x2212: /* MINUS SIGN */
173         case 0x2215: /* DIVISION SLASH */
174         case 0x2216: /* SET MINUS */
175         case 0x2236: /* RATIO */
176         case 0x233F: /* APL FUNCTIONAL SYMBOL SLASH BAR */
177         case 0x23AE: /* INTEGRAL EXTENSION */
178         case 0x244A: /* OCR DOUBLE BACKSLASH */
179         case 0x2571: /* DisplayType::Box DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT */
180         case 0x2572: /* DisplayType::Box DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT */
181         case 0x29F6: /* SOLIDUS WITH OVERBAR */
182         case 0x29F8: /* BIG SOLIDUS */
183         case 0x2AFB: /* TRIPLE SOLIDUS BINARY RELATION */
184         case 0x2AFD: /* DOUBLE SOLIDUS OPERATOR */
185         case 0x2FF0: /* IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT */
186         case 0x2FF1: /* IDEOGRAPHIC DESCRIPTION CHARACTER ABOVE TO BELOW */
187         case 0x2FF2: /* IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO MIDDLE AND RIGHT */
188         case 0x2FF3: /* IDEOGRAPHIC DESCRIPTION CHARACTER ABOVE TO MIDDLE AND BELOW */
189         case 0x2FF4: /* IDEOGRAPHIC DESCRIPTION CHARACTER FULL SURROUND */
190         case 0x2FF5: /* IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM ABOVE */
191         case 0x2FF6: /* IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM BELOW */
192         case 0x2FF7: /* IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM LEFT */
193         case 0x2FF8: /* IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM UPPER LEFT */
194         case 0x2FF9: /* IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM UPPER RIGHT */
195         case 0x2FFA: /* IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM LOWER LEFT */
196         case 0x2FFB: /* IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID */
197         case 0x3002: /* IDEOGRAPHIC FULL STOP */
198         case 0x3008: /* LEFT ANGLE BRACKET */
199         case 0x3014: /* LEFT TORTOISE SHELL BRACKET */
200         case 0x3015: /* RIGHT TORTOISE SHELL BRACKET */
201         case 0x3033: /* VERTICAL KANA REPEAT MARK UPPER HALF */
202         case 0x3035: /* VERTICAL KANA REPEAT MARK LOWER HALF */
203         case 0x321D: /* PARENTHESIZED KOREAN CHARACTER OJEON */
204         case 0x321E: /* PARENTHESIZED KOREAN CHARACTER O HU */
205         case 0x33AE: /* SQUARE RAD OVER S */
206         case 0x33AF: /* SQUARE RAD OVER S SQUARED */
207         case 0x33C6: /* SQUARE C OVER KG */
208         case 0x33DF: /* SQUARE A OVER M */
209         case 0x05B9: /* HEBREW POINT HOLAM */
210         case 0x05BA: /* HEBREW POINT HOLAM HASER FOR VAV */
211         case 0x05C1: /* HEBREW POINT SHIN DOT */
212         case 0x05C2: /* HEBREW POINT SIN DOT */
213         case 0x05C4: /* HEBREW MARK UPPER DOT */
214         case 0xA731: /* LATIN LETTER SMALL CAPITAL S */
215         case 0xA771: /* LATIN SMALL LETTER DUM */
216         case 0xA789: /* MODIFIER LETTER COLON */
217         case 0xFE14: /* PRESENTATION FORM FOR VERTICAL SEMICOLON */
218         case 0xFE15: /* PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK */
219         case 0xFE3F: /* PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET */
220         case 0xFE5D: /* SMALL LEFT TORTOISE SHELL BRACKET */
221         case 0xFE5E: /* SMALL RIGHT TORTOISE SHELL BRACKET */
222         case 0xFF0E: /* FULLWIDTH FULL STOP */
223         case 0xFF0F: /* FULL WIDTH SOLIDUS */
224         case 0xFF61: /* HALFWIDTH IDEOGRAPHIC FULL STOP */
225         case 0xFFFC: /* OBJECT REPLACEMENT CHARACTER */
226         case 0xFFFD: /* REPLACEMENT CHARACTER */
227         case 0x1F50F: /* LOCK WITH INK PEN */
228         case 0x1F510: /* CLOSED LOCK WITH KEY */
229         case 0x1F511: /* KEY */
230         case 0x1F512: /* LOCK */
231         case 0x1F513: /* OPEN LOCK */
232             return YES;
233         case 0x0307: /* COMBINING DOT ABOVE */
234             return previousCodePoint == 0x0237 /* LATIN SMALL LETTER DOTLESS J */
235                 || previousCodePoint == 0x0131 /* LATIN SMALL LETTER DOTLESS I */
236                 || previousCodePoint == 0x05D5; /* HEBREW LETTER VAV */
237         case 0x0548: /* ARMENIAN CAPITAL LETTER VO */
238         case 0x054D: /* ARMENIAN CAPITAL LETTER SEH */
239         case 0x0578: /* ARMENIAN SMALL LETTER VO */
240         case 0x057D: /* ARMENIAN SMALL LETTER SEH */
241             return previousCodePoint
242                 && !isASCIIDigitOrValidHostCharacter(previousCodePoint.value())
243                 && !isArmenianScriptCharacter(previousCodePoint.value());
244         case '.':
245             return NO;
246         default:
247             return previousCodePoint
248                 && isArmenianLookalikeCharacter(previousCodePoint.value())
249                 && !(isArmenianScriptCharacter(charCode) || isASCIIDigitOrValidHostCharacter(charCode));
250     }
251 }
252
253 static void whiteListIDNScript(const char* scriptName)
254 {
255     int32_t script = u_getPropertyValueEnum(UCHAR_SCRIPT, scriptName);
256     if (script >= 0 && script < USCRIPT_CODE_LIMIT) {
257         size_t index = script / 32;
258         uint32_t mask = 1 << (script % 32);
259         IDNScriptWhiteList[index] |= mask;
260     }
261 }
262
263 static BOOL readIDNScriptWhiteListFile(NSString *filename)
264 {
265     if (!filename)
266         return NO;
267
268     FILE *file = fopen([filename fileSystemRepresentation], "r");
269     if (!file)
270         return NO;
271     
272     // Read a word at a time.
273     // Allow comments, starting with # character to the end of the line.
274     while (1) {
275         // Skip a comment if present.
276         if (fscanf(file, " #%*[^\n\r]%*[\n\r]") == EOF)
277             break;
278         
279         // Read a script name if present.
280         char word[33];
281         int result = fscanf(file, " %32[^# \t\n\r]%*[^# \t\n\r] ", word);
282         if (result == EOF)
283             break;
284         
285         if (result == 1) {
286             // Got a word, map to script code and put it into the array.
287             whiteListIDNScript(word);
288         }
289     }
290     fclose(file);
291     return YES;
292 }
293
294 static BOOL allCharactersInIDNScriptWhiteList(const UChar *buffer, int32_t length)
295 {
296     static dispatch_once_t flag;
297     dispatch_once(&flag, ^{
298         // Read white list from library.
299         NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES);
300         int numDirs = [dirs count];
301         for (int i = 0; i < numDirs; i++) {
302             if (readIDNScriptWhiteListFile([[dirs objectAtIndex:i] stringByAppendingPathComponent:@"IDNScriptWhiteList.txt"]))
303                 return;
304         }
305         const char* defaultIDNScriptWhiteList[20] = {
306             "Common",
307             "Inherited",
308             "Arabic",
309             "Armenian",
310             "Bopomofo",
311             "Canadian_Aboriginal",
312             "Devanagari",
313             "Deseret",
314             "Gujarati",
315             "Gurmukhi",
316             "Hangul",
317             "Han",
318             "Hebrew",
319             "Hiragana",
320             "Katakana_Or_Hiragana",
321             "Katakana",
322             "Latin",
323             "Tamil",
324             "Thai",
325             "Yi",
326         };
327         for (const char* scriptName : defaultIDNScriptWhiteList)
328             whiteListIDNScript(scriptName);
329     });
330     
331     int32_t i = 0;
332     Optional<UChar32> previousCodePoint;
333     while (i < length) {
334         UChar32 c;
335         U16_NEXT(buffer, i, length, c)
336         UErrorCode error = U_ZERO_ERROR;
337         UScriptCode script = uscript_getScript(c, &error);
338         if (error != U_ZERO_ERROR) {
339             LOG_ERROR("got ICU error while trying to look at scripts: %d", error);
340             return NO;
341         }
342         if (script < 0) {
343             LOG_ERROR("got negative number for script code from ICU: %d", script);
344             return NO;
345         }
346         if (script >= USCRIPT_CODE_LIMIT)
347             return NO;
348
349         size_t index = script / 32;
350         uint32_t mask = 1 << (script % 32);
351         if (!(IDNScriptWhiteList[index] & mask))
352             return NO;
353         
354         if (isLookalikeCharacter(previousCodePoint, c))
355             return NO;
356         previousCodePoint = c;
357     }
358     return YES;
359 }
360
361 static bool isSecondLevelDomainNameAllowedByTLDRules(const UChar* buffer, int32_t length, const WTF::Function<bool(UChar)>& characterIsAllowed)
362 {
363     ASSERT(length > 0);
364
365     for (int32_t i = length - 1; i >= 0; --i) {
366         UChar ch = buffer[i];
367         
368         if (characterIsAllowed(ch))
369             continue;
370         
371         // Only check the second level domain. Lower level registrars may have different rules.
372         if (ch == '.')
373             break;
374         
375         return false;
376     }
377     return true;
378 }
379
380 #define CHECK_RULES_IF_SUFFIX_MATCHES(suffix, function) \
381     { \
382         static const int32_t suffixLength = sizeof(suffix) / sizeof(suffix[0]); \
383         if (length > suffixLength && 0 == memcmp(buffer + length - suffixLength, suffix, sizeof(suffix))) \
384             return isSecondLevelDomainNameAllowedByTLDRules(buffer, length - suffixLength, function); \
385     }
386
387 static bool isRussianDomainNameCharacter(UChar ch)
388 {
389     // Only modern Russian letters, digits and dashes are allowed.
390     return (ch >= 0x0430 && ch <= 0x044f) || ch == 0x0451 || isASCIIDigit(ch) || ch == '-';
391 }
392
393 static BOOL allCharactersAllowedByTLDRules(const UChar* buffer, int32_t length)
394 {
395     // Skip trailing dot for root domain.
396     if (buffer[length - 1] == '.')
397         length--;
398
399     // http://cctld.ru/files/pdf/docs/rules_ru-rf.pdf
400     static const UChar cyrillicRF[] = {
401         '.',
402         0x0440, // CYRILLIC SMALL LETTER ER
403         0x0444  // CYRILLIC SMALL LETTER EF
404     };
405     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicRF, isRussianDomainNameCharacter);
406
407     // http://rusnames.ru/rules.pl
408     static const UChar cyrillicRUS[] = {
409         '.',
410         0x0440, // CYRILLIC SMALL LETTER ER
411         0x0443, // CYRILLIC SMALL LETTER U
412         0x0441  // CYRILLIC SMALL LETTER ES
413     };
414     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicRUS, isRussianDomainNameCharacter);
415
416     // http://ru.faitid.org/projects/moscow/documents/moskva/idn
417     static const UChar cyrillicMOSKVA[] = {
418         '.',
419         0x043C, // CYRILLIC SMALL LETTER EM
420         0x043E, // CYRILLIC SMALL LETTER O
421         0x0441, // CYRILLIC SMALL LETTER ES
422         0x043A, // CYRILLIC SMALL LETTER KA
423         0x0432, // CYRILLIC SMALL LETTER VE
424         0x0430  // CYRILLIC SMALL LETTER A
425     };
426     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicMOSKVA, isRussianDomainNameCharacter);
427
428     // http://www.dotdeti.ru/foruser/docs/regrules.php
429     static const UChar cyrillicDETI[] = {
430         '.',
431         0x0434, // CYRILLIC SMALL LETTER DE
432         0x0435, // CYRILLIC SMALL LETTER IE
433         0x0442, // CYRILLIC SMALL LETTER TE
434         0x0438  // CYRILLIC SMALL LETTER I
435     };
436     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicDETI, isRussianDomainNameCharacter);
437
438     // http://corenic.org - rules not published. The word is Russian, so only allowing Russian at this time,
439     // although we may need to revise the checks if this ends up being used with other languages spoken in Russia.
440     static const UChar cyrillicONLAYN[] = {
441         '.',
442         0x043E, // CYRILLIC SMALL LETTER O
443         0x043D, // CYRILLIC SMALL LETTER EN
444         0x043B, // CYRILLIC SMALL LETTER EL
445         0x0430, // CYRILLIC SMALL LETTER A
446         0x0439, // CYRILLIC SMALL LETTER SHORT I
447         0x043D  // CYRILLIC SMALL LETTER EN
448     };
449     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicONLAYN, isRussianDomainNameCharacter);
450
451     // http://corenic.org - same as above.
452     static const UChar cyrillicSAYT[] = {
453         '.',
454         0x0441, // CYRILLIC SMALL LETTER ES
455         0x0430, // CYRILLIC SMALL LETTER A
456         0x0439, // CYRILLIC SMALL LETTER SHORT I
457         0x0442  // CYRILLIC SMALL LETTER TE
458     };
459     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicSAYT, isRussianDomainNameCharacter);
460
461     // http://pir.org/products/opr-domain/ - rules not published. According to the registry site,
462     // the intended audience is "Russian and other Slavic-speaking markets".
463     // Chrome appears to only allow Russian, so sticking with that for now.
464     static const UChar cyrillicORG[] = {
465         '.',
466         0x043E, // CYRILLIC SMALL LETTER O
467         0x0440, // CYRILLIC SMALL LETTER ER
468         0x0433  // CYRILLIC SMALL LETTER GHE
469     };
470     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicORG, isRussianDomainNameCharacter);
471
472     // http://cctld.by/rules.html
473     static const UChar cyrillicBEL[] = {
474         '.',
475         0x0431, // CYRILLIC SMALL LETTER BE
476         0x0435, // CYRILLIC SMALL LETTER IE
477         0x043B  // CYRILLIC SMALL LETTER EL
478     };
479     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicBEL, [](UChar ch) {
480         // Russian and Byelorussian letters, digits and dashes are allowed.
481         return (ch >= 0x0430 && ch <= 0x044f) || ch == 0x0451 || ch == 0x0456 || ch == 0x045E || ch == 0x2019 || isASCIIDigit(ch) || ch == '-';
482     });
483
484     // http://www.nic.kz/docs/poryadok_vnedreniya_kaz_ru.pdf
485     static const UChar cyrillicKAZ[] = {
486         '.',
487         0x049B, // CYRILLIC SMALL LETTER KA WITH DESCENDER
488         0x0430, // CYRILLIC SMALL LETTER A
489         0x0437  // CYRILLIC SMALL LETTER ZE
490     };
491     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicKAZ, [](UChar ch) {
492         // Kazakh letters, digits and dashes are allowed.
493         return (ch >= 0x0430 && ch <= 0x044f) || ch == 0x0451 || ch == 0x04D9 || ch == 0x0493 || ch == 0x049B || ch == 0x04A3 || ch == 0x04E9 || ch == 0x04B1 || ch == 0x04AF || ch == 0x04BB || ch == 0x0456 || isASCIIDigit(ch) || ch == '-';
494     });
495
496     // http://uanic.net/docs/documents-ukr/Rules%20of%20UKR_v4.0.pdf
497     static const UChar cyrillicUKR[] = {
498         '.',
499         0x0443, // CYRILLIC SMALL LETTER U
500         0x043A, // CYRILLIC SMALL LETTER KA
501         0x0440  // CYRILLIC SMALL LETTER ER
502     };
503     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicUKR, [](UChar ch) {
504         // Russian and Ukrainian letters, digits and dashes are allowed.
505         return (ch >= 0x0430 && ch <= 0x044f) || ch == 0x0451 || ch == 0x0491 || ch == 0x0404 || ch == 0x0456 || ch == 0x0457 || isASCIIDigit(ch) || ch == '-';
506     });
507
508     // http://www.rnids.rs/data/DOKUMENTI/idn-srb-policy-termsofuse-v1.4-eng.pdf
509     static const UChar cyrillicSRB[] = {
510         '.',
511         0x0441, // CYRILLIC SMALL LETTER ES
512         0x0440, // CYRILLIC SMALL LETTER ER
513         0x0431  // CYRILLIC SMALL LETTER BE
514     };
515     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicSRB, [](UChar ch) {
516         // Serbian letters, digits and dashes are allowed.
517         return (ch >= 0x0430 && ch <= 0x0438) || (ch >= 0x043A && ch <= 0x0448) || ch == 0x0452 || ch == 0x0458 || ch == 0x0459 || ch == 0x045A || ch == 0x045B || ch == 0x045F || isASCIIDigit(ch) || ch == '-';
518     });
519
520     // http://marnet.mk/doc/pravilnik-mk-mkd.pdf
521     static const UChar cyrillicMKD[] = {
522         '.',
523         0x043C, // CYRILLIC SMALL LETTER EM
524         0x043A, // CYRILLIC SMALL LETTER KA
525         0x0434  // CYRILLIC SMALL LETTER DE
526     };
527     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicMKD, [](UChar ch) {
528         // Macedonian letters, digits and dashes are allowed.
529         return (ch >= 0x0430 && ch <= 0x0438) || (ch >= 0x043A && ch <= 0x0448) || ch == 0x0453 || ch == 0x0455 || ch == 0x0458 || ch == 0x0459 || ch == 0x045A || ch == 0x045C || ch == 0x045F || isASCIIDigit(ch) || ch == '-';
530     });
531
532     // https://www.mon.mn/cs/
533     static const UChar cyrillicMON[] = {
534         '.',
535         0x043C, // CYRILLIC SMALL LETTER EM
536         0x043E, // CYRILLIC SMALL LETTER O
537         0x043D  // CYRILLIC SMALL LETTER EN
538     };
539     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicMON, [](UChar ch) {
540         // Mongolian letters, digits and dashes are allowed.
541         return (ch >= 0x0430 && ch <= 0x044f) || ch == 0x0451 || ch == 0x04E9 || ch == 0x04AF || isASCIIDigit(ch) || ch == '-';
542     });
543
544     // https://www.icann.org/sites/default/files/packages/lgr/lgr-second-level-bulgarian-30aug16-en.html
545     static const UChar cyrillicBG[] = {
546         '.',
547         0x0431, // CYRILLIC SMALL LETTER BE
548         0x0433 // CYRILLIC SMALL LETTER GHE
549     };
550     CHECK_RULES_IF_SUFFIX_MATCHES(cyrillicBG, [](UChar ch) {
551         return (ch >= 0x0430 && ch <= 0x044A) || ch == 0x044C || (ch >= 0x044E && ch <= 0x0450) || ch == 0x045D || isASCIIDigit(ch) || ch == '-';
552     });
553
554     // Not a known top level domain with special rules.
555     return NO;
556 }
557
558 // Return value of nil means no mapping is necessary.
559 // If makeString is NO, then return value is either nil or self to indicate mapping is necessary.
560 // If makeString is YES, then return value is either nil or the mapped string.
561 static NSString *mapHostNameWithRange(NSString *string, NSRange range, BOOL encode, BOOL makeString, BOOL *error)
562 {
563     if (range.length > HOST_NAME_BUFFER_LENGTH)
564         return nil;
565     
566     if (![string length])
567         return nil;
568     
569     UChar sourceBuffer[HOST_NAME_BUFFER_LENGTH];
570     UChar destinationBuffer[HOST_NAME_BUFFER_LENGTH];
571     
572     if (encode && [string rangeOfString:@"%" options:NSLiteralSearch range:range].location != NSNotFound) {
573         NSString *substring = [string substringWithRange:range];
574         substring = CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(nullptr, (CFStringRef)substring, CFSTR("")));
575         if (substring) {
576             string = substring;
577             range = NSMakeRange(0, [string length]);
578         }
579     }
580     
581     int length = range.length;
582     [string getCharacters:sourceBuffer range:range];
583     
584     UErrorCode uerror = U_ZERO_ERROR;
585     UIDNAInfo processingDetails = UIDNA_INFO_INITIALIZER;
586     int32_t numCharactersConverted = (encode ? uidna_nameToASCII : uidna_nameToUnicode)(&URLParser::internationalDomainNameTranscoder(), sourceBuffer, length, destinationBuffer, HOST_NAME_BUFFER_LENGTH, &processingDetails, &uerror);
587     if (length && (U_FAILURE(uerror) || processingDetails.errors)) {
588         *error = YES;
589         return nil;
590     }
591     
592     if (numCharactersConverted == length && !memcmp(sourceBuffer, destinationBuffer, length * sizeof(UChar)))
593         return nil;
594     
595     if (!encode && !allCharactersInIDNScriptWhiteList(destinationBuffer, numCharactersConverted) && !allCharactersAllowedByTLDRules(destinationBuffer, numCharactersConverted))
596         return nil;
597     
598     return makeString ? [NSString stringWithCharacters:destinationBuffer length:numCharactersConverted] : string;
599 }
600
601 static BOOL hostNameNeedsDecodingWithRange(NSString *string, NSRange range, BOOL *error)
602 {
603     return mapHostNameWithRange(string, range, NO, NO, error) != nil;
604 }
605  
606 static BOOL hostNameNeedsEncodingWithRange(NSString *string, NSRange range, BOOL *error)
607 {
608     return mapHostNameWithRange(string, range, YES,  NO, error) != nil;
609 }
610
611 static NSString *decodeHostNameWithRange(NSString *string, NSRange range)
612 {
613     BOOL error = NO;
614     NSString *host = mapHostNameWithRange(string, range, NO, YES, &error);
615     if (error)
616         return nil;
617     return !host ? string : host;
618 }
619
620 static NSString *encodeHostNameWithRange(NSString *string, NSRange range)
621 {
622     BOOL error = NO;
623     NSString *host = mapHostNameWithRange(string, range, YES, YES, &error);
624     if (error)
625         return nil;
626     return !host ? string : host;
627 }
628
629 NSString *decodeHostName(NSString *string)
630 {
631     BOOL error = NO;
632     NSString *host = mapHostNameWithRange(string, NSMakeRange(0, [string length]), NO, YES, &error);
633     if (error)
634         return nil;
635     return !host ? string : host;
636 }
637
638 NSString *encodeHostName(NSString *string)
639 {
640     BOOL error = NO;
641     NSString *host = mapHostNameWithRange(string, NSMakeRange(0, [string length]), YES, YES, &error);
642     if (error)
643         return nil;
644     return !host ? string : host;
645 }
646
647 static void collectRangesThatNeedMapping(NSString *string, NSRange range, RetainPtr<NSMutableArray>& array, BOOL encode)
648 {
649     // Generally, we want to optimize for the case where there is one host name that does not need mapping.
650     // Therefore, we use nil to indicate no mapping here and an empty array to indicate error.
651
652     BOOL error = NO;
653     BOOL needsMapping = encode ? hostNameNeedsEncodingWithRange(string, range, &error) : hostNameNeedsDecodingWithRange(string, range, &error);
654     if (!error && !needsMapping)
655         return;
656     
657     if (!array)
658         array = adoptNS([NSMutableArray new]);
659
660     if (!error)
661         [array addObject:[NSValue valueWithRange:range]];
662 }
663
664 static void collectRangesThatNeedEncoding(NSString *string, NSRange range, RetainPtr<NSMutableArray>& array)
665 {
666     return collectRangesThatNeedMapping(string, range, array, YES);
667 }
668
669 static void collectRangesThatNeedDecoding(NSString *string, NSRange range, RetainPtr<NSMutableArray>& array)
670 {
671     return collectRangesThatNeedMapping(string, range, array, NO);
672 }
673
674 static void applyHostNameFunctionToMailToURLString(NSString *string, StringRangeApplierFunction f, RetainPtr<NSMutableArray>& array)
675 {
676     // In a mailto: URL, host names come after a '@' character and end with a '>' or ',' or '?' character.
677     // Skip quoted strings so that characters in them don't confuse us.
678     // When we find a '?' character, we are past the part of the URL that contains host names.
679     
680     static NeverDestroyed<RetainPtr<NSCharacterSet>> hostNameOrStringStartCharacters = [NSCharacterSet characterSetWithCharactersInString:@"\"@?"];
681     static NeverDestroyed<RetainPtr<NSCharacterSet>> hostNameEndCharacters = [NSCharacterSet characterSetWithCharactersInString:@">,?"];
682     static NeverDestroyed<RetainPtr<NSCharacterSet>> quotedStringCharacters = [NSCharacterSet characterSetWithCharactersInString:@"\"\\"];
683     
684     unsigned stringLength = [string length];
685     NSRange remaining = NSMakeRange(0, stringLength);
686     
687     while (1) {
688         // Find start of host name or of quoted string.
689         NSRange hostNameOrStringStart = [string rangeOfCharacterFromSet:hostNameOrStringStartCharacters.get().get() options:0 range:remaining];
690         if (hostNameOrStringStart.location == NSNotFound)
691             return;
692
693         unichar c = [string characterAtIndex:hostNameOrStringStart.location];
694         remaining.location = NSMaxRange(hostNameOrStringStart);
695         remaining.length = stringLength - remaining.location;
696         
697         if (c == '?')
698             return;
699         
700         if (c == '@') {
701             // Find end of host name.
702             unsigned hostNameStart = remaining.location;
703             NSRange hostNameEnd = [string rangeOfCharacterFromSet:hostNameEndCharacters.get().get() options:0 range:remaining];
704             BOOL done;
705             if (hostNameEnd.location == NSNotFound) {
706                 hostNameEnd.location = stringLength;
707                 done = YES;
708             } else {
709                 remaining.location = hostNameEnd.location;
710                 remaining.length = stringLength - remaining.location;
711                 done = NO;
712             }
713             
714             // Process host name range.
715             f(string, NSMakeRange(hostNameStart, hostNameEnd.location - hostNameStart), array);
716             
717             if (done)
718                 return;
719         } else {
720             // Skip quoted string.
721             ASSERT(c == '"');
722             while (1) {
723                 NSRange escapedCharacterOrStringEnd = [string rangeOfCharacterFromSet:quotedStringCharacters.get().get() options:0 range:remaining];
724                 if (escapedCharacterOrStringEnd.location == NSNotFound)
725                     return;
726
727                 c = [string characterAtIndex:escapedCharacterOrStringEnd.location];
728                 remaining.location = NSMaxRange(escapedCharacterOrStringEnd);
729                 remaining.length = stringLength - remaining.location;
730                 
731                 // If we are the end of the string, then break from the string loop back to the host name loop.
732                 if (c == '"')
733                     break;
734                 
735                 // Skip escaped character.
736                 ASSERT(c == '\\');
737                 if (!remaining.length)
738                     return;
739
740                 remaining.location += 1;
741                 remaining.length -= 1;
742             }
743         }
744     }
745 }
746
747 static void applyHostNameFunctionToURLString(NSString *string, StringRangeApplierFunction f, RetainPtr<NSMutableArray>& array)
748 {
749     // Find hostnames. Too bad we can't use any real URL-parsing code to do this,
750     // but we have to do it before doing all the %-escaping, and this is the only
751     // code we have that parses mailto URLs anyway.
752     
753     // Maybe we should implement this using a character buffer instead?
754     
755     if (protocolIs(string, "mailto")) {
756         applyHostNameFunctionToMailToURLString(string, f, array);
757         return;
758     }
759     
760     // Find the host name in a hierarchical URL.
761     // It comes after a "://" sequence, with scheme characters preceding.
762     // If ends with the end of the string or a ":", "/", or a "?".
763     // If there is a "@" character, the host part is just the part after the "@".
764     NSRange separatorRange = [string rangeOfString:@"://"];
765     if (separatorRange.location == NSNotFound)
766         return;
767     
768     // Check that all characters before the :// are valid scheme characters.
769     static NeverDestroyed<RetainPtr<NSCharacterSet>> nonSchemeCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-."] invertedSet];
770     if ([string rangeOfCharacterFromSet:nonSchemeCharacters.get().get() options:0 range:NSMakeRange(0, separatorRange.location)].location != NSNotFound)
771         return;
772     
773     unsigned stringLength = [string length];
774     
775     static NeverDestroyed<RetainPtr<NSCharacterSet>> hostTerminators = [NSCharacterSet characterSetWithCharactersInString:@":/?#"];
776     
777     // Start after the separator.
778     unsigned authorityStart = NSMaxRange(separatorRange);
779     
780     // Find terminating character.
781     NSRange hostNameTerminator = [string rangeOfCharacterFromSet:hostTerminators.get().get() options:0 range:NSMakeRange(authorityStart, stringLength - authorityStart)];
782     unsigned hostNameEnd = hostNameTerminator.location == NSNotFound ? stringLength : hostNameTerminator.location;
783     
784     // Find "@" for the start of the host name.
785     NSRange userInfoTerminator = [string rangeOfString:@"@" options:0 range:NSMakeRange(authorityStart, hostNameEnd - authorityStart)];
786     unsigned hostNameStart = userInfoTerminator.location == NSNotFound ? authorityStart : NSMaxRange(userInfoTerminator);
787     
788     return f(string, NSMakeRange(hostNameStart, hostNameEnd - hostNameStart), array);
789 }
790
791 static RetainPtr<NSString> mapHostNames(NSString *string, BOOL encode)
792 {
793     // Generally, we want to optimize for the case where there is one host name that does not need mapping.
794     
795     if (encode && [string canBeConvertedToEncoding:NSASCIIStringEncoding])
796         return string;
797     
798     // Make a list of ranges that actually need mapping.
799     RetainPtr<NSMutableArray> hostNameRanges;
800     StringRangeApplierFunction f = encode ? collectRangesThatNeedEncoding : collectRangesThatNeedDecoding;
801     applyHostNameFunctionToURLString(string, f, hostNameRanges);
802     if (!hostNameRanges)
803         return string;
804
805     if (![hostNameRanges count])
806         return nil;
807     
808     // Do the mapping.
809     auto mutableCopy = adoptNS([string mutableCopy]);
810     unsigned i = [hostNameRanges count];
811     while (i--) {
812         NSRange hostNameRange = [[hostNameRanges objectAtIndex:i] rangeValue];
813         NSString *mappedHostName = encode ? encodeHostNameWithRange(string, hostNameRange) : decodeHostNameWithRange(string, hostNameRange);
814         [mutableCopy replaceCharactersInRange:hostNameRange withString:mappedHostName];
815     }
816     return mutableCopy;
817 }
818
819 static RetainPtr<NSString> stringByTrimmingWhitespace(NSString *string)
820 {
821     auto trimmed = adoptNS([string mutableCopy]);
822     CFStringTrimWhitespace((__bridge CFMutableStringRef)trimmed.get());
823     return trimmed;
824 }
825
826 NSURL *URLByTruncatingOneCharacterBeforeComponent(NSURL *URL, CFURLComponentType component)
827 {
828     if (!URL)
829         return nil;
830     
831     CFRange fragRg = CFURLGetByteRangeForComponent((__bridge CFURLRef)URL, component, nullptr);
832     if (fragRg.location == kCFNotFound)
833         return URL;
834
835     Vector<UInt8, URL_BYTES_BUFFER_LENGTH> urlBytes(URL_BYTES_BUFFER_LENGTH);
836     CFIndex numBytes = CFURLGetBytes((__bridge CFURLRef)URL, urlBytes.data(), urlBytes.size());
837     if (numBytes == -1) {
838         numBytes = CFURLGetBytes((__bridge CFURLRef)URL, nullptr, 0);
839         urlBytes.grow(numBytes);
840         CFURLGetBytes((__bridge CFURLRef)URL, urlBytes.data(), numBytes);
841     }
842
843     CFURLRef result = CFURLCreateWithBytes(nullptr, urlBytes.data(), fragRg.location - 1, kCFStringEncodingUTF8, nullptr);
844     if (!result)
845         result = CFURLCreateWithBytes(nullptr, urlBytes.data(), fragRg.location - 1, kCFStringEncodingISOLatin1, nullptr);
846         
847     return result ? CFBridgingRelease(result) : URL;
848 }
849
850 static NSURL *URLByRemovingResourceSpecifier(NSURL *URL)
851 {
852     return URLByTruncatingOneCharacterBeforeComponent(URL, kCFURLComponentResourceSpecifier);
853 }
854
855 NSURL *URLWithData(NSData *data, NSURL *baseURL)
856 {
857     if (!data)
858         return nil;
859     
860     NSURL *result = nil;
861     size_t length = [data length];
862     if (length > 0) {
863         // work around <rdar://4470771>: CFURLCreateAbsoluteURLWithBytes(.., TRUE) doesn't remove non-path components.
864         baseURL = URLByRemovingResourceSpecifier(baseURL);
865         
866         const UInt8 *bytes = static_cast<const UInt8*>([data bytes]);
867         
868         // CFURLCreateAbsoluteURLWithBytes would complain to console if we passed a path to it.
869         if (bytes[0] == '/' && !baseURL)
870             return nil;
871         
872         // NOTE: We use UTF-8 here since this encoding is used when computing strings when returning URL components
873         // (e.g calls to NSURL -path). However, this function is not tolerant of illegal UTF-8 sequences, which
874         // could either be a malformed string or bytes in a different encoding, like shift-jis, so we fall back
875         // onto using ISO Latin 1 in those cases.
876         result = CFBridgingRelease(CFURLCreateAbsoluteURLWithBytes(nullptr, bytes, length, kCFStringEncodingUTF8, (__bridge CFURLRef)baseURL, YES));
877         if (!result)
878             result = CFBridgingRelease(CFURLCreateAbsoluteURLWithBytes(nullptr, bytes, length, kCFStringEncodingISOLatin1, (__bridge CFURLRef)baseURL, YES));
879     } else
880         result = [NSURL URLWithString:@""];
881
882     return result;
883 }
884 static NSData *dataWithUserTypedString(NSString *string)
885 {
886     NSData *userTypedData = [string dataUsingEncoding:NSUTF8StringEncoding];
887     ASSERT(userTypedData);
888     
889     const UInt8* inBytes = static_cast<const UInt8 *>([userTypedData bytes]);
890     int inLength = [userTypedData length];
891     if (!inLength)
892         return nil;
893     
894     char* outBytes = static_cast<char *>(malloc(inLength * 3)); // large enough to %-escape every character
895     char* p = outBytes;
896     int outLength = 0;
897     for (int i = 0; i < inLength; i++) {
898         UInt8 c = inBytes[i];
899         if (c <= 0x20 || c >= 0x7f) {
900             *p++ = '%';
901             *p++ = upperNibbleToASCIIHexDigit(c);
902             *p++ = lowerNibbleToASCIIHexDigit(c);
903             outLength += 3;
904         } else {
905             *p++ = c;
906             outLength++;
907         }
908     }
909     
910     return [NSData dataWithBytesNoCopy:outBytes length:outLength]; // adopts outBytes
911 }
912
913 NSURL *URLWithUserTypedString(NSString *string, NSURL *nsURL)
914 {
915     if (!string)
916         return nil;
917
918     auto mappedString = mapHostNames(stringByTrimmingWhitespace(string).get(), YES);
919     if (!mappedString)
920         return nil;
921
922     // Let's check whether the URL is bogus.
923     URL url { URL { nsURL }, mappedString.get() };
924     if (!url.createCFURL())
925         return nil;
926
927     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=186057
928     // We should be able to use url.createCFURL instead of using directly CFURL parsing routines.
929     NSData *data = dataWithUserTypedString(mappedString.get());
930     if (!data)
931         return [NSURL URLWithString:@""];
932
933     return URLWithData(data, nsURL);
934 }
935
936 NSURL *URLWithUserTypedStringDeprecated(NSString *string, NSURL *URL)
937 {
938     if (!string)
939         return nil;
940
941     NSURL *result = URLWithUserTypedString(string, URL);
942     if (!result) {
943         NSData *resultData = dataWithUserTypedString(string);
944         if (!resultData)
945             return [NSURL URLWithString:@""];
946         result = URLWithData(resultData, URL);
947     }
948
949     return result;
950 }
951
952 static BOOL hasQuestionMarkOnlyQueryString(NSURL *URL)
953 {
954     CFRange rangeWithSeparators;
955     CFURLGetByteRangeForComponent((__bridge CFURLRef)URL, kCFURLComponentQuery, &rangeWithSeparators);
956     if (rangeWithSeparators.location != kCFNotFound && rangeWithSeparators.length == 1)
957         return YES;
958
959     return NO;
960 }
961
962 NSData *dataForURLComponentType(NSURL *URL, CFURLComponentType componentType)
963 {
964     Vector<UInt8, URL_BYTES_BUFFER_LENGTH> allBytesBuffer(URL_BYTES_BUFFER_LENGTH);
965     CFIndex bytesFilled = CFURLGetBytes((__bridge CFURLRef)URL, allBytesBuffer.data(), allBytesBuffer.size());
966     if (bytesFilled == -1) {
967         CFIndex bytesToAllocate = CFURLGetBytes((__bridge CFURLRef)URL, nullptr, 0);
968         allBytesBuffer.grow(bytesToAllocate);
969         bytesFilled = CFURLGetBytes((__bridge CFURLRef)URL, allBytesBuffer.data(), bytesToAllocate);
970     }
971     
972     const CFURLComponentType completeURL = (CFURLComponentType)-1;
973     CFRange range;
974     if (componentType != completeURL) {
975         range = CFURLGetByteRangeForComponent((__bridge CFURLRef)URL, componentType, nullptr);
976         if (range.location == kCFNotFound)
977             return nil;
978     } else {
979         range.location = 0;
980         range.length = bytesFilled;
981     }
982     
983     NSData *componentData = [NSData dataWithBytes:allBytesBuffer.data() + range.location length:range.length]; 
984     
985     const unsigned char *bytes = static_cast<const unsigned char *>([componentData bytes]);
986     NSMutableData *resultData = [NSMutableData data];
987     // NOTE: add leading '?' to query strings non-zero length query strings.
988     // NOTE: retain question-mark only query strings.
989     if (componentType == kCFURLComponentQuery) {
990         if (range.length > 0 || hasQuestionMarkOnlyQueryString(URL))
991             [resultData appendBytes:"?" length:1];    
992     }
993     for (int i = 0; i < range.length; i++) {
994         unsigned char c = bytes[i];
995         if (c <= 0x20 || c >= 0x7f) {
996             char escaped[3];
997             escaped[0] = '%';
998             escaped[1] = upperNibbleToASCIIHexDigit(c);
999             escaped[2] = lowerNibbleToASCIIHexDigit(c);
1000             [resultData appendBytes:escaped length:3];    
1001         } else {
1002             char b[1];
1003             b[0] = c;
1004             [resultData appendBytes:b length:1];    
1005         }               
1006     }
1007     
1008     return resultData;
1009 }
1010
1011 static NSURL *URLByRemovingComponentAndSubsequentCharacter(NSURL *URL, CFURLComponentType component)
1012 {
1013     CFRange range = CFURLGetByteRangeForComponent((__bridge CFURLRef)URL, component, 0);
1014     if (range.location == kCFNotFound)
1015         return URL;
1016     
1017     // Remove one subsequent character.
1018     range.length++;
1019
1020     Vector<UInt8, URL_BYTES_BUFFER_LENGTH> buffer(URL_BYTES_BUFFER_LENGTH);
1021     CFIndex numBytes = CFURLGetBytes((__bridge CFURLRef)URL, buffer.data(), buffer.size());
1022     if (numBytes == -1) {
1023         numBytes = CFURLGetBytes((__bridge CFURLRef)URL, nullptr, 0);
1024         buffer.grow(numBytes);
1025         CFURLGetBytes((__bridge CFURLRef)URL, buffer.data(), numBytes);
1026     }
1027     UInt8* urlBytes = buffer.data();
1028         
1029     if (numBytes < range.location)
1030         return URL;
1031     if (numBytes < range.location + range.length)
1032         range.length = numBytes - range.location;
1033         
1034     memmove(urlBytes + range.location, urlBytes + range.location + range.length, numBytes - range.location + range.length);
1035     
1036     CFURLRef result = CFURLCreateWithBytes(nullptr, urlBytes, numBytes - range.length, kCFStringEncodingUTF8, nullptr);
1037     if (!result)
1038         result = CFURLCreateWithBytes(nullptr, urlBytes, numBytes - range.length, kCFStringEncodingISOLatin1, nullptr);
1039                 
1040     return result ? CFBridgingRelease(result) : URL;
1041 }
1042
1043 NSURL *URLByRemovingUserInfo(NSURL *URL)
1044 {
1045     return URLByRemovingComponentAndSubsequentCharacter(URL, kCFURLComponentUserInfo);
1046 }
1047
1048 NSData *originalURLData(NSURL *URL)
1049 {
1050     UInt8 *buffer = (UInt8 *)malloc(URL_BYTES_BUFFER_LENGTH);
1051     CFIndex bytesFilled = CFURLGetBytes((__bridge CFURLRef)URL, buffer, URL_BYTES_BUFFER_LENGTH);
1052     if (bytesFilled == -1) {
1053         CFIndex bytesToAllocate = CFURLGetBytes((__bridge CFURLRef)URL, nullptr, 0);
1054         buffer = (UInt8 *)realloc(buffer, bytesToAllocate);
1055         bytesFilled = CFURLGetBytes((__bridge CFURLRef)URL, buffer, bytesToAllocate);
1056         ASSERT(bytesFilled == bytesToAllocate);
1057     }
1058     
1059     // buffer is adopted by the NSData
1060     NSData *data = [NSData dataWithBytesNoCopy:buffer length:bytesFilled freeWhenDone:YES];
1061     
1062     NSURL *baseURL = (__bridge NSURL *)CFURLGetBaseURL((__bridge CFURLRef)URL);
1063     if (baseURL)
1064         return originalURLData(URLWithData(data, baseURL));
1065     return data;
1066 }
1067
1068 static CFStringRef createStringWithEscapedUnsafeCharacters(CFStringRef string)
1069 {
1070     CFIndex length = CFStringGetLength(string);
1071     Vector<UChar, URL_BYTES_BUFFER_LENGTH> sourceBuffer(length);
1072     CFStringGetCharacters(string, CFRangeMake(0, length), sourceBuffer.data());
1073     
1074     Vector<UChar, URL_BYTES_BUFFER_LENGTH> outBuffer;
1075     
1076     Optional<UChar32> previousCodePoint;
1077     CFIndex i = 0;
1078     while (i < length) {
1079         UChar32 c;
1080         U16_NEXT(sourceBuffer, i, length, c)
1081         
1082         if (isLookalikeCharacter(previousCodePoint, c)) {
1083             uint8_t utf8Buffer[4];
1084             CFIndex offset = 0;
1085             UBool failure = false;
1086             U8_APPEND(utf8Buffer, offset, 4, c, failure)
1087             ASSERT(!failure);
1088             
1089             for (CFIndex j = 0; j < offset; ++j) {
1090                 outBuffer.append('%');
1091                 outBuffer.append(upperNibbleToASCIIHexDigit(utf8Buffer[j]));
1092                 outBuffer.append(lowerNibbleToASCIIHexDigit(utf8Buffer[j]));
1093             }
1094         } else {
1095             UChar utf16Buffer[2];
1096             CFIndex offset = 0;
1097             UBool failure = false;
1098             U16_APPEND(utf16Buffer, offset, 2, c, failure)
1099             ASSERT(!failure);
1100             for (CFIndex j = 0; j < offset; ++j)
1101                 outBuffer.append(utf16Buffer[j]);
1102         }
1103         previousCodePoint = c;
1104     }
1105     
1106     return CFStringCreateWithCharacters(nullptr, outBuffer.data(), outBuffer.size());
1107 }
1108
1109 static String toNormalizationFormC(const String& string)
1110 {
1111     auto sourceBuffer = string.charactersWithNullTermination();
1112     ASSERT(sourceBuffer.last() == '\0');
1113     sourceBuffer.removeLast();
1114
1115     String result;
1116     Vector<UChar, URL_BYTES_BUFFER_LENGTH> normalizedCharacters(sourceBuffer.size());
1117     UErrorCode uerror = U_ZERO_ERROR;
1118     int32_t normalizedLength = 0;
1119     const UNormalizer2 *normalizer = unorm2_getNFCInstance(&uerror);
1120     if (!U_FAILURE(uerror)) {
1121         normalizedLength = unorm2_normalize(normalizer, sourceBuffer.data(), sourceBuffer.size(), normalizedCharacters.data(), normalizedCharacters.size(), &uerror);
1122         if (uerror == U_BUFFER_OVERFLOW_ERROR) {
1123             uerror = U_ZERO_ERROR;
1124             normalizedCharacters.resize(normalizedLength);
1125             normalizedLength = unorm2_normalize(normalizer, sourceBuffer.data(), sourceBuffer.size(), normalizedCharacters.data(), normalizedLength, &uerror);
1126         }
1127         if (!U_FAILURE(uerror))
1128             result = String(normalizedCharacters.data(), normalizedLength);
1129     }
1130
1131     return result;
1132 }
1133
1134 NSString *userVisibleString(NSURL *URL)
1135 {
1136     NSData *data = originalURLData(URL);
1137     const unsigned char *before = static_cast<const unsigned char*>([data bytes]);
1138     int length = [data length];
1139     
1140     bool mayNeedHostNameDecoding = false;
1141     
1142     const unsigned char *p = before;
1143     int bufferLength = (length * 3) + 1;
1144     Vector<char, URL_BYTES_BUFFER_LENGTH> after(bufferLength); // large enough to %-escape every character
1145     char *q = after.data();
1146     for (int i = 0; i < length; i++) {
1147         unsigned char c = p[i];
1148         // unescape escape sequences that indicate bytes greater than 0x7f
1149         if (c == '%' && (i + 1 < length && isASCIIHexDigit(p[i + 1])) && i + 2 < length && isASCIIHexDigit(p[i + 2])) {
1150             auto u = toASCIIHexValue(p[i + 1], p[i + 2]);
1151             if (u > 0x7f) {
1152                 // unescape
1153                 *q++ = u;
1154             } else {
1155                 // do not unescape
1156                 *q++ = p[i];
1157                 *q++ = p[i + 1];
1158                 *q++ = p[i + 2];
1159             }
1160             i += 2;
1161         } else {
1162             *q++ = c;
1163             
1164             // Check for "xn--" in an efficient, non-case-sensitive, way.
1165             if (c == '-' && i >= 3 && !mayNeedHostNameDecoding && (q[-4] | 0x20) == 'x' && (q[-3] | 0x20) == 'n' && q[-2] == '-')
1166                 mayNeedHostNameDecoding = true;
1167         }
1168     }
1169     *q = '\0';
1170     
1171     // Check string to see if it can be converted to display using UTF-8  
1172     RetainPtr<NSString> result = [NSString stringWithUTF8String:after.data()];
1173     if (!result) {
1174         // Could not convert to UTF-8.
1175         // Convert characters greater than 0x7f to escape sequences.
1176         // Shift current string to the end of the buffer
1177         // then we will copy back bytes to the start of the buffer 
1178         // as we convert.
1179         int afterlength = q - after.data();
1180         char *p = after.data() + bufferLength - afterlength - 1;
1181         memmove(p, after.data(), afterlength + 1); // copies trailing '\0'
1182         char *q = after.data();
1183         while (*p) {
1184             unsigned char c = *p;
1185             if (c > 0x7f) {
1186                 *q++ = '%';
1187                 *q++ = upperNibbleToASCIIHexDigit(c);
1188                 *q++ = lowerNibbleToASCIIHexDigit(c);
1189             } else
1190                 *q++ = *p;
1191             p++;
1192         }
1193         *q = '\0';
1194         result = [NSString stringWithUTF8String:after.data()];
1195     }
1196     
1197     if (mayNeedHostNameDecoding) {
1198         // FIXME: Is it good to ignore the failure of mapHostNames and keep result intact?
1199         auto mappedResult = mapHostNames(result.get(), NO);
1200         if (mappedResult)
1201             result = mappedResult;
1202     }
1203
1204     auto wtfString = String(result.get());
1205     auto normalized = toNormalizationFormC(wtfString);
1206     result = static_cast<NSString *>(normalized);
1207     return CFBridgingRelease(createStringWithEscapedUnsafeCharacters((__bridge CFStringRef)result.get()));
1208 }
1209
1210 BOOL isUserVisibleURL(NSString *string)
1211 {
1212     BOOL valid = YES;
1213     // get buffer
1214     
1215     char static_buffer[1024];
1216     const char *p;
1217     BOOL success = CFStringGetCString((__bridge CFStringRef)string, static_buffer, 1023, kCFStringEncodingUTF8);
1218     p = success ? static_buffer : [string UTF8String];
1219     
1220     int length = strlen(p);
1221     
1222     // check for characters <= 0x20 or >=0x7f, %-escape sequences of %7f, and xn--, these
1223     // are the things that will lead _web_userVisibleString to actually change things.
1224     for (int i = 0; i < length; i++) {
1225         unsigned char c = p[i];
1226         // escape control characters, space, and delete
1227         if (c <= 0x20 || c == 0x7f) {
1228             valid = NO;
1229             break;
1230         } else if (c == '%' && (i + 1 < length && isASCIIHexDigit(p[i + 1])) && i + 2 < length && isASCIIHexDigit(p[i + 2])) {
1231             auto u = toASCIIHexValue(p[i + 1], p[i + 2]);
1232             if (u > 0x7f) {
1233                 valid = NO;
1234                 break;
1235             }
1236             i += 2;
1237         } else {
1238             // Check for "xn--" in an efficient, non-case-sensitive, way.
1239             if (c == '-' && i >= 3 && (p[i - 3] | 0x20) == 'x' && (p[i - 2] | 0x20) == 'n' && p[i - 1] == '-') {
1240                 valid = NO;
1241                 break;
1242             }
1243         }
1244     }
1245     
1246     return valid;
1247 }
1248
1249 NSRange rangeOfURLScheme(NSString *string)
1250 {
1251     NSRange colon = [string rangeOfString:@":"];
1252     if (colon.location != NSNotFound && colon.location > 0) {
1253         NSRange scheme = {0, colon.location};
1254         /*
1255          This stuff is very expensive.  10-15 msec on a 2x1.2GHz.  If not cached it swamps
1256          everything else when adding items to the autocomplete DB.  Makes me wonder if we
1257          even need to enforce the character set here.
1258          */
1259         NSString *acceptableCharacters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+.-";
1260         static NeverDestroyed<RetainPtr<NSCharacterSet>> InverseSchemeCharacterSet([[NSCharacterSet characterSetWithCharactersInString:acceptableCharacters] invertedSet]);
1261         NSRange illegals = [string rangeOfCharacterFromSet:InverseSchemeCharacterSet.get().get() options:0 range:scheme];
1262         if (illegals.location == NSNotFound)
1263             return scheme;
1264     }
1265     return NSMakeRange(NSNotFound, 0);
1266 }
1267
1268 BOOL looksLikeAbsoluteURL(NSString *string)
1269 {
1270     // Trim whitespace because _web_URLWithString allows whitespace.
1271     return rangeOfURLScheme(stringByTrimmingWhitespace(string).get()).location != NSNotFound;
1272 }
1273
1274 } // namespace WebCore