diff --git a/examples/vweb/file_upload/index.html b/examples/vweb/file_upload/index.html
new file mode 100644
index 0000000000..a2bffad0c4
--- /dev/null
+++ b/examples/vweb/file_upload/index.html
@@ -0,0 +1,6 @@
+
File Upload
+
+
diff --git a/examples/vweb/file_upload/upload.html b/examples/vweb/file_upload/upload.html
new file mode 100644
index 0000000000..e80359de7f
--- /dev/null
+++ b/examples/vweb/file_upload/upload.html
@@ -0,0 +1,14 @@
+
+
+File amount: @fdata.len
+
+@for i, data in fdata
+
+Filename: @data.filename
+Type: @data.content_type
+
+@{files[i]}
+
+@end
+
+Back
diff --git a/examples/vweb/file_upload/vweb_example.v b/examples/vweb/file_upload/vweb_example.v
new file mode 100644
index 0000000000..f3ab36e459
--- /dev/null
+++ b/examples/vweb/file_upload/vweb_example.v
@@ -0,0 +1,33 @@
+module main
+
+import vweb
+
+const (
+ port = 8082
+)
+
+struct App {
+ vweb.Context
+}
+
+fn main() {
+ vweb.run(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', '
', '\n\r', '
' '\t', ' ', ' ', ' '])
+ }
+
+ return $vweb.html()
+}
diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v
index 7ac148a3ce..4a4899fa70 100644
--- a/vlib/vweb/vweb.v
+++ b/vlib/vweb/vweb.v
@@ -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(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(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(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(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 {