From 58129f606aff9a8f3f509c17e4a7e9ca79e817b0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 19 Jul 2023 12:50:44 +0800 Subject: [PATCH 1/5] fix --- cmd/web.go | 10 ++++++ .../administration/customizing-gitea.en-us.md | 6 +++- modules/public/public.go | 34 ++++++------------- modules/setting/server.go | 5 --- public/.well-known/security.txt | 4 +++ routers/install/routes.go | 2 +- routers/web/misc/misc.go | 7 ++-- routers/web/web.go | 13 +++---- tests/integration/links_test.go | 1 + 9 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 public/.well-known/security.txt diff --git a/cmd/web.go b/cmd/web.go index 05f3b2ddb2eaa..e248eb1a7b6b1 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -172,6 +172,16 @@ func serveInstalled(ctx *cli.Context) error { } } + legacyPublicAssetFiles := []string{"img", "css", "js"} + for _, fn := range legacyPublicAssetFiles { + if _, err := os.Stat(filepath.Join(setting.CustomPath, "public", fn)); err == nil { + log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn) + } + } + if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil { + log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath) + } + routers.InitWebInstalled(graceful.GetManager().HammerContext()) // We check that AppDataPath exists here (it should have been created during installation) diff --git a/docs/content/doc/administration/customizing-gitea.en-us.md b/docs/content/doc/administration/customizing-gitea.en-us.md index fc4ded899bba7..eb2099d750857 100644 --- a/docs/content/doc/administration/customizing-gitea.en-us.md +++ b/docs/content/doc/administration/customizing-gitea.en-us.md @@ -56,7 +56,11 @@ is set under the "Configuration" tab on the site administration page. To make Gitea serve custom public files (like pages and images), use the folder `$GITEA_CUSTOM/public/` as the webroot. Symbolic links will be followed. -At the moment, only files in the `public/assets/` folder are served. +At the moment, only the following files are served: + +- `public/robots.txt` +- files in the `public/.well-known/` folder +- files in the `public/assets/` folder For example, a file `image.png` stored in `$GITEA_CUSTOM/public/assets/`, can be accessed with the url `http://gitea.domain.tld/assets/image.png`. diff --git a/modules/public/public.go b/modules/public/public.go index d5f0efb17ac34..5fbfe30a81c7e 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -28,27 +28,15 @@ func AssetFS() *assetfs.LayeredFS { return assetfs.Layered(CustomAssets(), BuiltinAssets()) } -// AssetsHandlerFunc implements the static handler for serving custom or original assets. -func AssetsHandlerFunc(prefix string) http.HandlerFunc { +// FileHandlerFunc implements the static handler for serving files in "public" assets +func FileHandlerFunc() http.HandlerFunc { assetFS := AssetFS() - prefix = strings.TrimSuffix(prefix, "/") + "/" return func(resp http.ResponseWriter, req *http.Request) { - subPath := req.URL.Path - if !strings.HasPrefix(subPath, prefix) { - return - } - subPath = strings.TrimPrefix(subPath, prefix) - if req.Method != "GET" && req.Method != "HEAD" { resp.WriteHeader(http.StatusNotFound) return } - - if handleRequest(resp, req, assetFS, subPath) { - return - } - - resp.WriteHeader(http.StatusNotFound) + handleRequest(resp, req, assetFS, req.URL.Path) } } @@ -71,16 +59,17 @@ func setWellKnownContentType(w http.ResponseWriter, file string) { } } -func handleRequest(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) { // 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("assets", file)) + f, err := fs.Open(util.PathJoinRelX(file)) if err != nil { if os.IsNotExist(err) { - return false + w.WriteHeader(http.StatusNotFound) + return } w.WriteHeader(http.StatusInternalServerError) log.Error("[Static] Open %q failed: %v", file, err) - return true + return } defer f.Close() @@ -88,17 +77,16 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, if err != nil { w.WriteHeader(http.StatusInternalServerError) log.Error("[Static] %q exists, but fails to open: %v", file, err) - return true + return } - // Try to serve index file + // need to serve index file? (no at the moment) if fi.IsDir() { w.WriteHeader(http.StatusNotFound) - return true + return } serveContent(w, req, fi, fi.ModTime(), f) - return true } type GzipBytesProvider interface { diff --git a/modules/setting/server.go b/modules/setting/server.go index 7c033bcc6ba79..08eb82fb3dece 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -349,9 +349,4 @@ func loadServerFrom(rootCfg ConfigProvider) { default: LandingPageURL = LandingPage(landingPage) } - - HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt")) - if err != nil { - log.Error("Unable to check if %s is a file. Error: %v", path.Join(CustomPath, "robots.txt"), err) - } } diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt new file mode 100644 index 0000000000000..944611d72b94f --- /dev/null +++ b/public/.well-known/security.txt @@ -0,0 +1,4 @@ +Contact: ... +Acknowledgments: ... +Preferred-Languages: en +Policy: ... diff --git a/routers/install/routes.go b/routers/install/routes.go index f09a22b1e6018..90e2d5fbc935b 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -20,7 +20,7 @@ import ( func Routes() *web.Route { base := web.NewRoute() base.Use(common.ProtocolMiddlewares()...) - base.RouteMethods("/assets/*", "GET, HEAD", public.AssetsHandlerFunc("/assets/")) + base.RouteMethods("/assets/*", "GET, HEAD", public.FileHandlerFunc()) r := web.NewRoute() r.Use(common.Sessioner(), Contexter()) diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go index 6ed3b5c3adb8d..54c93763f6a00 100644 --- a/routers/web/misc/misc.go +++ b/routers/web/misc/misc.go @@ -34,9 +34,12 @@ func DummyOK(w http.ResponseWriter, req *http.Request) { } func RobotsTxt(w http.ResponseWriter, req *http.Request) { - filePath := util.FilePathJoinAbs(setting.CustomPath, "robots.txt") + robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt") + if ok, _ := util.IsExist(robotsTxt); !ok { + robotsTxt = util.FilePathJoinAbs(setting.CustomPath, "robots.txt") // the legacy "robots.txt" + } httpcache.SetCacheControlInHeader(w.Header(), setting.StaticCacheTime) - http.ServeFile(w, req, filePath) + http.ServeFile(w, req, robotsTxt) } func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request) { diff --git a/routers/web/web.go b/routers/web/web.go index 4f5901c0ec661..101b8e3d4dea3 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -108,7 +108,7 @@ func Routes() *web.Route { routes := web.NewRoute() routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler - routes.RouteMethods("/assets/*", "GET, HEAD", CorsHandler(), public.AssetsHandlerFunc("/assets/")) + routes.RouteMethods("/assets/*", "GET, HEAD", CorsHandler(), public.FileHandlerFunc()) routes.RouteMethods("/avatars/*", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) routes.RouteMethods("/repo-avatars/*", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) routes.RouteMethods("/apple-touch-icon.png", "GET, HEAD", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) @@ -132,15 +132,12 @@ func Routes() *web.Route { routes.RouteMethods("/captcha/*", "GET,HEAD", append(mid, captcha.Captchaer(context.GetImageCaptcha()))...) } - if setting.HasRobotsTxt { - routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...) - } - if setting.Metrics.Enabled { prometheus.MustRegister(metrics.NewCollector()) routes.Get("/metrics", append(mid, Metrics)...) } + routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...) routes.Get("/ssh_info", misc.SSHInfo) routes.Get("/api/healthz", healthcheck.Check) @@ -336,8 +333,7 @@ func registerRoutes(m *web.Route) { // FIXME: not all routes need go through same middleware. // Especially some AJAX requests, we can reduce middleware number to improve performance. - // Routers. - // for health check + m.Get("/", Home) m.Get("/sitemap.xml", sitemapEnabled, ignExploreSignIn, HomeSitemap) m.Group("/.well-known", func() { @@ -349,7 +345,8 @@ func registerRoutes(m *web.Route) { m.Get("/change-password", func(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/account") }) - }) + m.Any("/*", CorsHandler(), public.FileHandlerFunc()) + }, CorsHandler()) m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go index 9136f8f9153ae..91655833af041 100644 --- a/tests/integration/links_test.go +++ b/tests/integration/links_test.go @@ -38,6 +38,7 @@ func TestLinksNoLogin(t *testing.T) { "/user2/repo1/projects/1", "/assets/img/404.png", "/assets/img/500.png", + "/.well-known/security.txt", } for _, link := range links { From 9aaa1fe74337af42859a3be6a82e89dce4ca6546 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 19 Jul 2023 17:38:30 +0800 Subject: [PATCH 2/5] Update cmd/web.go Co-authored-by: silverwind --- cmd/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/web.go b/cmd/web.go index e248eb1a7b6b1..0329e972caf80 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -172,7 +172,7 @@ func serveInstalled(ctx *cli.Context) error { } } - legacyPublicAssetFiles := []string{"img", "css", "js"} + legacyPublicAssetFiles := []string{"img", "css", "js", "fonts"} for _, fn := range legacyPublicAssetFiles { if _, err := os.Stat(filepath.Join(setting.CustomPath, "public", fn)); err == nil { log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn) From 2b605f2407a4c0f224410f49c8c0cd70b92f7fc6 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 19 Jul 2023 23:13:16 +0800 Subject: [PATCH 3/5] better legacy asset file detection --- cmd/web.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index 0329e972caf80..e2857521eb84c 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -15,9 +15,11 @@ import ( _ "net/http/pprof" // Used for debugging if enabled and a web server is running + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/install" @@ -172,11 +174,15 @@ func serveInstalled(ctx *cli.Context) error { } } - legacyPublicAssetFiles := []string{"img", "css", "js", "fonts"} - for _, fn := range legacyPublicAssetFiles { - if _, err := os.Stat(filepath.Join(setting.CustomPath, "public", fn)); err == nil { - log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn) - } + // in old versions, user's custom web files are placed in "custom/public", and they were served as "http://domain.com/assets/xxx" + // now, Gitea only serves pre-defined files in the "custom/public" folder basing on the web root, the user should move their custom files to "custom/public/assets" + publicFiles, _ := public.AssetFS().ListFiles(".") + publicFilesSet := container.SetOf(publicFiles...) + publicFilesSet.Remove(".well-known") + publicFilesSet.Remove("assets") + publicFilesSet.Remove("robots.txt") + for _, fn := range publicFilesSet.Values() { + log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn) } if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil { log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath) From bee6d14d041fca9d1b92c853a72ddc9b5a6699ac Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 21 Jul 2023 12:19:31 +0800 Subject: [PATCH 4/5] fix merge --- routers/install/routes.go | 2 +- routers/web/web.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/install/routes.go b/routers/install/routes.go index ce6d41b32db40..06c9d389a60f4 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -20,7 +20,7 @@ import ( func Routes() *web.Route { base := web.NewRoute() base.Use(common.ProtocolMiddlewares()...) - base.Methods("GET, HEAD", "/assets/*", public.AssetsHandlerFunc("/assets/")) + base.Methods("GET, HEAD", "/assets/*", public.FileHandlerFunc()) r := web.NewRoute() r.Use(common.Sessioner(), Contexter()) diff --git a/routers/web/web.go b/routers/web/web.go index d62822b3b5d85..11ff68dd0906a 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -108,7 +108,7 @@ func Routes() *web.Route { routes := web.NewRoute() routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler - routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.AssetsHandlerFunc("/assets/")) + routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.FileHandlerFunc()) routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) From 16ca037e5b6fc4f1828d872cbf64482925500262 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 21 Jul 2023 15:59:08 +0800 Subject: [PATCH 5/5] Update security.txt --- public/.well-known/security.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt index 944611d72b94f..2cae3cbea4d2f 100644 --- a/public/.well-known/security.txt +++ b/public/.well-known/security.txt @@ -1,4 +1,6 @@ -Contact: ... -Acknowledgments: ... +# This site is running a Gitea instance. +# Gitea related security problems could be reported to Gitea community. +# Site related security problems should be reported to this site's admin. +Contact: https://github.com/go-gitea/gitea/blob/main/SECURITY.md +Policy: https://github.com/go-gitea/gitea/blob/main/SECURITY.md Preferred-Languages: en -Policy: ...