summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorflywind <43030857+xflywind@users.noreply.github.com>2020-12-27 04:59:32 -0600
committerGitHub <noreply@github.com>2020-12-27 11:59:32 +0100
commit689504081f476ec23ebfd7123ad2f8b27c387a97 (patch)
tree3d290fd319da7b6ba2657ec9df2dc6ab46ac3d0a
parent2bdc479622c465e570fdb87df112dd56ddc9030f (diff)
downloadNim-689504081f476ec23ebfd7123ad2f8b27c387a97.tar.gz
follow #15357 and move decodeQuery (#15860)
* follow #15357 and move decodeQuery
* solve problem one
* minor
* deprecate decodeData
* add changelog and since
* add testcase for decodeQuery
-rw-r--r--changelog.md1
-rw-r--r--lib/pure/cgi.nim50
-rw-r--r--lib/pure/uri.nim43
-rw-r--r--tests/stdlib/tdecodequery.nim7
4 files changed, 67 insertions, 34 deletions
diff --git a/changelog.md b/changelog.md
index a6a865514..6b12891c5 100644
--- a/changelog.md
+++ b/changelog.md
@@ -55,6 +55,7 @@
 
 - `writeStackTrace` is available in JS backend now.
 
+- Added `decodeQuery` to `std/uri`.
 - `strscans.scanf` now supports parsing single characters.
 - `strscans.scanTuple` added which uses `strscans.scanf` internally, returning a tuple which can be unpacked for easier usage of `scanf`. 
 
diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim
index cb64a3b1b..d3a762911 100644
--- a/lib/pure/cgi.nim
+++ b/lib/pure/cgi.nim
@@ -32,8 +32,10 @@
 import strutils, os, strtabs, cookies, uri
 export uri.encodeUrl, uri.decodeUrl
 
+
 import std/private/decode_helpers
 
+
 proc addXmlChar(dest: var string, c: char) {.inline.} =
   case c
   of '&': add(dest, "&amp;")
@@ -53,18 +55,15 @@ proc xmlEncode*(s: string): string =
   for i in 0..len(s)-1: addXmlChar(result, s[i])
 
 type
-  CgiError* = object of IOError ## exception that is raised if a CGI error occurs
+  CgiError* = object of IOError ## Exception that is raised if a CGI error occurs
   RequestMethod* = enum ## the used request method
     methodNone,         ## no REQUEST_METHOD environment variable
     methodPost,         ## query uses the POST method
     methodGet           ## query uses the GET method
 
 proc cgiError*(msg: string) {.noreturn.} =
-  ## raises an ECgi exception with message `msg`.
-  var e: ref CgiError
-  new(e)
-  e.msg = msg
-  raise e
+  ## Raises a ``CgiError`` exception with message `msg`.
+  raise newException(CgiError, msg)
 
 proc getEncodedData(allowedMethods: set[RequestMethod]): string =
   case getEnv("REQUEST_METHOD").string
@@ -88,40 +87,23 @@ 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.
-  proc parseData(data: string, i: int, field: var string): int =
-    result = i
-    while result < data.len:
-      case data[result]
-      of '%': add(field, decodePercent(data, result))
-      of '+': add(field, ' ')
-      of '=', '&': break
-      else: add(field, data[result])
-      inc(result)
-
-  var i = 0
-  var name = ""
-  var value = ""
-  # decode everything in one pass:
-  while i < data.len:
-    setLen(name, 0) # reuse memory
-    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)
-    yield (name.TaintedString, value.TaintedString)
-    if i < data.len:
-      if data[i] == '&': inc(i)
-      else: cgiError("'&' expected")
+  try:
+    for (key, value) in uri.decodeQuery(data):
+      yield (key, value)
+  except UriParseError as e:
+    cgiError(e.msg)
 
 iterator decodeData*(allowedMethods: set[RequestMethod] =
        {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] =
   ## Reads and decodes CGI data and yields the (name, value) pairs the
   ## data consists of. If the client does not use a method listed in the
-  ## `allowedMethods` set, an `ECgi` exception is raised.
+  ## `allowedMethods` set, a ``CgiError`` exception is raised.
   let data = getEncodedData(allowedMethods)
-  for key, value in decodeData(data):
-    yield (key, value)
+  try:
+    for (key, value) in uri.decodeQuery(data):
+      yield (key, value)
+  except UriParseError as e:
+    cgiError(e.msg)
 
 proc readData*(allowedMethods: set[RequestMethod] =
                {methodNone, methodPost, methodGet}): StringTableRef =
diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim
index e993c240d..7f553be1a 100644
--- a/lib/pure/uri.nim
+++ b/lib/pure/uri.nim
@@ -59,6 +59,13 @@ type
     opaque*: bool
     isIpv6: bool # not expose it for compatibility.
 
+  UriParseError* = object of ValueError
+
+
+proc uriParseError*(msg: string) {.noreturn.} =
+  ## Raises a ``UriParseError`` exception with message `msg`.
+  raise newException(UriParseError, msg)
+
 func encodeUrl*(s: string, usePlus = true): string =
   ## Encodes a URL according to RFC3986.
   ##
@@ -153,6 +160,42 @@ func encodeQuery*(query: openArray[(string, string)], usePlus = true,
       result.add('=')
       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.
+  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")]
+
+  proc parseData(data: string, i: int, field: var string): int =
+    result = i
+    while result < data.len:
+      case data[result]
+      of '%': add(field, decodePercent(data, result))
+      of '+': add(field, ' ')
+      of '=', '&': break
+      else: add(field, data[result])
+      inc(result)
+
+  var i = 0
+  var name = ""
+  var value = ""
+  # decode everything in one pass:
+  while i < data.len:
+    setLen(name, 0) # reuse memory
+    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)
+    yield (name.TaintedString, value.TaintedString)
+    if i < data.len:
+      if data[i] == '&': inc(i)
+      else:
+        uriParseError("'&' expected at index '$#' for '$#'" % [$i, data])
+
 func parseAuthority(authority: string, result: var Uri) =
   var i = 0
   var inPort = false
diff --git a/tests/stdlib/tdecodequery.nim b/tests/stdlib/tdecodequery.nim
new file mode 100644
index 000000000..ae180742f
--- /dev/null
+++ b/tests/stdlib/tdecodequery.nim
@@ -0,0 +1,7 @@
+import std/[uri, sequtils]
+
+
+block:
+  doAssert toSeq(decodeQuery("a=1&b=0")) == @[("a", "1"), ("b", "0")]
+  doAssertRaises(UriParseError):
+    discard toSeq(decodeQuery("a=1&b=2c=6"))