优化结构
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
// Package staticfiles serves files from the configured local upload store.
|
||||
// It owns the Gin/storage adapter so the server root only has to compose it.
|
||||
package staticfiles
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Register adds local upload routes when local storage is enabled. A route is
|
||||
// skipped when its root conflicts with an existing GET/HEAD route.
|
||||
func Register(engine *gin.Engine, runtime *conf.Runtime) {
|
||||
config := localConfig(runtime)
|
||||
if config == nil || strings.Trim(config.PathPrefix, "/") == "" {
|
||||
return
|
||||
}
|
||||
prefix := "/" + strings.Trim(config.PathPrefix, "/")
|
||||
if localStorageRouteConflicts(engine.Routes(), prefix) {
|
||||
return
|
||||
}
|
||||
handler := func(c *gin.Context) {
|
||||
if !serveAt(c, runtime, prefix) {
|
||||
c.Status(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
engine.GET(prefix+"/*filepath", handler)
|
||||
engine.HEAD(prefix+"/*filepath", handler)
|
||||
}
|
||||
|
||||
// Serve resolves the current runtime configuration on every request so a
|
||||
// configuration reload takes effect without rebuilding the Gin engine.
|
||||
func Serve(c *gin.Context, runtime *conf.Runtime) bool {
|
||||
config := localConfig(runtime)
|
||||
if config == nil {
|
||||
return false
|
||||
}
|
||||
prefix := "/" + strings.Trim(config.PathPrefix, "/")
|
||||
return serveAt(c, runtime, prefix)
|
||||
}
|
||||
|
||||
func localConfig(runtime *conf.Runtime) *conf.AdminBackend_Local {
|
||||
if runtime == nil {
|
||||
return nil
|
||||
}
|
||||
config := runtime.Admin()
|
||||
if config == nil || config.Local == nil || config.Local.StorePath == "" {
|
||||
return nil
|
||||
}
|
||||
if config.Storage != nil && config.Storage.Type != "" && config.Storage.Type != "local" {
|
||||
return nil
|
||||
}
|
||||
return config.Local
|
||||
}
|
||||
|
||||
func localStorageRouteConflicts(routes []gin.RouteInfo, prefix string) bool {
|
||||
staticRoot := strings.Split(strings.TrimPrefix(prefix, "/"), "/")[0]
|
||||
for _, route := range routes {
|
||||
if route.Method != http.MethodGet && route.Method != http.MethodHead {
|
||||
continue
|
||||
}
|
||||
routeRoot := strings.Split(strings.TrimPrefix(route.Path, "/"), "/")[0]
|
||||
if routeRoot == staticRoot {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func serveAt(c *gin.Context, runtime *conf.Runtime, prefix string) bool {
|
||||
config := localConfig(runtime)
|
||||
if config == nil || "/"+strings.Trim(config.PathPrefix, "/") != prefix {
|
||||
return false
|
||||
}
|
||||
if prefix == "/" || (c.Request.URL.Path != prefix && !strings.HasPrefix(c.Request.URL.Path, prefix+"/")) {
|
||||
return false
|
||||
}
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
filename := path.Base(c.Request.URL.Path)
|
||||
if !canServeInline(filename) {
|
||||
c.Header("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
|
||||
}
|
||||
http.StripPrefix(prefix, http.FileServer(filesOnly{FileSystem: http.Dir(config.StorePath)})).ServeHTTP(c.Writer, c.Request)
|
||||
return true
|
||||
}
|
||||
|
||||
func canServeInline(filename string) bool {
|
||||
switch strings.ToLower(path.Ext(filename)) {
|
||||
case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".avif",
|
||||
".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac",
|
||||
".mp4", ".webm", ".mov", ".avi", ".mkv":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type filesOnly struct{ http.FileSystem }
|
||||
|
||||
func (f filesOnly) Open(name string) (http.File, error) {
|
||||
file, err := f.FileSystem.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, statErr := file.Stat()
|
||||
if statErr != nil {
|
||||
_ = file.Close()
|
||||
return nil, statErr
|
||||
}
|
||||
if info.IsDir() {
|
||||
_ = file.Close()
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
Reference in New Issue
Block a user