Permalink
Browse files

! http: fix raw queries still performing %-decoding and not being ren…

…dered as raw, fixes #330

Up to now queries parsed in Uri.ParsingMode.RelaxedWithRawQuery did not have a
dedicated model class. Rather they were parsed into the `key` member of
Uri.Query.Cons instances. This patch changes this, raw queries are now parsed
into the `value` member of Uri.Query.Raw instances. Additionally the Query
type now has an additional `isRaw: Boolean` member.
  • Loading branch information...
sirthias committed Jun 20, 2013
1 parent 9a919fe commit a915b8ff22c0d55d5df1a96e6e0c6d8177a7b238
@@ -400,6 +400,7 @@ object Uri {
with ToStringRenderable {
def key: String
def value: String
+ def isRaw: Boolean
def +:(kvp: (String, String)) = Query.Cons(kvp._1, kvp._2, this)
def get(key: String): Option[String] = {
@tailrec def g(q: Query): Option[String] = if (q.isEmpty) None else if (q.key == key) Some(q.value) else g(q.tail)
@@ -421,15 +422,17 @@ object Uri {
}
def render[R <: Rendering](r: R): r.type = render(r, UTF8)
def render[R <: Rendering](r: R, charset: Charset): r.type = {
- def enc(r: Rendering, s: String): r.type =
- encode(r, s, charset, QUERY_FRAGMENT_CHAR & ~(AMP | EQUAL | PLUS), replaceSpaces = true)
+ def enc(s: String): Unit = encode(r, s, charset, QUERY_FRAGMENT_CHAR & ~(AMP | EQUAL | PLUS), replaceSpaces = true)
@tailrec def append(q: Query): r.type =
- if (!q.isEmpty) {
- if (q ne this) r ~~ '&'
- enc(r, q.key)
- if (!q.value.isEmpty) enc(r ~~ '=', q.value)
- append(q.tail)
- } else r
+ q match {
+ case Query.Empty r
+ case Query.Cons(key, value, tail)
+ if (q ne this) r ~~ '&'
+ enc(key)
+ if (!value.isEmpty) { r ~~ '='; enc(value) }
+ append(tail)
+ case Query.Raw(value) r ~~ value
+ }
append(this)
}
override def newBuilder: mutable.Builder[(String, String), Query] = Query.newBuilder
@@ -466,14 +469,23 @@ object Uri {
case object Empty extends Query {
def key = throw new NoSuchElementException("key of empty path")
def value = throw new NoSuchElementException("value of empty path")
+ def isRaw = true
override def isEmpty = true
override def head = throw new NoSuchElementException("head of empty list")
override def tail = throw new UnsupportedOperationException("tail of empty query")
}
case class Cons(key: String, value: String, override val tail: Query) extends Query {
+ def isRaw = false
override def isEmpty = false
override def head = (key, value)
}
+ case class Raw(value: String) extends Query {
+ def key = ""
+ def isRaw = true
+ override def isEmpty = false
+ override def head = ("", value)
+ override def tail = Empty
+ }
}
val defaultPorts: Map[String, Int] =
@@ -306,7 +306,12 @@ private[http] class UriParser(input: ParserInput, charset: Charset, mode: Uri.Pa
val tail = if (ch('&')) readKVP() else Query.Empty
Query.Cons(key, value, tail)
}
- _query = readKVP()
+ _query =
+ if (mode == Uri.ParsingMode.RelaxedWithRawQuery) {
+ val start = cursor
+ while (is(current, PARSE_QUERY_CHAR)) advance()
+ if (cursor > start) Query.Raw(slice(start, cursor)) else Query.Empty
+ } else readKVP()
true
}
@@ -450,7 +455,8 @@ private[http] object UriParser {
final val RESERVED = GEN_DELIM | SUB_DELIM | QUESTIONMARK | COLON | SLASH | HASH | AT
final val OTHER_VCHAR = 0x200000
- final val VCHAR = OTHER_VCHAR | UNRESERVED | HEX_DIGIT | RESERVED | AT | COLON | SLASH | QUESTIONMARK | DASH | DOT | HASH
+ final val PERCENT = 0x400000
+ final val VCHAR = OTHER_VCHAR | UNRESERVED | HEX_DIGIT | RESERVED | AT | COLON | SLASH | QUESTIONMARK | DASH | DOT | HASH | PERCENT
// FRAGMENT/QUERY and PATH characters have two classes of acceptable characters: one that strictly
// follows rfc3986, which should be used for rendering urls, and one relaxed, which accepts all visible
@@ -459,9 +465,9 @@ private[http] object UriParser {
final val QUERY_FRAGMENT_CHAR = UNRESERVED | SUB_DELIM | COLON | AT | SLASH | QUESTIONMARK
final val PATH_SEGMENT_CHAR = UNRESERVED | SUB_DELIM | COLON | AT
- final val RELAXED_FRAGMENT_CHAR = VCHAR
- final val RELAXED_PATH_SEGMENT_CHAR = VCHAR & ~(SLASH | QUESTIONMARK | HASH)
- final val RELAXED_QUERY_CHAR = VCHAR & ~(AMP | EQUAL | HASH)
+ final val RELAXED_FRAGMENT_CHAR = VCHAR & ~PERCENT
+ final val RELAXED_PATH_SEGMENT_CHAR = VCHAR & ~(PERCENT | SLASH | QUESTIONMARK | HASH)
+ final val RELAXED_QUERY_CHAR = VCHAR & ~(PERCENT | AMP | EQUAL | HASH)
final val RAW_QUERY_CHAR = VCHAR & ~HASH
private[this] val props = new Array[Int](128)
@@ -492,6 +498,7 @@ private[http] object UriParser {
mark(EQUAL, '=')
mark(SPACE, ' ')
mark(HASH, '#')
+ mark(PERCENT, '%')
mark(OTHER_VCHAR, '<', '>', '\\', '^', '`', '{', '|', '}')
@@ -240,7 +240,8 @@ class UriSpec extends Specification {
}
"be parsed and rendered correctly in relaxed-with-raw-query mode" in {
val test = parser(Uri.ParsingMode.RelaxedWithRawQuery)
- test("a^=b&c").toString === "a%5E%3Db%26c"
+ test("a^=b&c").toString === "a^=b&c"
+ test("a%2Fb") === Uri.Query.Raw("a%2Fb")
}
"properly support the retrieval interface" in {
val query = Query("a=1&b=2&c=3&b=4&b")

0 comments on commit a915b8f

Please sign in to comment.