From 099479a894c48cca35ca86997fa3cd4fb20d4fe3 Mon Sep 17 00:00:00 2001 From: Justyn Date: Thu, 14 Jan 2021 08:47:33 -0600 Subject: [PATCH] feat: add a config option to bypass mime content type sniffing (#60) --- cmd/config.go | 17 +++++++---- lib/dir.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/webdav.go | 7 +++-- 3 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 lib/dir.go diff --git a/cmd/config.go b/cmd/config.go index 8fcd459..50ca8b9 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -122,8 +122,11 @@ func parseUsers(raw []interface{}, c *lib.Config) { } user.Handler = &webdav.Handler{ - Prefix: c.User.Handler.Prefix, - FileSystem: webdav.Dir(user.Scope), + Prefix: c.User.Handler.Prefix, + FileSystem: lib.WebDavDir{ + Dir: webdav.Dir(user.Scope), + NoSniff: c.NoSniff, + }, LockSystem: webdav.NewMemLS(), } @@ -179,12 +182,16 @@ func readConfig(flags *pflag.FlagSet) *lib.Config { Modify: getOptB(flags, "modify"), Rules: []*lib.Rule{}, Handler: &webdav.Handler{ - Prefix: getOpt(flags, "prefix"), - FileSystem: webdav.Dir(getOpt(flags, "scope")), + Prefix: getOpt(flags, "prefix"), + FileSystem: lib.WebDavDir{ + Dir: webdav.Dir(getOpt(flags, "scope")), + NoSniff: getOptB(flags, "nosniff"), + }, LockSystem: webdav.NewMemLS(), }, }, - Auth: getOptB(flags, "auth"), + Auth: getOptB(flags, "auth"), + NoSniff: getOptB(flags, "nosniff"), Cors: lib.CorsCfg{ Enabled: false, Credentials: false, diff --git a/lib/dir.go b/lib/dir.go new file mode 100644 index 0000000..8c16db8 --- /dev/null +++ b/lib/dir.go @@ -0,0 +1,83 @@ +package lib + +import ( + "context" + "mime" + "os" + "path" + + "golang.org/x/net/webdav" +) + +// NoSniffFileInfo wraps any generic FileInfo interface and bypasses mime type sniffing. +type NoSniffFileInfo struct { + os.FileInfo +} + +func (w NoSniffFileInfo) ContentType(ctx context.Context) (contentType string, err error) { + if mimeType := mime.TypeByExtension(path.Ext(w.FileInfo.Name())); mimeType != "" { + // We can figure out the mime from the extension. + return mimeType, nil + } else { + // We can't figure out the mime type without sniffing, call it an octet stream. + return "application/octet-stream", nil + } +} + +type WebDavDir struct { + webdav.Dir + NoSniff bool +} + +func (d WebDavDir) Stat(ctx context.Context, name string) (os.FileInfo, error) { + // Skip wrapping if NoSniff is off + if !d.NoSniff { + return d.Dir.Stat(ctx, name) + } + + info, err := d.Dir.Stat(ctx, name) + if err != nil { + return nil, err + } + + return NoSniffFileInfo{info}, nil +} + +func (d WebDavDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { + // Skip wrapping if NoSniff is off + if !d.NoSniff { + return d.Dir.OpenFile(ctx, name, flag, perm) + } + + file, err := d.Dir.OpenFile(ctx, name, flag, perm) + if err != nil { + return nil, err + } + + return WebDavFile{File: file}, nil +} + +type WebDavFile struct { + webdav.File +} + +func (f WebDavFile) Stat() (os.FileInfo, error) { + info, err := f.File.Stat() + if err != nil { + return nil, err + } + + return NoSniffFileInfo{info}, nil +} + +func (f WebDavFile) Readdir(count int) (fis []os.FileInfo, err error) { + fis, err = f.File.Readdir(count) + if err != nil { + return nil, err + } + + for i := range fis { + fis[i] = NoSniffFileInfo{fis[i]} + } + return fis, nil +} diff --git a/lib/webdav.go b/lib/webdav.go index 5b47624..5481c49 100755 --- a/lib/webdav.go +++ b/lib/webdav.go @@ -20,9 +20,10 @@ type CorsCfg struct { // Config is the configuration of a WebDAV instance. type Config struct { *User - Auth bool - Cors CorsCfg - Users map[string]*User + Auth bool + NoSniff bool + Cors CorsCfg + Users map[string]*User } // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.