         ] if url-decode-iter
     ] if ;
 
+PRIVATE>
+
+: url-decode ( str -- decoded )
+    [ 0 swap url-decode-iter ] "" make utf8 decode ;
+
+<PRIVATE
+
 : add-query-param ( value key assoc -- )
     [
         {

    ] "" make ;

: escape-uri-char? ( ch -- ? )
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789;,/?:@&=+$-_.!~*'()#" member? not ; inline

: encode-uri ( str -- str' )
    [


PRIVATE>

: query-decode ( str -- decoded )
    "+" split "%20" join url-decode ;

: query>assoc ( query -- assoc )
    dup [
        "&;" split <linked-hash> [
            [
                [ "=" split1 [ dup [ query-decode ] when ] bi@ swap ] dip
                add-query-param
            ] curry each
        ] keep
    ] when ;

: assoc>query ( assoc -- str )
    [
        [
            [ url-encode-full ] dip [
                dup array? [ 1array ] unless
                [ url-encode-full "=" glue , ] with each
            ] [ , ] if*
        ] assoc-each
    ] { } make "&" join ;

: escape-uri-component-char? ( ch -- ? )
    {
        [ letter? ]
        [ LETTER? ]
        [ digit? ]
        [ "-_.!~*'()" member? ]
    } 1|| not ; foldable

: encode-uri-component ( str -- str' )
    [
