From 531cd585100f9de429e85731175860dd3bc51a36 Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Fri, 19 Jun 2026 01:13:28 +0200 Subject: [PATCH] Fix Form.parse to decode + as space per application/x-www-form-urlencoded spec --- http.carp | 14 ++++++++++---- test/http.carp | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/http.carp b/http.carp index 8c32e43..6c4a6b0 100644 --- a/http.carp +++ b/http.carp @@ -567,8 +567,14 @@ Returns `(Maybe String)`.") (Result.Error e) (IO.errorln &e)) ```") (defmodule Form + (private form-unescape) + (hidden form-unescape) + (defn form-unescape [s] + (URI.unescape &(String.join " " &(String.split-by s &[\+])))) + (doc parse "parses a URL-encoded form body into a `(Map String String)`. -Keys and values are percent-decoded.") +Keys and values are percent-decoded. The `+` character is decoded as space +per the `application/x-www-form-urlencoded` specification.") (defn parse [s] (let-do [m (the (Map String String) {}) pairs &(String.split-by s &[\&])] @@ -576,9 +582,9 @@ Keys and values are percent-decoded.") (let [pair (Array.unsafe-nth pairs i) eq (String.index-of pair \=)] (if (= eq -1) - (let [k (URI.unescape pair)] (Map.put! &m &k "")) - (let [k (URI.unescape &(String.byte-slice pair 0 eq)) - v (URI.unescape + (let [k (form-unescape pair)] (Map.put! &m &k "")) + (let [k (form-unescape &(String.byte-slice pair 0 eq)) + v (form-unescape &(String.byte-slice pair (Int.inc eq) (String.length pair)))] (Map.put! &m &k &v))))) (Result.Success m)))) diff --git a/test/http.carp b/test/http.carp index ccd60ba..2c1784e 100644 --- a/test/http.carp +++ b/test/http.carp @@ -275,6 +275,35 @@ _ @"FAIL") "Form.parse handles key without value") + (assert-equal test + "hello world" + &(match (the (Result (Map String String) String) + (Form.parse "msg=hello+world")) + (Result.Success m) (Map.get &m "msg") + _ @"") + "Form.parse decodes + as space") + + (assert-equal test + "a b c" + &(match (the (Result (Map String String) String) (Form.parse "x=a+b+c")) + (Result.Success m) (Map.get &m "x") + _ @"") + "Form.parse decodes multiple + as spaces") + + (assert-equal test + "+" + &(match (the (Result (Map String String) String) (Form.parse "x=%2B")) + (Result.Success m) (Map.get &m "x") + _ @"") + "Form.parse decodes %2B as literal +") + + (assert-equal test + "a b" + &(match (the (Result (Map String String) String) (Form.parse "key+name=a+b")) + (Result.Success m) (Map.get &m "key name") + _ @"") + "Form.parse decodes + in keys too") + ; --------------------------------------------------------------------------- ; Request.ignore-body? ; ---------------------------------------------------------------------------