diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2021-05-30 18:25:11 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-30 18:25:11 +0800 |
commit | effad26c0e7348d27f7fdd1c0cbf120d7558cf67 (patch) | |
tree | f6e4bfa168d1de5adaa40f73c52849a3fb529469 /modules | |
parent | d79c8bc30241c98e044de40aa673138e819f765f (diff) | |
download | gitea-effad26c0e7348d27f7fdd1c0cbf120d7558cf67.tar.gz gitea-effad26c0e7348d27f7fdd1c0cbf120d7558cf67.zip |
Improve assets handler middleware (#15961)
* Use route to serve assets but not middleware
* Fix build error with bindata tag
* convert path to absolute
* fix build
* reduce function stack
* Add tests for assets
* Remove test for assets because they are not generated
* Use a http function to serve assets
* Still use middleware to serve assets then less middleware stack for assets
* Move serveContent to original position
* remove unnecessary blank line change
* Fix bug for /assets* requests
* clean code
Co-authored-by: zeripath <art27@cantab.net>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/public/dynamic.go | 9 | ||||
-rw-r--r-- | modules/public/public.go | 177 | ||||
-rw-r--r-- | modules/public/static.go | 14 |
3 files changed, 76 insertions, 124 deletions
diff --git a/modules/public/dynamic.go b/modules/public/dynamic.go index a57b636369..0bfe38bc3f 100644 --- a/modules/public/dynamic.go +++ b/modules/public/dynamic.go @@ -13,12 +13,11 @@ import ( "time" ) -// Static implements the static handler for serving assets. -func Static(opts *Options) func(next http.Handler) http.Handler { - return opts.staticHandler(opts.Directory) +func fileSystem(dir string) http.FileSystem { + return http.Dir(dir) } -// ServeContent serve http content -func ServeContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { +// serveContent serve http content +func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { http.ServeContent(w, req, fi.Name(), modtime, content) } diff --git a/modules/public/public.go b/modules/public/public.go index c68f980352..a58709d86f 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -5,85 +5,82 @@ package public import ( - "log" "net/http" + "os" "path" "path/filepath" "strings" "code.gitea.io/gitea/modules/httpcache" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) // Options represents the available options to configure the handler. type Options struct { Directory string - IndexFile string - SkipLogging bool - FileSystem http.FileSystem Prefix string + CorsHandler func(http.Handler) http.Handler } -// KnownPublicEntries list all direct children in the `public` directory -var KnownPublicEntries = []string{ - "css", - "fonts", - "img", - "js", - "serviceworker.js", - "vendor", -} - -// Custom implements the static handler for serving custom assets. -func Custom(opts *Options) func(next http.Handler) http.Handler { - return opts.staticHandler(path.Join(setting.CustomPath, "public")) -} - -// staticFileSystem implements http.FileSystem interface. -type staticFileSystem struct { - dir *http.Dir -} - -func newStaticFileSystem(directory string) staticFileSystem { - if !filepath.IsAbs(directory) { - directory = filepath.Join(setting.AppWorkPath, directory) +// AssetsHandler implements the static handler for serving custom or original assets. +func AssetsHandler(opts *Options) func(next http.Handler) http.Handler { + var custPath = filepath.Join(setting.CustomPath, "public") + if !filepath.IsAbs(custPath) { + custPath = filepath.Join(setting.AppWorkPath, custPath) + } + if !filepath.IsAbs(opts.Directory) { + opts.Directory = filepath.Join(setting.AppWorkPath, opts.Directory) + } + if !strings.HasSuffix(opts.Prefix, "/") { + opts.Prefix += "/" } - dir := http.Dir(directory) - return staticFileSystem{&dir} -} -func (fs staticFileSystem) Open(name string) (http.File, error) { - return fs.dir.Open(name) -} + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if !strings.HasPrefix(req.URL.Path, opts.Prefix) { + next.ServeHTTP(resp, req) + return + } + if req.Method != "GET" && req.Method != "HEAD" { + resp.WriteHeader(http.StatusNotFound) + return + } -// StaticHandler sets up a new middleware for serving static files in the -func StaticHandler(dir string, opts *Options) func(next http.Handler) http.Handler { - return opts.staticHandler(dir) -} + file := req.URL.Path + file = file[len(opts.Prefix):] + if len(file) == 0 { + resp.WriteHeader(http.StatusNotFound) + return + } + if strings.Contains(file, "\\") { + resp.WriteHeader(http.StatusBadRequest) + return + } + file = "/" + file + + var written bool + if opts.CorsHandler != nil { + written = true + opts.CorsHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + written = false + })).ServeHTTP(resp, req) + } + if written { + return + } -func (opts *Options) staticHandler(dir string) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - // Defaults - if len(opts.IndexFile) == 0 { - opts.IndexFile = "index.html" - } - // Normalize the prefix if provided - if opts.Prefix != "" { - // Ensure we have a leading '/' - if opts.Prefix[0] != '/' { - opts.Prefix = "/" + opts.Prefix + // custom files + if opts.handle(resp, req, http.Dir(custPath), file) { + return } - // Remove any trailing '/' - opts.Prefix = strings.TrimRight(opts.Prefix, "/") - } - if opts.FileSystem == nil { - opts.FileSystem = newStaticFileSystem(dir) - } - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if !opts.handle(w, req, opts) { - next.ServeHTTP(w, req) + // internal files + if opts.handle(resp, req, fileSystem(opts.Directory), file) { + return } + + resp.WriteHeader(http.StatusNotFound) }) } } @@ -98,76 +95,36 @@ func parseAcceptEncoding(val string) map[string]bool { return types } -func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Options) bool { - if req.Method != "GET" && req.Method != "HEAD" { - return false - } - - file := req.URL.Path - // if we have a prefix, filter requests by stripping the prefix - if opt.Prefix != "" { - if !strings.HasPrefix(file, opt.Prefix) { - return false - } - file = file[len(opt.Prefix):] - if file != "" && file[0] != '/' { - return false - } - } - - f, err := opt.FileSystem.Open(file) +func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool { + // use clean to keep the file is a valid path with no . or .. + f, err := fs.Open(path.Clean(file)) if err != nil { - // 404 requests to any known entries in `public` - if path.Base(opts.Directory) == "public" { - parts := strings.Split(file, "/") - if len(parts) < 2 { - return false - } - for _, entry := range KnownPublicEntries { - if entry == parts[1] { - w.WriteHeader(404) - return true - } - } + if os.IsNotExist(err) { + return false } - return false + w.WriteHeader(http.StatusInternalServerError) + log.Error("[Static] Open %q failed: %v", file, err) + return true } defer f.Close() fi, err := f.Stat() if err != nil { - log.Printf("[Static] %q exists, but fails to open: %v", file, err) + w.WriteHeader(http.StatusInternalServerError) + log.Error("[Static] %q exists, but fails to open: %v", file, err) return true } // Try to serve index file if fi.IsDir() { - // Redirect if missing trailing slash. - if !strings.HasSuffix(req.URL.Path, "/") { - http.Redirect(w, req, path.Clean(req.URL.Path+"/"), http.StatusFound) - return true - } - - f, err = opt.FileSystem.Open(file) - if err != nil { - return false // Discard error. - } - defer f.Close() - - fi, err = f.Stat() - if err != nil || fi.IsDir() { - return false - } - } - - if !opt.SkipLogging { - log.Println("[Static] Serving " + file) + w.WriteHeader(http.StatusNotFound) + return true } if httpcache.HandleFileETagCache(req, w, fi) { return true } - ServeContent(w, req, fi, fi.ModTime(), f) + serveContent(w, req, fi, fi.ModTime(), f) return true } diff --git a/modules/public/static.go b/modules/public/static.go index 36cfdbe44f..827dc2a1e0 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -20,12 +20,8 @@ import ( "code.gitea.io/gitea/modules/log" ) -// Static implements the static handler for serving assets. -func Static(opts *Options) func(next http.Handler) http.Handler { - opts.FileSystem = Assets - // we don't need to pass the directory, because the directory var is only - // used when in the options there is no FileSystem. - return opts.staticHandler("") +func fileSystem(dir string) http.FileSystem { + return Assets } func Asset(name string) ([]byte, error) { @@ -59,8 +55,8 @@ func AssetIsDir(name string) (bool, error) { } } -// ServeContent serve http content -func ServeContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { +// serveContent serve http content +func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) if encodings["gzip"] { if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok { @@ -76,7 +72,7 @@ func ServeContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt _, err := rd.Seek(0, io.SeekStart) // rewind to output whole file if err != nil { log.Error("rd.Seek error: %v", err) - http.Error(w, http.StatusText(500), 500) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } } |