@@ -23,13 +23,98 @@ public function __construct(?string $uri = null) {
2323 return ;
2424 }
2525
26- $ parts = parse_url ($ uri );
26+ $ parts = $ this -> parseUri ($ uri );
2727 if ($ parts === false ) {
2828 throw new UriParseErrorException ($ uri );
2929 }
3030 $ this ->applyParts ($ parts );
3131 }
3232
33+ /** @return false|array<string, int|string> */
34+ protected function parseUri (string $ uri ):false |array {
35+ $ parts = parse_url ($ uri );
36+ if ($ parts === false ) {
37+ return false ;
38+ }
39+
40+ $ authorityStyleParts = $ this ->parseAuthorityStyleParts ($ uri , $ parts );
41+ if (!is_null ($ authorityStyleParts )) {
42+ return $ authorityStyleParts ;
43+ }
44+
45+ return $ parts ;
46+ }
47+
48+ /**
49+ * @param array<string, int|string> $parts
50+ * @return null|array<string, int|string>
51+ */
52+ protected function parseAuthorityStyleParts (
53+ string $ uri ,
54+ array $ parts
55+ ):?array {
56+ if (!$ this ->canBeAuthorityStyleUri ($ uri , $ parts )) {
57+ return null ;
58+ }
59+
60+ $ authorityParts = parse_url ("// " . $ uri );
61+ if ($ authorityParts === false ) {
62+ return null ;
63+ }
64+
65+ if (!$ this ->hasRequiredAuthorityParts ($ authorityParts )) {
66+ return null ;
67+ }
68+
69+ if (!$ this ->isAuthorityPathValid ($ authorityParts )) {
70+ return null ;
71+ }
72+
73+ if (!$ this ->isAuthorityHostLike ($ authorityParts )) {
74+ return null ;
75+ }
76+
77+ return $ authorityParts ;
78+ }
79+
80+ /** @param array<string, int|string> $parts */
81+ protected function canBeAuthorityStyleUri (string $ uri , array $ parts ):bool {
82+ if (str_contains ($ uri , ":// " )) {
83+ return false ;
84+ }
85+
86+ if (!isset ($ parts ["scheme " ]) || isset ($ parts ["host " ])) {
87+ return false ;
88+ }
89+
90+ $ path = (string )($ parts ["path " ] ?? "" );
91+ return str_contains ($ path , "@ " );
92+ }
93+
94+ /** @param array<string, int|string> $authorityParts */
95+ protected function hasRequiredAuthorityParts (array $ authorityParts ):bool {
96+ return isset ($ authorityParts ["user " ], $ authorityParts ["pass " ], $ authorityParts ["host " ]);
97+ }
98+
99+ /** @param array<string, int|string> $authorityParts */
100+ protected function isAuthorityPathValid (array $ authorityParts ):bool {
101+ $ authorityPath = (string )($ authorityParts ["path " ] ?? "" );
102+ return strlen ($ authorityPath ) === 0 || str_starts_with ($ authorityPath , "/ " );
103+ }
104+
105+ /** @param array<string, int|string> $authorityParts */
106+ protected function isAuthorityHostLike (array $ authorityParts ):bool {
107+ $ host = (string )$ authorityParts ["host " ];
108+ if (filter_var ($ host , FILTER_VALIDATE_IP ) !== false ) {
109+ return true ;
110+ }
111+
112+ return str_contains ($ host , ". " )
113+ || str_contains ($ host , ": " )
114+ || str_starts_with ($ host , "[ " )
115+ || $ host === self ::DEFAULT_HOST_HTTP ;
116+ }
117+
33118 /** @inheritDoc */
34119 public function __toString ():string {
35120 $ uri = "" ;
0 commit comments