Suave is a simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition.
Here’s how System.Web.HttpUtility.ParseQueryString, Python and Node.js/browsers parse it (they all handle unescaped equal signs in query paramter values):
❯ dotnet fsi
Microsoft (R) F# Interactive version 12.4.0.0 for F# 7.0
Copyright (c) Microsoft Corporation. All Rights Reserved.
For help type #help;;
> System.Web.HttpUtility.ParseQueryString("?q=a=b").Get("q");;
val it: string = "a=b"
❯ python3
Python 3.11.2 (main, Feb 16 2023, 02:55:59) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib.parse
>>> urllib.parse.parse_qs("q=a=b")["q"]
['a=b']
❯ node
Welcome to Node.js v16.17.0.
Type ".help" for more information.
> new URLSearchParams("?q=a=b").get("q")
'a=b'
I believe this is the relevant section in the WHATWG URL spec:
If bytes contains a 0x3D (=), then let name be the bytes from the start of bytes up to but excluding its first 0x3D (=), and let value be the bytes, if any, after the first 0x3D (=) up to the end of bytes. If 0x3D (=) is the first byte, then name will be the empty byte sequence. If it is the last, then value will be the empty byte sequence.
This is a valid URL:
http://example.com/?q=a=b
It has a query parameter called
q
with the valuea=b
.ctx.request.query
does not contain theq
parameter at all though – it’s dropped. Because of the equals sign in the value.A workaround is to escape that equals sign:
http://example.com/?q=a%3Db
Here’s how
System.Web.HttpUtility.ParseQueryString
, Python and Node.js/browsers parse it (they all handle unescaped equal signs in query paramter values):I believe this is the relevant section in the WHATWG URL spec:
https://url.spec.whatwg.org/#urlencoded-parsing
This code splits on
=
but only uses the value if the splitted list has length 2. It needs to either use the head as the key, and join the tail back up with=
, or split just on the first equals sign: https://github.com/SuaveIO/suave/blob/8efe4b32ea0dc52f36c10c8d8fec8191c6ae901c/src/Suave/Utils/Parsing.fs#L18-L27