From 8f9140b705011eea9a7344466362662f3106cd69 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 7 Jun 2021 15:33:19 +0300 Subject: [PATCH] New Context.FormFiles method --- HISTORY.md | 1 + context/context.go | 47 +++++++++++++++++++++++++++++++++++++++++++- core/netutil/addr.go | 23 ++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 2a694e9a..6b9230e0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -396,6 +396,7 @@ var dirOpts = iris.DirOptions{ ## New Context Methods +- `Context.FormFiles(key string, before ...func(*Context, *multipart.FileHeader) bool) (files []multipart.File, headers []*multipart.FileHeader, err error)` method. - `Context.ReadURL(ptr interface{}) error` shortcut of `ReadParams` and `ReadQuery`. Binds URL dynamic path parameters and URL query parameters to the given "ptr" pointer of a struct value. - `Context.SetUser(User)` and `Context.User() User` to store and retrieve an authenticated client. Read more [here](https://github.com/iris-contrib/middleware/issues/63). - `Context.SetLogoutFunc(fn interface{}, persistenceArgs ...interface{})` and `Logout(args ...interface{}) error` methods to allow different kind of auth middlewares to be able to set a "logout" a user/client feature with a single function, the route handler may not be aware of the implementation of the authentication used. diff --git a/context/context.go b/context/context.go index 1fed82ae..29907a07 100644 --- a/context/context.go +++ b/context/context.go @@ -1943,6 +1943,51 @@ func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, return ctx.request.FormFile(key) } +// FormFiles same as FormFile but may return multiple file inputs based on a key, e.g. "files[]". +func (ctx *Context) FormFiles(key string, before ...func(*Context, *multipart.FileHeader) bool) (files []multipart.File, headers []*multipart.FileHeader, err error) { + err = ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory()) + if err != nil { + return + } + + if ctx.request.MultipartForm != nil { + fhs := ctx.request.MultipartForm.File + if n := len(fhs); n > 0 { + files = make([]multipart.File, 0, n) + headers = make([]*multipart.FileHeader, 0, n) + + innerLoop: + for _, header := range fhs[key] { + // Fix an issue that net/http has, + // an attacker can push a filename + // which could lead to override existing system files + // by ../../$header. + // Reported by Frank through security reports. + header.Filename = strings.ReplaceAll(header.Filename, "../", "") + header.Filename = strings.ReplaceAll(header.Filename, "..\\", "") + + for _, b := range before { + if !b(ctx, header) { + continue innerLoop + } + } + + file, fErr := header.Open() + if fErr != nil { // exit on first error but return the succeed. + return files, headers, fErr + } + + files = append(files, file) + headers = append(headers, header) + } + } + + return + } + + return nil, nil, http.ErrMissingFile +} + // UploadFormFiles uploads any received file(s) from the client // to the system physical location "destDirectory". // @@ -1967,7 +2012,7 @@ func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, // the `WithPostMaxMemory` configurator or by `SetMaxRequestBodySize` or // by the `LimitRequestBodySize` middleware (depends the use case). // -// See `FormFile` to a more controlled way to receive a file. +// See `FormFile` and `FormFiles` to a more controlled way to receive a file. // // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-files func (ctx *Context) UploadFormFiles(destDirectory string, before ...func(*Context, *multipart.FileHeader) bool) (uploaded []*multipart.FileHeader, n int64, err error) { diff --git a/core/netutil/addr.go b/core/netutil/addr.go index 12db4e74..47ecdf3b 100644 --- a/core/netutil/addr.go +++ b/core/netutil/addr.go @@ -98,6 +98,29 @@ var IsLoopbackHost = func(requestHost string) bool { return valid } +/* +func isLoopbackHostGoVersion(host string) bool { + ip := net.ParseIP(host) + if ip != nil { + return ip.IsLoopback() + } + + // Host is not an ip, perform lookup. + addrs, err := net.LookupHost(host) + if err != nil { + return false + } + + for _, addr := range addrs { + if !net.ParseIP(addr).IsLoopback() { + return false + } + } + + return true +} +*/ + const ( // defaultServerHostname returns the default hostname which is "localhost" defaultServerHostname = "localhost"