From 9f455e9bbeb6cbf2570a99d1f30d032d7c1f1ba9 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Fri, 6 Sep 2024 02:00:36 +0200 Subject: [PATCH] Make int and long segment parsing JS compatible (#3087) (#3090) --- .../codec/PathCodecPlatformSpecific.scala | 91 +++++++++++++++++++ .../codec/PathCodecPlatformSpecific.scala | 9 ++ .../main/scala/zio/http/codec/PathCodec.scala | 8 +- 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 zio-http/js/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala create mode 100644 zio-http/jvm/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala diff --git a/zio-http/js/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala b/zio-http/js/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala new file mode 100644 index 0000000000..a5c599958c --- /dev/null +++ b/zio-http/js/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala @@ -0,0 +1,91 @@ +package zio.http.codec + +import java.util.Objects + +trait PathCodecPlatformSpecific { + private[codec] def parseLong(s: CharSequence, beginIndex: Int, endIndex: Int, radix: Int): Long = { + Objects.requireNonNull(s) + Objects.checkFromToIndex(beginIndex, endIndex, s.length) + if (radix < Character.MIN_RADIX) + throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX") + if (radix > Character.MAX_RADIX) + throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX") + var negative = false + var i = beginIndex + var limit = -Long.MaxValue + if (i < endIndex) { + val firstChar = s.charAt(i) + if (firstChar < '0') { // Possible leading "+" or "-" + if (firstChar == '-') { + negative = true + limit = Long.MinValue + } else if (firstChar != '+') throw forCharSequence(s, beginIndex, endIndex, i) + i += 1 + } + if (i >= endIndex) { // Cannot have lone "+", "-" or "" + throw forCharSequence(s, beginIndex, endIndex, i) + } + val multmin = limit / radix + var result = 0L + while (i < endIndex) { + // Accumulating negatively avoids surprises near MAX_VALUE + val digit = Character.digit(s.charAt(i), radix) + if (digit < 0 || result < multmin) throw forCharSequence(s, beginIndex, endIndex, i) + result *= radix + if (result < limit + digit) throw forCharSequence(s, beginIndex, endIndex, i) + i += 1 + result -= digit + } + if (negative) result + else -result + } else throw new NumberFormatException("") + } + + private[codec] def parseInt(s: CharSequence, beginIndex: Int, endIndex: Int, radix: Int): Int = { + Objects.requireNonNull(s) + Objects.checkFromToIndex(beginIndex, endIndex, s.length) + if (radix < Character.MIN_RADIX) + throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX") + if (radix > Character.MAX_RADIX) + throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX") + var negative = false + var i = beginIndex + var limit = -Int.MaxValue + if (i < endIndex) { + val firstChar = s.charAt(i) + if (firstChar < '0') { // Possible leading "+" or "-" + if (firstChar == '-') { + negative = true + limit = Int.MinValue + } else if (firstChar != '+') throw forCharSequence(s, beginIndex, endIndex, i) + i += 1 + if (i == endIndex) { // Cannot have lone "+" or "-" + throw forCharSequence(s, beginIndex, endIndex, i) + } + } + val multmin = limit / radix + var result = 0 + while (i < endIndex) { + // Accumulating negatively avoids surprises near MAX_VALUE + val digit = Character.digit(s.charAt(i), radix) + if (digit < 0 || result < multmin) throw forCharSequence(s, beginIndex, endIndex, i) + result *= radix + if (result < limit + digit) throw forCharSequence(s, beginIndex, endIndex, i) + i += 1 + result -= digit + } + if (negative) result + else -result + } else throw forInputString("", radix) + } + + private[codec] def forCharSequence(s: CharSequence, beginIndex: Int, endIndex: Int, errorIndex: Int) = + new NumberFormatException( + "Error at index " + (errorIndex - beginIndex) + " in: \"" + s.subSequence(beginIndex, endIndex) + "\"", + ) + + private[codec] def forInputString(s: String, radix: Int) = new NumberFormatException( + "For input string: \"" + s + "\"" + (if (radix == 10) "" + else " under radix " + radix), + ) +} diff --git a/zio-http/jvm/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala b/zio-http/jvm/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala new file mode 100644 index 0000000000..a06f8b2659 --- /dev/null +++ b/zio-http/jvm/src/main/scala/zio/http/codec/PathCodecPlatformSpecific.scala @@ -0,0 +1,9 @@ +package zio.http.codec + +trait PathCodecPlatformSpecific { + def parseLong(s: CharSequence, beginIndex: Int, endIndex: Int, radix: Int): Long = + java.lang.Long.parseLong(s.subSequence(beginIndex, endIndex).toString, radix) + + def parseInt(s: CharSequence, beginIndex: Int, endIndex: Int, radix: Int): Int = + java.lang.Integer.parseInt(s.subSequence(beginIndex, endIndex).toString, radix) +} diff --git a/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala index ef4fe6d436..1829beaffc 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala @@ -35,7 +35,7 @@ import zio.http._ * val pathCodec = empty / "users" / int("user-id") / "posts" / string("post-id") * }}} */ -sealed trait PathCodec[A] { self => +sealed trait PathCodec[A] extends codec.PathCodecPlatformSpecific { self => import PathCodec._ /** @@ -339,7 +339,7 @@ sealed trait PathCodec[A] { self => } else { try { - val int = Integer.parseInt(value, j, end, 10) + val int = parseInt(value, j, end, 10) j = end if (isNegative) stack.push(-int) else stack.push(int) } catch { @@ -358,7 +358,7 @@ sealed trait PathCodec[A] { self => return "Expected long path segment but found: " + value.substring(j, end) } else { try { - val long = java.lang.Long.parseLong(value, j, end, 10) + val long = parseLong(value, j, end, 10) j = end if (isNegative) stack.push(-long) else stack.push(long) } catch { @@ -663,7 +663,7 @@ sealed trait PathCodec[A] { self => final def transformOrFailRight[A2](f: A => A2)(g: A2 => Either[String, A]): PathCodec[A2] = PathCodec.TransformOrFail[A, A2](self, in => Right(f(in)), g) } -object PathCodec { +object PathCodec { /** * Constructs a path codec from a method and a path literal.