mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vweb: fix parsing of form fields, send with multipart/form-data (by JS fetch)
This commit is contained in:
parent
f1bc5e6d46
commit
9047f7c9a8
@ -304,14 +304,19 @@ pub fn (err MultiplePathAttributesError) msg() string {
|
|||||||
// HTTP request body. It is the inverse of parse_multipart_form. Returns
|
// HTTP request body. It is the inverse of parse_multipart_form. Returns
|
||||||
// (body, boundary).
|
// (body, boundary).
|
||||||
// Note: Form keys should not contain quotes
|
// Note: Form keys should not contain quotes
|
||||||
|
[manualfree]
|
||||||
fn multipart_form_body(form map[string]string, files map[string][]FileData) (string, string) {
|
fn multipart_form_body(form map[string]string, files map[string][]FileData) (string, string) {
|
||||||
alpha_numeric := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
rboundary := rand.ulid()
|
||||||
boundary := rand.string_from_set(alpha_numeric, 64)
|
defer {
|
||||||
|
unsafe { rboundary.free() }
|
||||||
|
}
|
||||||
mut sb := strings.new_builder(1024)
|
mut sb := strings.new_builder(1024)
|
||||||
|
defer {
|
||||||
|
unsafe { sb.free() }
|
||||||
|
}
|
||||||
for name, value in form {
|
for name, value in form {
|
||||||
sb.write_string('\r\n--')
|
sb.write_string('\r\n--')
|
||||||
sb.write_string(boundary)
|
sb.write_string(rboundary)
|
||||||
sb.write_string('\r\nContent-Disposition: form-data; name="')
|
sb.write_string('\r\nContent-Disposition: form-data; name="')
|
||||||
sb.write_string(name)
|
sb.write_string(name)
|
||||||
sb.write_string('"\r\n\r\n')
|
sb.write_string('"\r\n\r\n')
|
||||||
@ -320,7 +325,7 @@ fn multipart_form_body(form map[string]string, files map[string][]FileData) (str
|
|||||||
for name, fs in files {
|
for name, fs in files {
|
||||||
for f in fs {
|
for f in fs {
|
||||||
sb.write_string('\r\n--')
|
sb.write_string('\r\n--')
|
||||||
sb.write_string(boundary)
|
sb.write_string(rboundary)
|
||||||
sb.write_string('\r\nContent-Disposition: form-data; name="')
|
sb.write_string('\r\nContent-Disposition: form-data; name="')
|
||||||
sb.write_string(name)
|
sb.write_string(name)
|
||||||
sb.write_string('"; filename="')
|
sb.write_string('"; filename="')
|
||||||
@ -332,9 +337,9 @@ fn multipart_form_body(form map[string]string, files map[string][]FileData) (str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.write_string('\r\n--')
|
sb.write_string('\r\n--')
|
||||||
sb.write_string(boundary)
|
sb.write_string(rboundary)
|
||||||
sb.write_string('--')
|
sb.write_string('--')
|
||||||
return sb.str(), boundary
|
return sb.str(), rboundary
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LineSegmentIndexes {
|
struct LineSegmentIndexes {
|
||||||
|
@ -154,6 +154,28 @@ ${contents[1]}\r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_parse_multipart_form2() {
|
||||||
|
boundary := '---------------------------27472781931927549291906391339'
|
||||||
|
data := '--${boundary}\r
|
||||||
|
Content-Disposition: form-data; name="username"\r
|
||||||
|
\r
|
||||||
|
admin\r
|
||||||
|
--${boundary}\r
|
||||||
|
Content-Disposition: form-data; name="password"\r
|
||||||
|
\r
|
||||||
|
admin123\r
|
||||||
|
--${boundary}--\r
|
||||||
|
'
|
||||||
|
form, files := parse_multipart_form(data, boundary)
|
||||||
|
for k, v in form {
|
||||||
|
eprintln('> k: ${k} | v: ${v}')
|
||||||
|
eprintln('>> k.bytes(): ${k.bytes()}')
|
||||||
|
eprintln('>> v.bytes(): ${v.bytes()}')
|
||||||
|
}
|
||||||
|
assert form['username'] == 'admin'
|
||||||
|
assert form['password'] == 'admin123'
|
||||||
|
}
|
||||||
|
|
||||||
fn test_multipart_form_body() {
|
fn test_multipart_form_body() {
|
||||||
files := {
|
files := {
|
||||||
'foo': [
|
'foo': [
|
||||||
@ -175,7 +197,7 @@ fn test_multipart_form_body() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_large_body() {
|
fn test_parse_large_body() {
|
||||||
body := 'A'.repeat(101) // greater than max_bytes
|
body := 'A'.repeat(10_001) // greater than max_bytes
|
||||||
req := 'GET / HTTP/1.1\r\nContent-Length: ${body.len}\r\n\r\n${body}'
|
req := 'GET / HTTP/1.1\r\nContent-Length: ${body.len}\r\n\r\n${body}'
|
||||||
mut reader_ := reader(req)
|
mut reader_ := reader(req)
|
||||||
result := parse_request(mut reader_)!
|
result := parse_request(mut reader_)!
|
||||||
|
@ -67,22 +67,25 @@ fn parse_query_from_url(url urllib.URL) map[string]string {
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boundary_start = 'boundary='
|
||||||
|
|
||||||
fn parse_form_from_request(request http.Request) !(map[string]string, map[string][]http.FileData) {
|
fn parse_form_from_request(request http.Request) !(map[string]string, map[string][]http.FileData) {
|
||||||
mut form := map[string]string{}
|
if request.method !in [http.Method.post, .put, .patch] {
|
||||||
mut files := map[string][]http.FileData{}
|
return map[string]string{}, map[string][]http.FileData{}
|
||||||
if request.method in methods_with_form {
|
|
||||||
ct := request.header.get(.content_type) or { '' }.split(';').map(it.trim_left(' \t'))
|
|
||||||
if 'multipart/form-data' in ct {
|
|
||||||
boundary := ct.filter(it.starts_with('boundary='))
|
|
||||||
if boundary.len != 1 {
|
|
||||||
return error('detected more that one form-data boundary')
|
|
||||||
}
|
|
||||||
// omit 'boundary="' and the last '"'
|
|
||||||
boundary_str := boundary[0].substr(10, boundary[0].len - 1)
|
|
||||||
form, files = http.parse_multipart_form(request.data, boundary_str)
|
|
||||||
} else {
|
|
||||||
form = http.parse_form(request.data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return form, files
|
ct := request.header.get(.content_type) or { '' }.split(';').map(it.trim_left(' \t'))
|
||||||
|
if 'multipart/form-data' in ct {
|
||||||
|
boundaries := ct.filter(it.starts_with(vweb.boundary_start))
|
||||||
|
if boundaries.len != 1 {
|
||||||
|
return error('detected more that one form-data boundary')
|
||||||
|
}
|
||||||
|
boundary := boundaries[0].all_after(vweb.boundary_start)
|
||||||
|
if boundary.len > 0 && boundary[0] == `"` {
|
||||||
|
// quotes are send by our http.post_multipart_form/2:
|
||||||
|
return http.parse_multipart_form(request.data, boundary.trim('"'))
|
||||||
|
}
|
||||||
|
// Firefox and other browsers, do not use quotes around the boundary:
|
||||||
|
return http.parse_multipart_form(request.data, boundary)
|
||||||
|
}
|
||||||
|
return http.parse_form(request.data), map[string][]http.FileData{}
|
||||||
}
|
}
|
||||||
|
@ -238,6 +238,19 @@ fn test_http_client_multipart_form_data() {
|
|||||||
assert x.body == files[0].data
|
assert x.body == files[0].data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_login_with_multipart_form_data_send_by_fetch() {
|
||||||
|
mut form_config := http.PostMultipartFormConfig{
|
||||||
|
form: {
|
||||||
|
'username': 'myusername'
|
||||||
|
'password': 'mypassword123'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x := http.post_multipart_form('http://${localserver}/login', form_config)!
|
||||||
|
assert x.status_code == 200
|
||||||
|
assert x.status_msg == 'OK'
|
||||||
|
assert x.body == 'username: xmyusernamex | password: xmypassword123x'
|
||||||
|
}
|
||||||
|
|
||||||
fn test_host() {
|
fn test_host() {
|
||||||
mut req := http.Request{
|
mut req := http.Request{
|
||||||
url: 'http://${localserver}/with_host'
|
url: 'http://${localserver}/with_host'
|
||||||
|
@ -89,6 +89,11 @@ pub fn (mut app App) json_echo() vweb.Result {
|
|||||||
return app.ok(app.req.data)
|
return app.ok(app.req.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
['/login'; post]
|
||||||
|
pub fn (mut app App) login_form(username string, password string) vweb.Result {
|
||||||
|
return app.html('username: x${username}x | password: x${password}x')
|
||||||
|
}
|
||||||
|
|
||||||
['/form_echo'; post]
|
['/form_echo'; post]
|
||||||
pub fn (mut app App) form_echo() vweb.Result {
|
pub fn (mut app App) form_echo() vweb.Result {
|
||||||
app.set_content_type(app.req.header.get(.content_type) or { '' })
|
app.set_content_type(app.req.header.get(.content_type) or { '' })
|
||||||
|
Loading…
Reference in New Issue
Block a user