_ "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"
}
}
+ // 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)
+ }
+
routers.InitWebInstalled(graceful.GetManager().HammerContext())
// We check that AppDataPath exists here (it should have been created during installation)
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`.
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)
}
}
}
}
-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()
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 {
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)
- }
}
--- /dev/null
+# 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
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())
}
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) {
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"))
routes.Methods("GET,HEAD", "/captcha/*", 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)
// 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() {
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) {
"/user2/repo1/projects/1",
"/assets/img/404.png",
"/assets/img/500.png",
+ "/.well-known/security.txt",
}
for _, link := range links {