diff options
author | adamw <adam@warski.org> | 2017-09-04 11:28:01 +0200 |
---|---|---|
committer | adamw <adam@warski.org> | 2017-09-04 11:28:01 +0200 |
commit | f0f428c85ef6f24e98f9465248d9155d29dc5b4d (patch) | |
tree | 32b0921a5d4b5bed12fdd0225ac8dfdf6a696b8f | |
parent | caf69a9742bf6a8593208af30ec27b7006e91328 (diff) | |
download | sttp-f0f428c85ef6f24e98f9465248d9155d29dc5b4d.tar.gz sttp-f0f428c85ef6f24e98f9465248d9155d29dc5b4d.tar.bz2 sttp-f0f428c85ef6f24e98f9465248d9155d29dc5b4d.zip |
#33: properly escape path segments in URIs
-rw-r--r-- | core/src/main/scala/com/softwaremill/sttp/Uri.scala | 27 | ||||
-rw-r--r-- | core/src/test/scala/com/softwaremill/sttp/UriTests.scala | 10 |
2 files changed, 19 insertions, 18 deletions
diff --git a/core/src/main/scala/com/softwaremill/sttp/Uri.scala b/core/src/main/scala/com/softwaremill/sttp/Uri.scala index ce758a1..ef94dd9 100644 --- a/core/src/main/scala/com/softwaremill/sttp/Uri.scala +++ b/core/src/main/scala/com/softwaremill/sttp/Uri.scala @@ -88,7 +88,8 @@ case class Uri(scheme: String, override def toString: String = { def encodeUserInfo(ui: UserInfo): String = - encode(ui.username) + ui.password.fold("")(":" + encode(_)) + encode(Rfc3986.UserInfo)(ui.username) + ui.password.fold("")( + ":" + encode(Rfc3986.UserInfo)(_)) @tailrec def encodeQueryFragments(qfs: List[QueryFragment], @@ -112,12 +113,12 @@ case class Uri(scheme: String, encodeQueryFragments(t, previousWasPlain = false, sb) } - val schemeS = encode(scheme) + val schemeS = encode(Rfc3986.Scheme)(scheme) val userInfoS = userInfo.fold("")(encodeUserInfo(_) + "@") - val hostS = encode(host) + val hostS = encode(Rfc3986.Host)(host) val portS = port.fold("")(":" + _) val pathPrefixS = if (path.isEmpty) "" else "/" - val pathS = path.map(encode).mkString("/") + val pathS = path.map(encode(Rfc3986.PathSegment)).mkString("/") val queryPrefixS = if (queryFragments.isEmpty) "" else "?" val queryS = encodeQueryFragments(queryFragments.toList, @@ -127,13 +128,6 @@ case class Uri(scheme: String, s"$schemeS://$userInfoS$hostS$portS$pathPrefixS$pathS$queryPrefixS$queryS$fragS" } - private def encode(s: Any): String = { - // space is encoded as a +, which is only valid in the query; - // in other contexts, it must be percent-encoded; see - // https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20 - URLEncoder.encode(String.valueOf(s), "UTF-8").replaceAll("\\+", "%20") - } - private def encodeQuery(s: String, relaxed: Boolean): String = if (relaxed) encodeQueryRelaxed(s) else @@ -146,19 +140,24 @@ case class Uri(scheme: String, val SubDelims: Set[Char] = Set('!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=') val PChar: Set[Char] = Unreserved ++ SubDelims ++ Set(':', '@') + + val Scheme: Set[Char] = AlphaNum ++ Set('+', '-', '.') + val UserInfo: Set[Char] = Unreserved ++ SubDelims + val Host: Set[Char] = Unreserved ++ SubDelims + val PathSegment: Set[Char] = PChar val Query: Set[Char] = PChar ++ Set('/', '?') val Fragment: Set[Char] = Query } // https://stackoverflow.com/questions/2322764/what-characters-must-be-escaped-in-an-http-query-string private def encodeQueryRelaxed(s: String): String = - encode(s, Rfc3986.Query) + encode(Rfc3986.Query)(s) // https://stackoverflow.com/questions/2053132/is-a-colon-safe-for-friendly-url-use/2053640#2053640 private def encodeFragment(s: String): String = - encode(s, Rfc3986.Fragment) + encode(Rfc3986.Fragment)(s) - private def encode(s: String, allowedCharacters: Set[Char]): String = { + private def encode(allowedCharacters: Set[Char])(s: String): String = { val sb = new StringBuilder() // based on https://gist.github.com/teigen/5865923 for (c <- s) { diff --git a/core/src/test/scala/com/softwaremill/sttp/UriTests.scala b/core/src/test/scala/com/softwaremill/sttp/UriTests.scala index c9bb8b5..f421413 100644 --- a/core/src/test/scala/com/softwaremill/sttp/UriTests.scala +++ b/core/src/test/scala/com/softwaremill/sttp/UriTests.scala @@ -31,17 +31,19 @@ class UriTests extends FunSuite with Matchers { List("a b", "z", "ą:ę"), List(QF.KeyValue("p:1", "v&v"), QF.KeyValue("p2", "v v")), None) -> - "http://exa%20mple.com/a%20b/z/%C4%85%3A%C4%99?p%3A1=v%26v&p2=v+v", + "http://exa%20mple.com/a%20b/z/%C4%85:%C4%99?p%3A1=v%26v&p2=v+v", Uri("http", - Some(UserInfo("us&er", Some("pa ss"))), + Some(UserInfo("us&e/r", Some("pa ss"))), "example.com", None, Nil, Nil, None) -> - "http://us%26er:pa%20ss@example.com", + "http://us&e%2Fr:pa%20ss@example.com", Uri("http", None, "example.com", None, Nil, Nil, Some("f:g/h i")) -> - "http://example.com#f:g/h%20i" + "http://example.com#f:g/h%20i", + Uri("http", None, "example.com", None, List("key=value"), Nil, None) -> + "http://example.com/key=value" ) for { |