Skip to content

Commit

Permalink
Merge pull request #350 from softwaremill/url-spec-compat
Browse files Browse the repository at this point in the history
Added method for parsing string body
  • Loading branch information
adamw committed May 27, 2024
2 parents 76141cc + 51b87b5 commit 3cf523b
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 6 deletions.
35 changes: 33 additions & 2 deletions core/src/main/scala/sttp/model/internal/Rfc3986.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,47 @@ object Rfc3986 {

val QueryWithBrackets: Set[Char] = Query ++ Set('[', ']')

/** @param spaceAsPlus
/** Encode string using UTF-8
* @param spaceAsPlus
* In the query, space is encoded as a `+`. In other contexts, it should be %-encoded as `%20`.
* @param encodePlus
* Should `+` (which is the encoded form of space in the query) be %-encoded.
*/
def encode(allowedCharacters: Set[Char], spaceAsPlus: Boolean = false, encodePlus: Boolean = false)(
s: String
): String =
encode(s, "UTF-8", allowedCharacters, spaceAsPlus, encodePlus)


/** Encode string using encoding, leave allowedCharacters as they are
* @param s
* String to be encoded
* @param enc
* Encoding to be used
* @param allowedCharacters
* Characters to be not to be encoded
*/
def encode(s: String, enc: String, allowedCharacters: Set[Char]): String =
encode(s, enc, allowedCharacters, spaceAsPlus = false, encodePlus = false)

/** @param spaceAsPlus
* In the query, space is encoded as a `+`. In other contexts, it should be %-encoded as `%20`.
* @param encodePlus
* Should `+` (which is the encoded form of space in the query) be %-encoded.
*/
private def encode(
s: String,
enc: String,
allowedCharacters: Set[Char],
spaceAsPlus: Boolean,
encodePlus: Boolean
): String = {
val sb = new StringBuilder()
// based on https://gist.github.com/teigen/5865923
for (b <- s.getBytes("UTF-8")) {
val bytes: Array[Byte] = s.getBytes(enc)
var i = 0
while (i < bytes.length) {
val b: Byte = bytes(i)
val c = (b & 0xff).toChar
if (c == '+' && encodePlus) sb.append("%2B") // #48
else if (allowedCharacters(c)) sb.append(c)
Expand All @@ -34,6 +64,7 @@ object Rfc3986 {
sb.append("%")
sb.append(Rfc3986Compatibility.formatByte(b))
}
i += 1
}
sb.toString
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ private[sttp] object UriCompatibility {
}

def encodeQuery(s: String, enc: String): String = URIUtils.encodeURIComponent(s)

def encodeBodyPart(s: String, enc: String): String = URIUtils.encodeURIComponent(s)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ private[sttp] object UriCompatibility {
}

def encodeQuery(s: String, enc: String): String = URLEncoder.encode(s, enc)

def encodeBodyPart(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ private[sttp] object UriCompatibility {
}

def encodeQuery(s: String, enc: String): String = URLEncoder.encode(s, enc)

def encodeBodyPart(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved)
}
28 changes: 24 additions & 4 deletions core/src/test/scala/sttp/model/UriTests.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package sttp.model

import java.net.URI

import Uri._
import org.scalatest.TryValues
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import sttp.model.Uri._
import sttp.model.internal.UriCompatibility

import java.net.URI

class UriTests extends AnyFunSuite with Matchers with TryValues {
class UriTests extends AnyFunSuite with Matchers with TryValues with UriTestsExtension {

val HS = HostSegment
val PS = PathSegment
Expand Down Expand Up @@ -116,6 +117,9 @@ class UriTests extends AnyFunSuite with Matchers with TryValues {
List(QS.KeyValue("k1&", "v1&", valueEncoding = QuerySegmentEncoding.Relaxed)) -> "k1%26=v1&",
List(QS.Plain("ą/ę&+;?", encoding = QuerySegmentEncoding.Relaxed)) -> "%C4%85/%C4%99&+;?",
List(QS.KeyValue("k", "v1,v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%2Cv2",
List(QS.KeyValue("k", "v1-v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1-v2",
List(QS.KeyValue("k", "v1_v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1_v2",
List(QS.KeyValue("k", "v1.v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1.v2",
List(QS.KeyValue("k", "v1,v2")) -> "k=v1,v2",
List(QS.KeyValue("k", "+1234")) -> "k=%2B1234",
List(QS.KeyValue("k", "[]")) -> "k=%5B%5D",
Expand All @@ -130,6 +134,22 @@ class UriTests extends AnyFunSuite with Matchers with TryValues {
}
}

private val bodyPartEncodingTestData = List(
"v1,v2" -> "v1%2Cv2",
"v1-v2" -> "v1-v2",
"v1~v2" -> "v1~v2",
"v1_v2" -> "v1_v2",
"v1.v2" -> "v1.v2",
)

for {
(segments, expected) <- bodyPartEncodingTestData
} {
test(s"$segments should serialize to$expected") {
UriCompatibility.encodeBodyPart(segments, "utf-8") should endWith(expected)
}
}

val hostTestData = List(
"www.mikołak.net" -> "http://www.xn--mikoak-6db.net",
"192.168.1.0" -> "http://192.168.1.0",
Expand Down
21 changes: 21 additions & 0 deletions core/src/test/scalajs/sttp/model/UriTestsExtension.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sttp.model

import org.scalatest.TryValues
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import sttp.model.Uri._

trait UriTestsExtension extends AnyFunSuite with Matchers with TryValues { this: UriTests =>

private val tildeEncodingTest = List(
List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2"
)

for {
(segments, expected) <- tildeEncodingTest
} {
test(s"$segments should serialize to$expected") {
testUri.copy(querySegments = segments).toString should endWith(expected)
}
}
}
21 changes: 21 additions & 0 deletions core/src/test/scalajvm/sttp/model/UriTestsExtension.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sttp.model

import org.scalatest.TryValues
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import sttp.model.Uri._

trait UriTestsExtension extends AnyFunSuite with Matchers with TryValues { this: UriTests =>

private val tildeEncodingTest = List(
List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%7Ev2"
)

for {
(segments, expected) <- tildeEncodingTest
} {
test(s"$segments should serialize to$expected") {
testUri.copy(querySegments = segments).toString should endWith(expected)
}
}
}
8 changes: 8 additions & 0 deletions core/src/test/scalanative/sttp/model/UriTestsExtension.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package sttp.model

import org.scalatest.TryValues
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import sttp.model.Uri._

trait UriTestsExtension

0 comments on commit 3cf523b

Please sign in to comment.