2727use Uri \UriComparisonMode ;
2828
2929use function in_array ;
30+ use function preg_match ;
3031use function substr ;
3132
3233use const PHP_VERSION_ID ;
@@ -44,12 +45,18 @@ final class Url
4445 {
4546 private const PORT_RANGE_MIN = 0 ;
4647 private const PORT_RANGE_MAX = 65535 ;
48+ private const REGEXP_IDNA_PATTERN = '/[^\x20-\x7f]/ ' ;
4749
4850 private WhatWgURL $ url ;
51+
4952 private ?string $ unicodeHost = null ;
5053 private bool $ unicodeHostInitialized = false ;
5154 private ?string $ urlUnicodeString = null ;
5255
56+ private ?string $ asciiHost = null ;
57+ private bool $ asciiHostInitialized = false ;
58+ private ?string $ urlAsciiString = null ;
59+
5360 /**
5461 * @param list<UrlValidationError> $errors
5562 */
@@ -167,7 +174,14 @@ public function withPassword(#[SensitiveParameter] ?string $password): self
167174
168175 public function getAsciiHost (): ?string
169176 {
170- return $ this ->url ->hostname ;
177+ if ($ this ->asciiHostInitialized ) {
178+ return $ this ->asciiHost ;
179+ }
180+
181+ $ this ->asciiHost = $ this ->setAsciiHost ();
182+ $ this ->asciiHostInitialized = true ;
183+
184+ return $ this ->asciiHost ;
171185 }
172186
173187 public function getUnicodeHost (): ?string
@@ -210,6 +224,29 @@ private function setUnicodeHost(): ?string
210224 return $ result ->getDomain ();
211225 }
212226
227+ private function setAsciiHost (): ?string
228+ {
229+ $ host = $ this ->url ->hostname ;
230+ if ('' === $ host || null === $ host || 1 !== preg_match (self ::REGEXP_IDNA_PATTERN , $ host )) {
231+ return $ host ;
232+ }
233+
234+ $ result = Idna::toAscii ($ host , [
235+ 'CheckHyphens ' => false ,
236+ 'CheckBidi ' => true ,
237+ 'CheckJoiners ' => true ,
238+ 'UseSTD3ASCIIRules ' => false ,
239+ 'Transitional_Processing ' => false ,
240+ 'IgnoreInvalidPunycode ' => false ,
241+ ]);
242+
243+ if ($ result ->hasErrors ()) {
244+ return $ host ;
245+ }
246+
247+ return $ result ->getDomain ();
248+ }
249+
213250 /**
214251 * @throws InvalidUrlException
215252 */
@@ -355,7 +392,22 @@ private static function urlRecord(self $url): URLRecord
355392
356393 public function toAsciiString (): string
357394 {
358- return $ this ->url ->href ;
395+ if (null !== $ this ->urlAsciiString ) {
396+ return $ this ->urlAsciiString ;
397+ }
398+
399+ $ asciiHost = $ this ->getAsciiHost ();
400+ if (null === $ asciiHost || $ this ->getUnicodeHost () === $ asciiHost ) {
401+ $ this ->urlAsciiString = $ this ->url ->href ;
402+
403+ return $ this ->urlAsciiString ;
404+ }
405+
406+ $ urlRecord = self ::urlRecord ($ this );
407+ $ urlRecord ->host = new StringHost ($ asciiHost );
408+ $ this ->urlAsciiString = $ urlRecord ->serializeURL ();
409+
410+ return $ this ->urlAsciiString ;
359411 }
360412
361413 public function toUnicodeString (): string
0 commit comments