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 {