Some more HTTP POST evaluation. Still broken! (0.39.10)
This commit is contained in:
parent
e240768c26
commit
5222f79434
5 changed files with 176 additions and 26 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(fun VERSION 0.39.9 LANGUAGES C)
|
||||
project(fun VERSION 0.39.10 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
|
|
@ -16,21 +16,39 @@
|
|||
cgi = CGI()
|
||||
cgi.content_type("text/html; charset=utf-8")
|
||||
pmap = cgi.params()
|
||||
html = "<html><body><h1>POST fields</h1><ul>"
|
||||
for k in keys(pmap)
|
||||
// Build HTML with a form at the top to submit POST data back to this script
|
||||
html = "<html><body>"
|
||||
html = html + "<h1>Submit data</h1>"
|
||||
html = html + "<form method=\"POST\" action=\"\">"
|
||||
html = html + "<label>A: <input type=\"text\" name=\"a\" value=\"1\"></label><br>"
|
||||
html = html + "<label>B: <input type=\"text\" name=\"b\" value=\"2\"></label><br>"
|
||||
html = html + "<label>C: <input type=\"text\" name=\"c\" value=\"3\"></label><br>"
|
||||
html = html + "<button type=\"submit\">Submit</button>"
|
||||
html = html + "</form>"
|
||||
html = html + "<h1>POST fields</h1><ul>"
|
||||
|
||||
// Always show fields a, b, and c (print names even if no value was submitted)
|
||||
names = ["a", "b", "c"]
|
||||
i_name = 0
|
||||
n_names = len(names)
|
||||
while (i_name < n_names)
|
||||
k = names[i_name]
|
||||
vals = cgi.param_all(k)
|
||||
i = 0; n = len(vals); joined = ""
|
||||
i = 0
|
||||
n = len(vals)
|
||||
joined = ""
|
||||
while (i < n)
|
||||
if (i > 0) joined = joined + ", "
|
||||
joined = joined + cgi.escape_html(vals[i])
|
||||
i = i + 1
|
||||
html = html + "<li><b>" + cgi.escape_html(k) + "</b>: " + joined + "</li>"
|
||||
i_name = i_name + 1
|
||||
html = html + "</ul></body></html>"
|
||||
cgi.send(html)
|
||||
|
||||
/* Expected output (POST with body: "a=1&b=two&b=2"):
|
||||
/* Possible output (POST with body: "a=1&b=2&c=3"):
|
||||
Status: 200 OK
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<html><body><h1>POST fields</h1><ul><li><b>a</b>: 1</li><li><b>b</b>: two, 2</li></ul></body></html>
|
||||
<html><body><h1>POST fields</h1><ul><li><b>a</b>: 1</li><li><b>b</b>: b, 2</li><li><b>c</b>: 3</li></ul></body></html>
|
||||
*/
|
||||
|
|
|
|||
12
examples/snippets/parse_test2.fun
Executable file
12
examples/snippets/parse_test2.fun
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
#include <net/cgi.fun>
|
||||
|
||||
cgi = CGI()
|
||||
pairs = cgi._parse_urlencoded("a=1&b=2&c=3")
|
||||
i = 0
|
||||
n = len(pairs)
|
||||
while (i < n)
|
||||
p = pairs[i]
|
||||
print(to_string(p[0]) + "=" + to_string(p[1]))
|
||||
i = i + 1
|
||||
12
examples/snippets/test_parse.fun
Executable file
12
examples/snippets/test_parse.fun
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
#include <net/cgi.fun>
|
||||
|
||||
cgi = CGI()
|
||||
pairs = cgi._parse_urlencoded("a=1&b=2&c=3")
|
||||
i = 0
|
||||
n = len(pairs)
|
||||
while (i < n)
|
||||
p = pairs[i]
|
||||
print(to_string(p[0]) + "=" + to_string(p[1]))
|
||||
i = i + 1
|
||||
148
lib/net/cgi.fun
148
lib/net/cgi.fun
|
|
@ -43,7 +43,60 @@ class CGI()
|
|||
// Params from POST (x-www-form-urlencoded only)
|
||||
ct = this.env["CONTENT_TYPE"]
|
||||
pd = this.env["POST_DATA"]
|
||||
if (typeof(ct) == "String" && len(ct) > 0 && find(str_to_lower(ct), "application/x-www-form-urlencoded") >= 0)
|
||||
// Fallback: when running under a real CGI, POST data comes from stdin, not env.
|
||||
// Only attempt to read stdin if method is POST, content-length > 0, and POST_DATA is empty.
|
||||
if (!(typeof(pd) == "String" && len(pd) > 0))
|
||||
if (str_to_upper(this.env["REQUEST_METHOD"]) == "POST")
|
||||
cl = to_number(this.env["CONTENT_LENGTH"])
|
||||
if (cl > 0)
|
||||
// Read up to CONTENT_LENGTH bytes from stdin. Some CGI runners may deliver
|
||||
// urlencoded chunks split at '&' without including the delimiter; reconstruct
|
||||
// by inserting '&' between successive chunks.
|
||||
parts = []
|
||||
total = 0
|
||||
// Safeguard: cap iterations to avoid infinite loops on unexpected behavior
|
||||
iter = 0
|
||||
while (total < cl && iter < 100000)
|
||||
chunk = input("")
|
||||
if (!(typeof(chunk) == "String"))
|
||||
break
|
||||
if (len(chunk) == 0)
|
||||
// EOF without newline
|
||||
break
|
||||
push(parts, chunk)
|
||||
total = total + len(chunk)
|
||||
iter = iter + 1
|
||||
// Rebuild with '&' between pieces to restore typical form encoding
|
||||
if (len(parts) > 0)
|
||||
rebuilt = parts[0]
|
||||
idx = 1
|
||||
np = len(parts)
|
||||
while (idx < np)
|
||||
rebuilt = rebuilt + "&" + parts[idx]
|
||||
idx = idx + 1
|
||||
tmp = rebuilt
|
||||
else
|
||||
tmp = ""
|
||||
// Trim to declared content length to drop any stray newline added by the CGI runner
|
||||
if (len(tmp) > cl)
|
||||
pd = substr(tmp, 0, cl)
|
||||
else
|
||||
pd = tmp
|
||||
this.env["POST_DATA"] = pd
|
||||
else
|
||||
// No declared length; try to read a single line (common with simple runners)
|
||||
tmp = input("")
|
||||
if (typeof(tmp) == "String" && len(tmp) > 0)
|
||||
this.env["POST_DATA"] = tmp
|
||||
// Keep local variable in sync so parsing below sees the data
|
||||
pd = this.env["POST_DATA"]
|
||||
// Decide whether to treat body as urlencoded: default yes for POST unless explicitly multipart
|
||||
is_urlencoded = 1
|
||||
if (typeof(ct) == "String" && len(ct) > 0)
|
||||
lct = str_to_lower(ct)
|
||||
if (find(lct, "multipart/form-data") >= 0)
|
||||
is_urlencoded = 0
|
||||
if (is_urlencoded)
|
||||
if (typeof(pd) == "String" && len(pd) > 0)
|
||||
parsed_pd = this._parse_urlencoded(pd)
|
||||
this._merge_params(parsed_pd)
|
||||
|
|
@ -202,11 +255,46 @@ class CGI()
|
|||
resp = resp + "Connection: close\r\n\r\n" + b
|
||||
return resp
|
||||
|
||||
// Minimal url-decoder: '+' -> space; %XX for ASCII printable
|
||||
// Minimal url-decoder: '+' -> space; %XX for ASCII hex
|
||||
fun url_decode(this, s)
|
||||
src = to_string(s)
|
||||
// Simplified for parser-compatibility: only translate '+' to space
|
||||
return str_replace_all(src, "+", " ")
|
||||
out = []
|
||||
i = 0
|
||||
n = len(src)
|
||||
while (i < n)
|
||||
ch = substr(src, i, 1)
|
||||
if (ch == "+")
|
||||
push(out, " ")
|
||||
i = i + 1
|
||||
else if (ch == "%" && i + 2 < n)
|
||||
h1 = substr(src, i + 1, 1)
|
||||
h2 = substr(src, i + 2, 1)
|
||||
// Convert two hex digits to a single character (uppercase/lowercase allowed)
|
||||
hexdigits = "0123456789ABCDEF"
|
||||
hexdigits_l = "0123456789abcdef"
|
||||
v1 = find(hexdigits, str_to_upper(h1))
|
||||
v2 = find(hexdigits, str_to_upper(h2))
|
||||
if (v1 >= 0 && v2 >= 0)
|
||||
code = v1 * 16 + v2
|
||||
// Build single-byte string from code using substr on a constant table
|
||||
// Table of 256 bytes isn't available; approximate by mapping common ASCII 0x20..0x7E
|
||||
ascii = "\x20!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
||||
if (code >= 32 && code <= 126)
|
||||
push(out, substr(ascii, code - 32, 1))
|
||||
else
|
||||
// Non-printable: keep percent sequence as-is
|
||||
push(out, "%")
|
||||
push(out, h1)
|
||||
push(out, h2)
|
||||
i = i + 3
|
||||
else
|
||||
// Not hex, keep as-is
|
||||
push(out, ch)
|
||||
i = i + 1
|
||||
else
|
||||
push(out, ch)
|
||||
i = i + 1
|
||||
return join(out, "")
|
||||
|
||||
fun _merge_params(this, pairs)
|
||||
// pairs: array of [key, value] entries
|
||||
|
|
@ -231,23 +319,43 @@ class CGI()
|
|||
src = to_string(s)
|
||||
if (len(src) == 0)
|
||||
return out
|
||||
parts = str_split(src, "&")
|
||||
i = 0
|
||||
lp = len(parts)
|
||||
while (i < lp)
|
||||
kv = parts[i]
|
||||
if (typeof(kv) == "String" && len(kv) > 0)
|
||||
eq = find(kv, "=")
|
||||
if (eq >= 0)
|
||||
k = substr(kv, 0, eq)
|
||||
v = substr(kv, eq + 1, len(kv) - eq - 1)
|
||||
else
|
||||
k = kv
|
||||
v = ""
|
||||
key = this.url_decode(k)
|
||||
val = this.url_decode(v)
|
||||
push(out, [key, val])
|
||||
// Manually scan so we can treat both '&' and ';' as pair separators (robust across environments)
|
||||
pairs = []
|
||||
buf = []
|
||||
number i = 0
|
||||
number n = len(src)
|
||||
while (i < n)
|
||||
ch = substr(src, i, 1)
|
||||
if (ch == "&" || ch == ";")
|
||||
push(pairs, join(buf, ""))
|
||||
buf = []
|
||||
else
|
||||
push(buf, ch)
|
||||
i = i + 1
|
||||
// tail
|
||||
push(pairs, join(buf, ""))
|
||||
|
||||
j = 0
|
||||
lp = len(pairs)
|
||||
while (j < lp)
|
||||
kv = pairs[j]
|
||||
if (typeof(kv) == "String")
|
||||
token = kv
|
||||
// Trim CR/LF that might trail if body ended with a newline
|
||||
// Reuse str_trim which trims spaces and CR/LF
|
||||
token = str_trim(token)
|
||||
if (len(token) > 0)
|
||||
eq = find(token, "=")
|
||||
if (eq >= 0)
|
||||
k = substr(token, 0, eq)
|
||||
v = substr(token, eq + 1, len(token) - eq - 1)
|
||||
else
|
||||
k = token
|
||||
v = ""
|
||||
key = this.url_decode(k)
|
||||
val = this.url_decode(v)
|
||||
push(out, [key, val])
|
||||
j = j + 1
|
||||
return out
|
||||
|
||||
fun _parse_cookies(this, cookie_str)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue