@@ -245,23 +245,7 @@ public static Uri create(String s) {
245245 break ;
246246 }
247247 }
248- String authority = s .substring (authorityStart , i );
249-
250- // 3.2.1. UserInfo. Easy, because '@' cannot appear unencoded inside userinfo or host.
251- int userInfoEnd = authority .indexOf ('@' );
252- if (userInfoEnd >= 0 ) {
253- builder .setRawUserInfo (authority .substring (0 , userInfoEnd ));
254- }
255-
256- // 3.2.2/3. Host/Port.
257- int hostStart = userInfoEnd >= 0 ? userInfoEnd + 1 : 0 ;
258- int portStartColon = findPortStartColon (authority , hostStart );
259- if (portStartColon < 0 ) {
260- builder .setRawHost (authority .substring (hostStart , authority .length ()));
261- } else {
262- builder .setRawHost (authority .substring (hostStart , portStartColon ));
263- builder .setRawPort (authority .substring (portStartColon + 1 ));
264- }
248+ builder .setRawAuthority (s .substring (authorityStart , i ));
265249 }
266250
267251 // 3.3. Path: Whatever is left before '?' or '#'.
@@ -356,6 +340,15 @@ public String getScheme() {
356340 /**
357341 * Returns the percent-decoded "Authority" component of this URI, or null if not present.
358342 *
343+ * <p>NB: This method's decoding is lossy -- It only exists for compatibility with {@link
344+ * java.net.URI}. Prefer {@link #getRawAuthority()} or work instead with authority in terms of its
345+ * individual components ({@link #getUserInfo()}, {@link #getHost()} and {@link #getPort()}). The
346+ * problem with getAuthority() is that it returns the delimited concatenation of the percent-
347+ * decoded userinfo, host and port components. But both userinfo and host can contain the '@'
348+ * character, which becomes indistinguishable from the userinfo/host delimiter after decoding. For
349+ * example, URIs <code>scheme://x@y%40z</code> and <code>scheme://x%40y@z</code> have different
350+ * userinfo and host components but getAuthority() returns "x@y@z" for both of them.
351+ *
359352 * <p>NB: This method assumes the "host" component was encoded as UTF-8, as mandated by RFC 3986.
360353 * This method also assumes the "user information" part of authority was encoded as UTF-8,
361354 * although RFC 3986 doesn't specify an encoding.
@@ -954,6 +947,51 @@ Builder setRawPort(String port) {
954947 return this ;
955948 }
956949
950+ /**
951+ * Specifies the userinfo, host and port URI components all at once using a single string.
952+ *
953+ * <p>This setter is "raw" in the sense that special characters in userinfo and host must be
954+ * passed in percent-encoded. See <a
955+ * href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.2">RFC 3986 3.2</a> for the set
956+ * of characters allowed in each component of an authority.
957+ *
958+ * <p>There's no "cooked" method to set authority like for other URI components because
959+ * authority is a *compound* URI component whose userinfo, host and port components are
960+ * delimited with special characters '@' and ':'. But the first two of those components can
961+ * themselves contain these delimiters so we need percent-encoding to parse them unambiguously.
962+ *
963+ * @param authority an RFC 3986 authority string that will be used to set userinfo, host and
964+ * port, or null to clear all three of those components
965+ */
966+ @ CanIgnoreReturnValue
967+ public Builder setRawAuthority (@ Nullable String authority ) {
968+ if (authority == null ) {
969+ setUserInfo (null );
970+ setHost ((String ) null );
971+ setPort (-1 );
972+ } else {
973+ // UserInfo. Easy because '@' cannot appear unencoded inside userinfo or host.
974+ int userInfoEnd = authority .indexOf ('@' );
975+ if (userInfoEnd >= 0 ) {
976+ setRawUserInfo (authority .substring (0 , userInfoEnd ));
977+ } else {
978+ setUserInfo (null );
979+ }
980+
981+ // Host/Port.
982+ int hostStart = userInfoEnd >= 0 ? userInfoEnd + 1 : 0 ;
983+ int portStartColon = findPortStartColon (authority , hostStart );
984+ if (portStartColon < 0 ) {
985+ setRawHost (authority .substring (hostStart ));
986+ setPort (-1 );
987+ } else {
988+ setRawHost (authority .substring (hostStart , portStartColon ));
989+ setRawPort (authority .substring (portStartColon + 1 ));
990+ }
991+ }
992+ return this ;
993+ }
994+
957995 /** Builds a new instance of {@link Uri} as specified by the setters. */
958996 public Uri build () {
959997 checkState (scheme != null , "Missing required scheme." );
0 commit comments