@@ -51,9 +51,19 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
5151 private final String userInfo ;
5252 private final Host host ;
5353
54+ static URIAuthority parse (final CharSequence s ) throws URISyntaxException {
55+ if (TextUtils .isBlank (s )) {
56+ return null ;
57+ }
58+ final Tokenizer .Cursor cursor = new Tokenizer .Cursor (0 , s .length ());
59+ return parse (s , cursor ); // intentionally no cursor.atEnd() check
60+ }
61+
5462 static URIAuthority parse (final CharSequence s , final Tokenizer .Cursor cursor ) throws URISyntaxException {
5563 final Tokenizer tokenizer = Tokenizer .INSTANCE ;
5664 String userInfo = null ;
65+
66+ // optional userinfo@
5767 final int initPos = cursor .getPos ();
5868 final String token = tokenizer .parseContent (s , cursor , URISupport .HOST_DELIMITERS );
5969 if (!cursor .atEnd () && s .charAt (cursor .getPos ()) == '@' ) {
@@ -62,26 +72,108 @@ static URIAuthority parse(final CharSequence s, final Tokenizer.Cursor cursor) t
6272 userInfo = token ;
6373 }
6474 } else {
65- //Rewind
6675 cursor .updatePos (initPos );
6776 }
77+
78+ if (!cursor .atEnd () && s .charAt (cursor .getPos ()) == '[' ) {
79+ final int lb = cursor .getPos ();
80+ final int upper = cursor .getUpperBound ();
81+ int rb = -1 ;
82+ for (int i = lb + 1 ; i < upper ; i ++) {
83+ if (s .charAt (i ) == ']' ) { rb = i ; break ; }
84+ }
85+ if (rb < 0 ) {
86+ throw URISupport .createException (s .toString (), cursor , "Expected closing bracket for IPv6 address" );
87+ }
88+
89+ final String literal = s .subSequence (lb + 1 , rb ).toString ();
90+ final int z = literal .indexOf ("%25" );
91+ final String addr = z >= 0 ? literal .substring (0 , z ) : literal ;
92+
93+ // validate only the IPv6 address part (zone excluded)
94+ if (!InetAddressUtils .isIPv6 (addr )) {
95+ throw URISupport .createException (s .toString (), cursor , "Expected an IPv6 address" );
96+ }
97+
98+ // hostName must be stored in friendly form: "...%<decoded-zone>"
99+ final String hostName ;
100+ if (z >= 0 ) {
101+ final String zoneEnc = literal .substring (z + 3 );
102+ ZoneIdSupport .validateZoneIdEncoded (zoneEnc );
103+ hostName = ZoneIdSupport .decodeZoneId (literal ); // turns %25 + %HH into % + decoded UTF-8
104+ } else {
105+ hostName = addr ;
106+ }
107+
108+ // optional :port
109+ int pos = rb + 1 ;
110+ int port = -1 ;
111+ if (pos < upper && s .charAt (pos ) == ':' ) {
112+ pos ++;
113+ if (pos >= upper || !Character .isDigit (s .charAt (pos ))) {
114+ throw URISupport .createException (s .toString (), cursor , "Invalid port" );
115+ }
116+ long p = 0 ;
117+ while (pos < upper && Character .isDigit (s .charAt (pos ))) {
118+ p = p * 10 + (s .charAt (pos ) - '0' );
119+ if (p > 65535 ) {
120+ throw URISupport .createException (s .toString (), cursor , "Port out of range" );
121+ }
122+ pos ++;
123+ }
124+ port = (int ) p ;
125+ }
126+ cursor .updatePos (pos );
127+ return new URIAuthority (userInfo , hostName , port );
128+ }
129+
130+ {
131+ final int start = cursor .getPos ();
132+ final int upper = cursor .getUpperBound ();
133+ int i = start ;
134+ int colonCount = 0 ;
135+ while (i < upper ) {
136+ final char ch = s .charAt (i );
137+ if (ch == '/' || ch == '?' || ch == '#' ) {
138+ break ; // end of authority
139+ }
140+ if (ch == ']' ) {
141+ break ; // safety
142+ }
143+ if (ch == ':' ) {
144+ colonCount ++;
145+ // more than one ':' before path/query/fragment => looks like IPv6, but not bracketed
146+ if (colonCount > 1 ) {
147+ throw URISupport .createException (s .toString (), cursor , "Expected an IPv6 address" );
148+ }
149+ }
150+ i ++;
151+ }
152+ }
153+
154+ // ===== fallback: reg-name / IPv4 (and optional :port) =====
68155 final Host host = Host .parse (s , cursor );
69156 return new URIAuthority (userInfo , host );
70157 }
71158
72- static URIAuthority parse (final CharSequence s ) throws URISyntaxException {
73- final Tokenizer .Cursor cursor = new Tokenizer .Cursor (0 , s .length ());
74- return parse (s , cursor );
75- }
76159
77160 static void format (final StringBuilder buf , final URIAuthority uriAuthority ) {
78161 if (uriAuthority .getUserInfo () != null ) {
79- buf .append (uriAuthority .getUserInfo ());
80- buf .append ("@" );
162+ buf .append (uriAuthority .getUserInfo ()).append ("@" );
163+ }
164+ final String hostName = uriAuthority .getHostName ();
165+ final int port = uriAuthority .getPort ();
166+
167+ if (ZoneIdSupport .appendBracketedIPv6 (buf , hostName )) {
168+ if (port >= 0 ) {
169+ buf .append (':' ).append (port );
170+ }
171+ } else {
172+ Host .format (buf , uriAuthority );
81173 }
82- Host .format (buf , uriAuthority );
83174 }
84175
176+
85177 static String format (final URIAuthority uriAuthority ) {
86178 final StringBuilder buf = new StringBuilder ();
87179 format (buf , uriAuthority );
0 commit comments