net.http: add post_multipart_form function (#11511)
parent
f295469fac
commit
ead5e66afd
|
@ -80,6 +80,22 @@ pub fn post_form(url string, data map[string]string) ?Response {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PostMultipartFormConfig {
|
||||||
|
form map[string]string
|
||||||
|
files map[string][]FileData
|
||||||
|
}
|
||||||
|
|
||||||
|
// post_multipart_form sends a POST HTTP request to the URL with multipart form data
|
||||||
|
pub fn post_multipart_form(url string, conf PostMultipartFormConfig) ?Response {
|
||||||
|
body, boundary := multipart_form_body(conf.form, conf.files)
|
||||||
|
return fetch(
|
||||||
|
method: .post
|
||||||
|
url: url
|
||||||
|
header: new_header(key: .content_type, value: 'multipart/form-data; boundary="$boundary"')
|
||||||
|
data: body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// put sends a PUT HTTP request to the URL with a string data
|
// put sends a PUT HTTP request to the URL with a string data
|
||||||
pub fn put(url string, data string) ?Response {
|
pub fn put(url string, data string) ?Response {
|
||||||
return fetch(
|
return fetch(
|
||||||
|
|
|
@ -6,6 +6,7 @@ module http
|
||||||
import io
|
import io
|
||||||
import net
|
import net
|
||||||
import net.urllib
|
import net.urllib
|
||||||
|
import rand
|
||||||
import strings
|
import strings
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -261,6 +262,43 @@ struct MultiplePathAttributesError {
|
||||||
code int
|
code int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// multipart_form_body converts form and file data into a multipart/form
|
||||||
|
// HTTP request body. It is the inverse of parse_multipart_form. Returns
|
||||||
|
// (body, boundary).
|
||||||
|
// NB: Form keys should not contain quotes
|
||||||
|
fn multipart_form_body(form map[string]string, files map[string][]FileData) (string, string) {
|
||||||
|
alpha_numeric := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
boundary := rand.string_from_set(alpha_numeric, 64)
|
||||||
|
|
||||||
|
mut sb := strings.new_builder(1024)
|
||||||
|
for name, value in form {
|
||||||
|
sb.write_string('\r\n--')
|
||||||
|
sb.write_string(boundary)
|
||||||
|
sb.write_string('\r\nContent-Disposition: form-data; name="')
|
||||||
|
sb.write_string(name)
|
||||||
|
sb.write_string('"\r\n\r\n')
|
||||||
|
sb.write_string(value)
|
||||||
|
}
|
||||||
|
for name, fs in files {
|
||||||
|
for f in fs {
|
||||||
|
sb.write_string('\r\n--')
|
||||||
|
sb.write_string(boundary)
|
||||||
|
sb.write_string('\r\nContent-Disposition: form-data; name="')
|
||||||
|
sb.write_string(name)
|
||||||
|
sb.write_string('"; filename="')
|
||||||
|
sb.write_string(f.filename)
|
||||||
|
sb.write_string('"\r\nContent-Type: ')
|
||||||
|
sb.write_string(f.content_type)
|
||||||
|
sb.write_string('\r\n\r\n')
|
||||||
|
sb.write_string(f.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.write_string('\r\n--')
|
||||||
|
sb.write_string(boundary)
|
||||||
|
sb.write_string('--')
|
||||||
|
return sb.str(), boundary
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_multipart_form(body string, boundary string) (map[string]string, map[string][]FileData) {
|
pub fn parse_multipart_form(body string, boundary string) (map[string]string, map[string][]FileData) {
|
||||||
sections := body.split(boundary)
|
sections := body.split(boundary)
|
||||||
fields := sections[1..sections.len - 1]
|
fields := sections[1..sections.len - 1]
|
||||||
|
@ -275,8 +313,7 @@ pub fn parse_multipart_form(body string, boundary string) (map[string]string, ma
|
||||||
name := disposition['name'] or { continue }
|
name := disposition['name'] or { continue }
|
||||||
// Parse files
|
// Parse files
|
||||||
// TODO: filename*
|
// TODO: filename*
|
||||||
if 'filename' in disposition {
|
if filename := disposition['filename'] {
|
||||||
filename := disposition['filename']
|
|
||||||
// Parse Content-Type header
|
// Parse Content-Type header
|
||||||
if lines.len == 1 || !lines[1].to_lower().starts_with('content-type:') {
|
if lines.len == 1 || !lines[1].to_lower().starts_with('content-type:') {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -128,6 +128,24 @@ ${contents[1]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_multipart_form_body() {
|
||||||
|
files := {
|
||||||
|
'foo': [FileData{
|
||||||
|
filename: 'bar.v'
|
||||||
|
content_type: 'application/octet-stream'
|
||||||
|
data: 'baz'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
form := {
|
||||||
|
'fooz': 'buzz'
|
||||||
|
}
|
||||||
|
|
||||||
|
body, boundary := multipart_form_body(form, files)
|
||||||
|
parsed_form, parsed_files := parse_multipart_form(body, boundary)
|
||||||
|
assert parsed_files == files
|
||||||
|
assert parsed_form == form
|
||||||
|
}
|
||||||
|
|
||||||
fn test_parse_large_body() ? {
|
fn test_parse_large_body() ? {
|
||||||
body := 'A'.repeat(101) // greater than max_bytes
|
body := 'A'.repeat(101) // 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'
|
||||||
|
|
Loading…
Reference in New Issue