diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/HttpUrl.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/HttpUrl.kt index 646c698437e2..6343bf707656 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/HttpUrl.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/HttpUrl.kt @@ -32,6 +32,7 @@ import okhttp3.internal.url.FRAGMENT_ENCODE_SET import okhttp3.internal.url.FRAGMENT_ENCODE_SET_URI import okhttp3.internal.url.PASSWORD_ENCODE_SET import okhttp3.internal.url.PATH_SEGMENT_ENCODE_SET +import okhttp3.internal.url.PATH_SEGMENT_ENCODE_SET_RAW import okhttp3.internal.url.PATH_SEGMENT_ENCODE_SET_URI import okhttp3.internal.url.QUERY_COMPONENT_ENCODE_SET import okhttp3.internal.url.QUERY_COMPONENT_ENCODE_SET_URI @@ -1045,7 +1046,7 @@ class HttpUrl private constructor( index: Int, pathSegment: String, ) = apply { - val canonicalPathSegment = pathSegment.canonicalize(encodeSet = PATH_SEGMENT_ENCODE_SET) + val canonicalPathSegment = pathSegment.canonicalize(encodeSet = PATH_SEGMENT_ENCODE_SET_RAW) require(!isDot(canonicalPathSegment) && !isDotDot(canonicalPathSegment)) { "unexpected path segment: $pathSegment" } @@ -1547,7 +1548,7 @@ class HttpUrl private constructor( input.canonicalize( pos = pos, limit = limit, - encodeSet = PATH_SEGMENT_ENCODE_SET, + encodeSet = if (alreadyEncoded) PATH_SEGMENT_ENCODE_SET else PATH_SEGMENT_ENCODE_SET_RAW, alreadyEncoded = alreadyEncoded, ) if (isDot(segment)) { diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/url/-Url.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/url/-Url.kt index f3380485e1a0..50fb00550617 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/url/-Url.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/url/-Url.kt @@ -26,6 +26,7 @@ internal val HEX_DIGITS = internal const val USERNAME_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#" internal const val PASSWORD_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#" internal const val PATH_SEGMENT_ENCODE_SET = " \"<>^`{}|/\\?#" +internal const val PATH_SEGMENT_ENCODE_SET_RAW = " \"<>^`{}|/\\?#[]" internal const val PATH_SEGMENT_ENCODE_SET_URI = "[]" internal const val QUERY_ENCODE_SET = " \"'<>#" internal const val QUERY_COMPONENT_REENCODE_SET = " \"'<>#&=" diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlJvmTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlJvmTest.kt index 217c6c14c48b..01c40bbe1eb9 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlJvmTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlJvmTest.kt @@ -263,7 +263,7 @@ open class HttpUrlJvmTest { .addPathSegment("=[]:;\"~|?#@^/$%*") .build() assertThat(url.toString()) - .isEqualTo("http://host/=[]:;%22~%7C%3F%23@%5E%2F$%25*") + .isEqualTo("http://host/=%5B%5D:;%22~%7C%3F%23@%5E%2F$%25*") assertThat(url.toUri().toString()) .isEqualTo("http://host/=%5B%5D:;%22~%7C%3F%23@%5E%2F$%25*") } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlTest.kt index 9ada0b3ee425..fce91f447e66 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/HttpUrlTest.kt @@ -1535,6 +1535,35 @@ open class HttpUrlTest { ).isEqualTo("/a/b/c/%252e%252e") } + @Test + fun addPathSegmentEncodesSquareBrackets() { + val url = + parse("http://host/") + .newBuilder() + .addPathSegment("a[0]") + .build() + assertThat(url.encodedPath).isEqualTo("/a%5B0%5D") + assertThat(url.pathSegments).containsExactly("a[0]") + } + + @Test + fun addEncodedPathSegmentRetainsSquareBrackets() { + val url = + parse("http://host/") + .newBuilder() + .addEncodedPathSegment("a[0]") + .build() + assertThat(url.encodedPath).isEqualTo("/a[0]") + assertThat(url.pathSegments).containsExactly("a[0]") + } + + @Test + fun parseRetainsSquareBracketsInPath() { + val url = parse("http://host/a[0]") + assertThat(url.encodedPath).isEqualTo("/a[0]") + assertThat(url.pathSegments).containsExactly("a[0]") + } + @Test fun addPathSegmentDotDotPopsDirectory() { val base = parse("http://host/a/b/c") @@ -1635,6 +1664,17 @@ open class HttpUrlTest { ).isEqualTo("/%252e/b/c") } + @Test + fun setPathSegmentEncodesSquareBrackets() { + val url = + parse("http://host/a/b/c") + .newBuilder() + .setPathSegment(0, "x[0]") + .build() + assertThat(url.encodedPath).isEqualTo("/x%5B0%5D/b/c") + assertThat(url.pathSegments).containsExactly("x[0]", "b", "c") + } + @Test fun setPathSegmentAcceptsEmpty() { val base = parse("http://host/a/b/c")