diff options
author | flywind <43030857+xflywind@users.noreply.github.com> | 2020-12-27 04:59:32 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-27 11:59:32 +0100 |
commit | 689504081f476ec23ebfd7123ad2f8b27c387a97 (patch) | |
tree | 3d290fd319da7b6ba2657ec9df2dc6ab46ac3d0a | |
parent | 2bdc479622c465e570fdb87df112dd56ddc9030f (diff) | |
download | Nim-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.md | 1 | ||||
-rw-r--r-- | lib/pure/cgi.nim | 50 | ||||
-rw-r--r-- | lib/pure/uri.nim | 43 | ||||
-rw-r--r-- | tests/stdlib/tdecodequery.nim | 7 |
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, "&") @@ -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")) |