Skip to content

Commit 52fb936

Browse files
wxiaoguangsilverwindGiteaBot
authored
Serve pre-defined files in "public", add "security.txt", add CORS header for ".well-known" (#25974)
Replace #25892 Close #21942 Close #25464 Major changes: 1. Serve "robots.txt" and ".well-known/security.txt" in the "public" custom path * All files in "public/.well-known" can be served, just like "public/assets" 3. Add a test for ".well-known/security.txt" 4. Simplify the "FileHandlerFunc" logic, now the paths are consistent so the code can be simpler 5. Add CORS header for ".well-known" endpoints 6. Add logs to tell users they should move some of their legacy custom public files ``` 2023/07/19 13:00:37 cmd/web.go:178:serveInstalled() [E] Found legacy public asset "img" in CustomPath. Please move it to /work/gitea/custom/public/assets/img 2023/07/19 13:00:37 cmd/web.go:182:serveInstalled() [E] Found legacy public asset "robots.txt" in CustomPath. Please move it to /work/gitea/custom/public/robots.txt ``` This PR is not breaking. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Giteabot <teabot@gitea.io>
1 parent 2f0e79e commit 52fb936

File tree

9 files changed

+50
-40
lines changed

9 files changed

+50
-40
lines changed

cmd/web.go

+16
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import (
1515

1616
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
1717

18+
"code.gitea.io/gitea/modules/container"
1819
"code.gitea.io/gitea/modules/graceful"
1920
"code.gitea.io/gitea/modules/log"
2021
"code.gitea.io/gitea/modules/process"
22+
"code.gitea.io/gitea/modules/public"
2123
"code.gitea.io/gitea/modules/setting"
2224
"code.gitea.io/gitea/routers"
2325
"code.gitea.io/gitea/routers/install"
@@ -175,6 +177,20 @@ func serveInstalled(ctx *cli.Context) error {
175177
}
176178
}
177179

180+
// in old versions, user's custom web files are placed in "custom/public", and they were served as "http://domain.com/assets/xxx"
181+
// 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"
182+
publicFiles, _ := public.AssetFS().ListFiles(".")
183+
publicFilesSet := container.SetOf(publicFiles...)
184+
publicFilesSet.Remove(".well-known")
185+
publicFilesSet.Remove("assets")
186+
publicFilesSet.Remove("robots.txt")
187+
for _, fn := range publicFilesSet.Values() {
188+
log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn)
189+
}
190+
if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil {
191+
log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath)
192+
}
193+
178194
routers.InitWebInstalled(graceful.GetManager().HammerContext())
179195

180196
// We check that AppDataPath exists here (it should have been created during installation)

docs/content/doc/administration/customizing-gitea.en-us.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ is set under the "Configuration" tab on the site administration page.
5656

5757
To make Gitea serve custom public files (like pages and images), use the folder
5858
`$GITEA_CUSTOM/public/` as the webroot. Symbolic links will be followed.
59-
At the moment, only files in the `public/assets/` folder are served.
59+
At the moment, only the following files are served:
60+
61+
- `public/robots.txt`
62+
- files in the `public/.well-known/` folder
63+
- files in the `public/assets/` folder
6064

6165
For example, a file `image.png` stored in `$GITEA_CUSTOM/public/assets/`, can be accessed with
6266
the url `http://gitea.domain.tld/assets/image.png`.

modules/public/public.go

+11-23
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,15 @@ func AssetFS() *assetfs.LayeredFS {
2828
return assetfs.Layered(CustomAssets(), BuiltinAssets())
2929
}
3030

31-
// AssetsHandlerFunc implements the static handler for serving custom or original assets.
32-
func AssetsHandlerFunc(prefix string) http.HandlerFunc {
31+
// FileHandlerFunc implements the static handler for serving files in "public" assets
32+
func FileHandlerFunc() http.HandlerFunc {
3333
assetFS := AssetFS()
34-
prefix = strings.TrimSuffix(prefix, "/") + "/"
3534
return func(resp http.ResponseWriter, req *http.Request) {
36-
subPath := req.URL.Path
37-
if !strings.HasPrefix(subPath, prefix) {
38-
return
39-
}
40-
subPath = strings.TrimPrefix(subPath, prefix)
41-
4235
if req.Method != "GET" && req.Method != "HEAD" {
4336
resp.WriteHeader(http.StatusNotFound)
4437
return
4538
}
46-
47-
if handleRequest(resp, req, assetFS, subPath) {
48-
return
49-
}
50-
51-
resp.WriteHeader(http.StatusNotFound)
39+
handleRequest(resp, req, assetFS, req.URL.Path)
5240
}
5341
}
5442

@@ -71,34 +59,34 @@ func setWellKnownContentType(w http.ResponseWriter, file string) {
7159
}
7260
}
7361

74-
func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
62+
func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) {
7563
// 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
76-
f, err := fs.Open(util.PathJoinRelX("assets", file))
64+
f, err := fs.Open(util.PathJoinRelX(file))
7765
if err != nil {
7866
if os.IsNotExist(err) {
79-
return false
67+
w.WriteHeader(http.StatusNotFound)
68+
return
8069
}
8170
w.WriteHeader(http.StatusInternalServerError)
8271
log.Error("[Static] Open %q failed: %v", file, err)
83-
return true
72+
return
8473
}
8574
defer f.Close()
8675

8776
fi, err := f.Stat()
8877
if err != nil {
8978
w.WriteHeader(http.StatusInternalServerError)
9079
log.Error("[Static] %q exists, but fails to open: %v", file, err)
91-
return true
80+
return
9281
}
9382

94-
// Try to serve index file
83+
// need to serve index file? (no at the moment)
9584
if fi.IsDir() {
9685
w.WriteHeader(http.StatusNotFound)
97-
return true
86+
return
9887
}
9988

10089
serveContent(w, req, fi, fi.ModTime(), f)
101-
return true
10290
}
10391

10492
type GzipBytesProvider interface {

modules/setting/server.go

-5
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,4 @@ func loadServerFrom(rootCfg ConfigProvider) {
349349
default:
350350
LandingPageURL = LandingPage(landingPage)
351351
}
352-
353-
HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt"))
354-
if err != nil {
355-
log.Error("Unable to check if %s is a file. Error: %v", path.Join(CustomPath, "robots.txt"), err)
356-
}
357352
}

public/.well-known/security.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# This site is running a Gitea instance.
2+
# Gitea related security problems could be reported to Gitea community.
3+
# Site related security problems should be reported to this site's admin.
4+
Contact: https://github.com/go-gitea/gitea/blob/main/SECURITY.md
5+
Policy: https://github.com/go-gitea/gitea/blob/main/SECURITY.md
6+
Preferred-Languages: en

routers/install/routes.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
func Routes() *web.Route {
2121
base := web.NewRoute()
2222
base.Use(common.ProtocolMiddlewares()...)
23-
base.Methods("GET, HEAD", "/assets/*", public.AssetsHandlerFunc("/assets/"))
23+
base.Methods("GET, HEAD", "/assets/*", public.FileHandlerFunc())
2424

2525
r := web.NewRoute()
2626
r.Use(common.Sessioner(), Contexter())

routers/web/misc/misc.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
3434
}
3535

3636
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
37-
filePath := util.FilePathJoinAbs(setting.CustomPath, "robots.txt")
37+
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
38+
if ok, _ := util.IsExist(robotsTxt); !ok {
39+
robotsTxt = util.FilePathJoinAbs(setting.CustomPath, "robots.txt") // the legacy "robots.txt"
40+
}
3841
httpcache.SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
39-
http.ServeFile(w, req, filePath)
42+
http.ServeFile(w, req, robotsTxt)
4043
}
4144

4245
func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request) {

routers/web/web.go

+5-8
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func Routes() *web.Route {
108108
routes := web.NewRoute()
109109

110110
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
111-
routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.AssetsHandlerFunc("/assets/"))
111+
routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.FileHandlerFunc())
112112
routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
113113
routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
114114
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
@@ -132,15 +132,12 @@ func Routes() *web.Route {
132132
routes.Methods("GET,HEAD", "/captcha/*", append(mid, captcha.Captchaer(context.GetImageCaptcha()))...)
133133
}
134134

135-
if setting.HasRobotsTxt {
136-
routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...)
137-
}
138-
139135
if setting.Metrics.Enabled {
140136
prometheus.MustRegister(metrics.NewCollector())
141137
routes.Get("/metrics", append(mid, Metrics)...)
142138
}
143139

140+
routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...)
144141
routes.Get("/ssh_info", misc.SSHInfo)
145142
routes.Get("/api/healthz", healthcheck.Check)
146143

@@ -336,8 +333,7 @@ func registerRoutes(m *web.Route) {
336333

337334
// FIXME: not all routes need go through same middleware.
338335
// Especially some AJAX requests, we can reduce middleware number to improve performance.
339-
// Routers.
340-
// for health check
336+
341337
m.Get("/", Home)
342338
m.Get("/sitemap.xml", sitemapEnabled, ignExploreSignIn, HomeSitemap)
343339
m.Group("/.well-known", func() {
@@ -349,7 +345,8 @@ func registerRoutes(m *web.Route) {
349345
m.Get("/change-password", func(ctx *context.Context) {
350346
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
351347
})
352-
})
348+
m.Any("/*", CorsHandler(), public.FileHandlerFunc())
349+
}, CorsHandler())
353350

354351
m.Group("/explore", func() {
355352
m.Get("", func(ctx *context.Context) {

tests/integration/links_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func TestLinksNoLogin(t *testing.T) {
3838
"/user2/repo1/projects/1",
3939
"/assets/img/404.png",
4040
"/assets/img/500.png",
41+
"/.well-known/security.txt",
4142
}
4243

4344
for _, link := range links {

0 commit comments

Comments
 (0)