vweb: add multipart/form-data parser and file upload (#8160)
parent
b44ec4921f
commit
f7c251f8f3
|
@ -0,0 +1,6 @@
|
||||||
|
<h2>File Upload</h2>
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data" action="/upload">
|
||||||
|
<input type="file" name="upfile" multiple>
|
||||||
|
<input type="submit" value="Press">
|
||||||
|
</form>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
File amount: @fdata.len
|
||||||
|
|
||||||
|
@for i, data in fdata
|
||||||
|
|
||||||
|
<h2>Filename: @data.filename</h2>
|
||||||
|
<h2>Type: @data.content_type</h2>
|
||||||
|
|
||||||
|
<p>@{files[i]}</p>
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
<a href="/">Back</a>
|
|
@ -0,0 +1,33 @@
|
||||||
|
module main
|
||||||
|
|
||||||
|
import vweb
|
||||||
|
|
||||||
|
const (
|
||||||
|
port = 8082
|
||||||
|
)
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
vweb.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
vweb.run<App>(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut app App) index() vweb.Result {
|
||||||
|
return $vweb.html()
|
||||||
|
}
|
||||||
|
|
||||||
|
[post]
|
||||||
|
['/upload']
|
||||||
|
pub fn (mut app App) upload() vweb.Result {
|
||||||
|
fdata := app.files['upfile']
|
||||||
|
|
||||||
|
mut files := []vweb.RawHtml{}
|
||||||
|
|
||||||
|
for d in fdata {
|
||||||
|
files << d.data.replace_each(['\n', '<br>', '\n\r', '<br>' '\t', ' ', ' ', ' '])
|
||||||
|
}
|
||||||
|
|
||||||
|
return $vweb.html()
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ pub mut:
|
||||||
static_mime_types map[string]string
|
static_mime_types map[string]string
|
||||||
form map[string]string
|
form map[string]string
|
||||||
query map[string]string
|
query map[string]string
|
||||||
|
files map[string][]FileData
|
||||||
headers string // response headers
|
headers string // response headers
|
||||||
done bool
|
done bool
|
||||||
page_gen_start i64
|
page_gen_start i64
|
||||||
|
@ -60,6 +61,13 @@ pub mut:
|
||||||
max_chunk_len int = 20
|
max_chunk_len int = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FileData {
|
||||||
|
pub:
|
||||||
|
filename string
|
||||||
|
content_type string
|
||||||
|
data string
|
||||||
|
}
|
||||||
|
|
||||||
// declaring init_once in your App struct is optional
|
// declaring init_once in your App struct is optional
|
||||||
pub fn (ctx Context) init_once() {}
|
pub fn (ctx Context) init_once() {}
|
||||||
|
|
||||||
|
@ -316,6 +324,9 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
|
||||||
mut body := ''
|
mut body := ''
|
||||||
mut in_headers := true
|
mut in_headers := true
|
||||||
mut len := 0
|
mut len := 0
|
||||||
|
// File receive stuff
|
||||||
|
mut ct := 'text/plain'
|
||||||
|
mut boundary := ''
|
||||||
// for line in lines[1..] {
|
// for line in lines[1..] {
|
||||||
for lindex in 0 .. 100 {
|
for lindex in 0 .. 100 {
|
||||||
// println(j)
|
// println(j)
|
||||||
|
@ -324,6 +335,14 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
sline := strip(line)
|
sline := strip(line)
|
||||||
|
// Parse content type
|
||||||
|
if sline.len >= 14 && sline[..14] == 'Content-Type: ' {
|
||||||
|
args := sline[14..].split('; ')
|
||||||
|
ct = args[0]
|
||||||
|
if args.len > 1 {
|
||||||
|
boundary = args[1][9..]
|
||||||
|
}
|
||||||
|
}
|
||||||
if sline == '' {
|
if sline == '' {
|
||||||
// if in_headers {
|
// if in_headers {
|
||||||
// End of headers, no body => exit
|
// End of headers, no body => exit
|
||||||
|
@ -340,8 +359,13 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
|
||||||
body += read_body.bytestr()
|
body += read_body.bytestr()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if ct == 'multipart/form-data' && sline == boundary {
|
||||||
|
body += boundary
|
||||||
|
read_body := io.read_all(reader: reader) or { []byte{} }
|
||||||
|
body += read_body.bytestr()
|
||||||
|
break
|
||||||
|
}
|
||||||
if in_headers {
|
if in_headers {
|
||||||
// println(sline)
|
|
||||||
headers << sline
|
headers << sline
|
||||||
if sline.starts_with('Content-Length') {
|
if sline.starts_with('Content-Length') {
|
||||||
len = sline.all_after(': ').int()
|
len = sline.all_after(': ').int()
|
||||||
|
@ -374,7 +398,11 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
if req.method in methods_with_form {
|
if req.method in methods_with_form {
|
||||||
app.parse_form(req.data)
|
if ct == 'multipart/form-data' {
|
||||||
|
app.parse_multipart_form(body, boundary)
|
||||||
|
} else {
|
||||||
|
app.parse_form(req.data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if vals.len < 2 {
|
if vals.len < 2 {
|
||||||
$if debug {
|
$if debug {
|
||||||
|
@ -603,6 +631,63 @@ pub fn (mut ctx Context) parse_form(s string) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[manualfree]
|
||||||
|
pub fn (mut ctx Context) parse_multipart_form(s string, b string) {
|
||||||
|
if ctx.req.method !in methods_with_form {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a := s.split('$b')[1..]
|
||||||
|
fields := a[..a.len - 1]
|
||||||
|
for field in fields {
|
||||||
|
lines := field.split_into_lines()[1..]
|
||||||
|
mut l := 0
|
||||||
|
// Parse name
|
||||||
|
disposition_data := lines[l].split('; ')[1..]
|
||||||
|
l++
|
||||||
|
name := disposition_data[0][6..disposition_data[0].len - 1]
|
||||||
|
// Parse files
|
||||||
|
if disposition_data.len > 1 {
|
||||||
|
filename := disposition_data[1][10..disposition_data[1].len - 1]
|
||||||
|
ct := lines[l].split(': ')[1]
|
||||||
|
l++
|
||||||
|
if name !in ctx.files {
|
||||||
|
ctx.files[name] = []FileData{}
|
||||||
|
}
|
||||||
|
mut sb := strings.new_builder(field.len)
|
||||||
|
for i in l + 1 .. lines.len - 1 {
|
||||||
|
sb.writeln(lines[i])
|
||||||
|
}
|
||||||
|
ctx.files[name] << FileData{
|
||||||
|
filename: filename
|
||||||
|
content_type: ct
|
||||||
|
data: sb.str()
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
filename.free()
|
||||||
|
ct.free()
|
||||||
|
sb.free()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mut sb := strings.new_builder(field.len)
|
||||||
|
for i in l + 1 .. lines.len - 1 {
|
||||||
|
sb.writeln(lines[i])
|
||||||
|
}
|
||||||
|
ctx.form[name] = sb.str()
|
||||||
|
unsafe {
|
||||||
|
disposition_data.free()
|
||||||
|
name.free()
|
||||||
|
sb.free()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
fields.free()
|
||||||
|
s.free()
|
||||||
|
b.free()
|
||||||
|
a.free()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) {
|
fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) {
|
||||||
files := os.ls(directory_path) or { panic(err) }
|
files := os.ls(directory_path) or { panic(err) }
|
||||||
if files.len > 0 {
|
if files.len > 0 {
|
||||||
|
|
Loading…
Reference in New Issue