diff options
Diffstat (limited to 'modules/public/public.go')
-rw-r--r-- | modules/public/public.go | 92 |
1 files changed, 49 insertions, 43 deletions
diff --git a/modules/public/public.go b/modules/public/public.go index 2c96cf9e76..0c0e6dc1cc 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -4,11 +4,15 @@ package public import ( + "bytes" + "io" "net/http" "os" - "path/filepath" + "path" "strings" + "time" + "code.gitea.io/gitea/modules/assetfs" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" @@ -16,55 +20,31 @@ import ( "code.gitea.io/gitea/modules/util" ) -// Options represents the available options to configure the handler. -type Options struct { - Directory string - Prefix string - CorsHandler func(http.Handler) http.Handler +func CustomAssets() *assetfs.Layer { + return assetfs.Local("custom", setting.CustomPath, "public") } -// AssetsURLPathPrefix is the path prefix for static asset files -const AssetsURLPathPrefix = "/assets/" +func AssetFS() *assetfs.LayeredFS { + return assetfs.Layered(CustomAssets(), BuiltinAssets()) +} // AssetsHandlerFunc implements the static handler for serving custom or original assets. -func AssetsHandlerFunc(opts *Options) http.HandlerFunc { - 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 += "/" - } - +func AssetsHandlerFunc(prefix string) http.HandlerFunc { + assetFS := AssetFS() + prefix = strings.TrimSuffix(prefix, "/") + "/" return func(resp http.ResponseWriter, req *http.Request) { - if req.Method != "GET" && req.Method != "HEAD" { - resp.WriteHeader(http.StatusNotFound) + subPath := req.URL.Path + if !strings.HasPrefix(subPath, prefix) { return } + subPath = strings.TrimPrefix(subPath, prefix) - if opts.CorsHandler != nil { - var corsSent bool - opts.CorsHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { - corsSent = true - })).ServeHTTP(resp, req) - // If CORS is not sent, the response must have been written by other handlers - if !corsSent { - return - } - } - - file := req.URL.Path[len(opts.Prefix):] - - // custom files - if opts.handle(resp, req, http.Dir(custPath), file) { + if req.Method != "GET" && req.Method != "HEAD" { + resp.WriteHeader(http.StatusNotFound) return } - // internal files - if opts.handle(resp, req, fileSystem(opts.Directory), file) { + if handleRequest(resp, req, assetFS, subPath) { return } @@ -85,13 +65,13 @@ func parseAcceptEncoding(val string) container.Set[string] { // setWellKnownContentType will set the Content-Type if the file is a well-known type. // See the comments of detectWellKnownMimeType func setWellKnownContentType(w http.ResponseWriter, file string) { - mimeType := detectWellKnownMimeType(filepath.Ext(file)) + mimeType := detectWellKnownMimeType(path.Ext(file)) if mimeType != "" { w.Header().Set("Content-Type", mimeType) } } -func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool { +func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool { // actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here f, err := fs.Open(util.PathJoinRelX(file)) if err != nil { @@ -121,8 +101,34 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.Fi return true } - setWellKnownContentType(w, file) - serveContent(w, req, fi, fi.ModTime(), f) return true } + +type GzipBytesProvider interface { + GzipBytes() []byte +} + +// serveContent serve http content +func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { + setWellKnownContentType(w, fi.Name()) + + encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) + if encodings.Contains("gzip") { + // try to provide gzip content directly from bindata (provided by vfsgen۰CompressedFileInfo) + if compressed, ok := fi.(GzipBytesProvider); ok { + rdGzip := bytes.NewReader(compressed.GzipBytes()) + // all gzipped static files (from bindata) are managed by Gitea, so we can make sure every file has the correct ext name + // then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/octet-stream") + } + w.Header().Set("Content-Encoding", "gzip") + http.ServeContent(w, req, fi.Name(), modtime, rdGzip) + return + } + } + + http.ServeContent(w, req, fi.Name(), modtime, content) + return +} |