summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md10
-rw-r--r--lib/pure/cgi.nim14
-rw-r--r--lib/pure/uri.nim38
-rw-r--r--tests/stdlib/turi.nim3
4 files changed, 38 insertions, 27 deletions
diff --git a/changelog.md b/changelog.md
index 285977488..8bdbc7416 100644
--- a/changelog.md
+++ b/changelog.md
@@ -96,6 +96,16 @@
 with other backends. see #9125. Use `-d:nimLegacyJsRound` for previous behavior.
 - Added `socketstream` module that wraps sockets in the stream interface
 
+- Changed the behavior of `uri.decodeQuery` when there are unencoded `=`
+  characters in the decoded values. Prior versions would raise an error. This is
+  no longer the case to comply with the HTML spec and other languages
+  implementations. Old behavior can be obtained with
+  `-d:nimLegacyParseQueryStrict`. `cgi.decodeData` which uses the same
+  underlying code is also updated the same way.
+
+
+
+
 - Added `math.signbit`.
 
 - Removed the optional `longestMatch` parameter of the `critbits._WithPrefix` iterators (it never worked reliably)
diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim
index 8d827f555..3b8dad849 100644
--- a/lib/pure/cgi.nim
+++ b/lib/pure/cgi.nim
@@ -84,11 +84,8 @@ proc getEncodedData(allowedMethods: set[RequestMethod]): string =
 iterator decodeData*(data: string): tuple[key, value: TaintedString] =
   ## Reads and decodes CGI data and yields the (name, value) pairs the
   ## data consists of.
-  try:
-    for (key, value) in uri.decodeQuery(data):
-      yield (key, value)
-  except UriParseError as e:
-    cgiError(e.msg)
+  for (key, value) in uri.decodeQuery(data):
+    yield (key, value)
 
 iterator decodeData*(allowedMethods: set[RequestMethod] =
        {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] =
@@ -96,11 +93,8 @@ iterator decodeData*(allowedMethods: set[RequestMethod] =
   ## data consists of. If the client does not use a method listed in the
   ## `allowedMethods` set, a `CgiError` exception is raised.
   let data = getEncodedData(allowedMethods)
-  try:
-    for (key, value) in uri.decodeQuery(data):
-      yield (key, value)
-  except UriParseError as e:
-    cgiError(e.msg)
+  for (key, value) in uri.decodeQuery(data):
+    yield (key, value)
 
 proc readData*(allowedMethods: set[RequestMethod] =
                {methodNone, methodPost, methodGet}): StringTableRef =
diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim
index 7f553be1a..c8ed28536 100644
--- a/lib/pure/uri.nim
+++ b/lib/pure/uri.nim
@@ -161,22 +161,26 @@ func encodeQuery*(query: openArray[(string, string)], usePlus = true,
       result.add(encodeUrl(val, usePlus))
 
 iterator decodeQuery*(data: string): tuple[key, value: TaintedString] =
-  ## Reads and decodes query string ``data`` and yields the (key, value) pairs the
-  ## data consists of.
+  ## Reads and decodes query string `data` and yields the `(key, value)` pairs
+  ## the data consists of. If compiled with `-d:nimLegacyParseQueryStrict`, an
+  ## error is raised when there is an unencoded `=` character in a decoded
+  ## value, which was the behavior in Nim < 1.5.1
   runnableExamples:
-    import std/sugar
-    let s = collect(newSeq):
-      for k, v in decodeQuery("foo=1&bar=2"): (k, v)
-    doAssert s == @[("foo", "1"), ("bar", "2")]
+    import std/sequtils
+    doAssert toSeq(decodeQuery("foo=1&bar=2=3")) == @[("foo", "1"), ("bar", "2=3")]
+    doAssert toSeq(decodeQuery("&a&=b&=&&")) == @[("", ""), ("a", ""), ("", "b"), ("", ""), ("", "")]
 
-  proc parseData(data: string, i: int, field: var string): int =
+  proc parseData(data: string, i: int, field: var string, sep: char): int =
     result = i
     while result < data.len:
-      case data[result]
+      let c = data[result]
+      case c
       of '%': add(field, decodePercent(data, result))
       of '+': add(field, ' ')
-      of '=', '&': break
-      else: add(field, data[result])
+      of '&': break
+      else:
+        if c == sep: break
+        else: add(field, data[result])
       inc(result)
 
   var i = 0
@@ -185,16 +189,20 @@ iterator decodeQuery*(data: string): tuple[key, value: TaintedString] =
   # decode everything in one pass:
   while i < data.len:
     setLen(name, 0) # reuse memory
-    i = parseData(data, i, name)
+    i = parseData(data, i, name, '=')
     setLen(value, 0) # reuse memory
     if i < data.len and data[i] == '=':
       inc(i) # skip '='
-      i = parseData(data, i, value)
+      when defined(nimLegacyParseQueryStrict):
+        i = parseData(data, i, value, '=')
+      else:
+        i = parseData(data, i, value, '&')
     yield (name.TaintedString, value.TaintedString)
     if i < data.len:
-      if data[i] == '&': inc(i)
-      else:
-        uriParseError("'&' expected at index '$#' for '$#'" % [$i, data])
+      when defined(nimLegacyParseQueryStrict):
+        if data[i] != '&':
+          uriParseError("'&' expected at index '$#' for '$#'" % [$i, data])
+      inc(i)
 
 func parseAuthority(authority: string, result: var Uri) =
   var i = 0
diff --git a/tests/stdlib/turi.nim b/tests/stdlib/turi.nim
index 6354850fc..1abab7a19 100644
--- a/tests/stdlib/turi.nim
+++ b/tests/stdlib/turi.nim
@@ -287,8 +287,7 @@ template main() =
 
   block: # decodeQuery
     doAssert toSeq(decodeQuery("a=1&b=0")) == @[("a", "1"), ("b", "0")]
-    doAssertRaises(UriParseError):
-      discard toSeq(decodeQuery("a=1&b=2c=6"))
+    doAssert toSeq(decodeQuery("a=1&b=2c=6")) == @[("a", "1"), ("b", "2c=6")]
 
 static: main()
 main()