diff --git a/zio-http/jvm/src/test/scala/zio/http/URLSpec.scala b/zio-http/jvm/src/test/scala/zio/http/URLSpec.scala index 9ac4ea3f88..6f627d832c 100644 --- a/zio-http/jvm/src/test/scala/zio/http/URLSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/URLSpec.scala @@ -160,6 +160,11 @@ object URLSpec extends ZIOHttpSpec { val expected = Right(host + channels) assertTrue(actual == expected) }, + test("does not escape valid characters") { + check(HttpGen.nonEmptyPath) { path => + assertTrue(URL(path = path).encode == path.encode) + } + } ), suite("builder")( test("creates a URL with all attributes set") { diff --git a/zio-http/jvm/src/test/scala/zio/http/internal/HttpGen.scala b/zio-http/jvm/src/test/scala/zio/http/internal/HttpGen.scala index 05febadcf6..7082672a9f 100644 --- a/zio-http/jvm/src/test/scala/zio/http/internal/HttpGen.scala +++ b/zio-http/jvm/src/test/scala/zio/http/internal/HttpGen.scala @@ -28,6 +28,20 @@ import zio.http.URL.Location import zio.http._ object HttpGen { + def pcharUriChar(implicit trace: Trace): Gen[Any, Char] = + Gen.weighted( + Gen.char(48, 57) -> 10, // digits + Gen.char(65, 90) -> 26, // uppercase letters + Gen.char(97, 122) -> 26, // lowercase letters + Gen.const('-') -> 1, + Gen.const('.') -> 1, + Gen.const('_') -> 1, + Gen.const('~') -> 1, + Gen.const('*') -> 1, + Gen.const(':') -> 1, + Gen.const('@') -> 1, + ) + def anyPath: Gen[Any, Path] = for { flags <- Gen.boolean.zip(Gen.boolean).map { case (left, right) => var flags = 0 @@ -37,7 +51,7 @@ object HttpGen { } segments <- Gen.listOfBounded(0, 5)( Gen.oneOf( - Gen.alphaNumericStringBounded(0, 5), + Gen.stringBounded(0, 5)(pcharUriChar), ), ) } yield Path(flags, Chunk.fromIterable(segments)) @@ -51,7 +65,7 @@ object HttpGen { } segments <- Gen.listOfBounded(1, 5)( Gen.oneOf( - Gen.alphaNumericStringBounded(0, 5), + Gen.stringBounded(0, 5)(pcharUriChar), ), ) } yield Path(flags, Chunk.fromIterable(segments)) diff --git a/zio-http/shared/src/main/scala/zio/http/internal/QueryParamEncoding.scala b/zio-http/shared/src/main/scala/zio/http/internal/QueryParamEncoding.scala index dec88891eb..f065fc9064 100644 --- a/zio-http/shared/src/main/scala/zio/http/internal/QueryParamEncoding.scala +++ b/zio-http/shared/src/main/scala/zio/http/internal/QueryParamEncoding.scala @@ -241,13 +241,7 @@ private[http] object QueryParamEncoding { val bytesLen = bytes.length while (k < bytesLen) { val unsignedByte = bytes(k) & 0xff - if ( - (unsignedByte >= 'a' && unsignedByte <= 'z') || - (unsignedByte >= 'A' && unsignedByte <= 'Z') || - (unsignedByte >= '0' && unsignedByte <= '9') || - unsignedByte == '-' || unsignedByte == '.' || - unsignedByte == '_' || unsignedByte == '~' || unsignedByte == '*' - ) { + if (needsNoEncoding(unsignedByte.toChar)) { // Unreserved character target.append(unsignedByte.toChar) } else if (unsignedByte == ' ') { @@ -267,6 +261,6 @@ private[http] object QueryParamEncoding { (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || - c == '-' || c == '.' || c == '_' || c == '~' || c == '*' + c == '-' || c == '.' || c == '_' || c == '~' || c == '*' || c == ':' || c == '@' } }