@@ -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