mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vweb: add multipart/form-data parser and file upload (#8160)
This commit is contained in:
parent
b44ec4921f
commit
f7c251f8f3
6
examples/vweb/file_upload/index.html
Normal file
6
examples/vweb/file_upload/index.html
Normal file
@ -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>
|
14
examples/vweb/file_upload/upload.html
Normal file
14
examples/vweb/file_upload/upload.html
Normal file
@ -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>
|
33
examples/vweb/file_upload/vweb_example.v
Normal file
33
examples/vweb/file_upload/vweb_example.v
Normal file
@ -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
|
||||
form map[string]string
|
||||
query map[string]string
|
||||
files map[string][]FileData
|
||||
headers string // response headers
|
||||
done bool
|
||||
page_gen_start i64
|
||||
@ -60,6 +61,13 @@ pub mut:
|
||||
max_chunk_len int = 20
|
||||
}
|
||||
|
||||
struct FileData {
|
||||
pub:
|
||||
filename string
|
||||
content_type string
|
||||
data string
|
||||
}
|
||||
|
||||
// declaring init_once in your App struct is optional
|
||||
pub fn (ctx Context) init_once() {}
|
||||
|
||||
@ -316,6 +324,9 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
|
||||
mut body := ''
|
||||
mut in_headers := true
|
||||
mut len := 0
|
||||
// File receive stuff
|
||||
mut ct := 'text/plain'
|
||||
mut boundary := ''
|
||||
// for line in lines[1..] {
|
||||
for lindex in 0 .. 100 {
|
||||
// println(j)
|
||||
@ -324,6 +335,14 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
|
||||
break
|
||||
}
|
||||
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 in_headers {
|
||||
// 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()
|
||||
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 {
|
||||
// println(sline)
|
||||
headers << sline
|
||||
if sline.starts_with('Content-Length') {
|
||||
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 {
|
||||
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 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) {
|
||||
files := os.ls(directory_path) or { panic(err) }
|
||||
if files.len > 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user