From 92fda9c5a2cf6b57063050d1d0948ae885257d4a Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Fri, 9 Feb 2024 22:06:03 +0800
Subject: [PATCH] Disallow duplicate storage paths (#26484)

Replace #26380
---
 modules/setting/indexer.go    | 31 +++++++++++++++++--------------
 modules/setting/path.go       |  4 ++++
 modules/setting/repository.go |  3 +++
 modules/setting/server.go     |  7 ++++---
 modules/setting/session.go    |  6 +++---
 modules/setting/setting.go    |  9 +++++++++
 modules/setting/storage.go    |  2 ++
 7 files changed, 42 insertions(+), 20 deletions(-)

diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go
index 16f3d80168..15f6150242 100644
--- a/modules/setting/indexer.go
+++ b/modules/setting/indexer.go
@@ -53,21 +53,24 @@ var Indexer = struct {
 func loadIndexerFrom(rootCfg ConfigProvider) {
 	sec := rootCfg.Section("indexer")
 	Indexer.IssueType = sec.Key("ISSUE_INDEXER_TYPE").MustString("bleve")
-	Indexer.IssuePath = filepath.ToSlash(sec.Key("ISSUE_INDEXER_PATH").MustString(filepath.ToSlash(filepath.Join(AppDataPath, "indexers/issues.bleve"))))
-	if !filepath.IsAbs(Indexer.IssuePath) {
-		Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath))
-	}
-	Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr)
-
-	if Indexer.IssueType == "meilisearch" {
-		u, err := url.Parse(Indexer.IssueConnStr)
-		if err != nil {
-			log.Warn("Failed to parse ISSUE_INDEXER_CONN_STR: %v", err)
-			u = &url.URL{}
+	if Indexer.IssueType == "bleve" {
+		Indexer.IssuePath = filepath.ToSlash(sec.Key("ISSUE_INDEXER_PATH").MustString(filepath.ToSlash(filepath.Join(AppDataPath, "indexers/issues.bleve"))))
+		if !filepath.IsAbs(Indexer.IssuePath) {
+			Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath))
+		}
+		fatalDuplicatedPath("issue_indexer", Indexer.IssuePath)
+	} else {
+		Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr)
+		if Indexer.IssueType == "meilisearch" {
+			u, err := url.Parse(Indexer.IssueConnStr)
+			if err != nil {
+				log.Warn("Failed to parse ISSUE_INDEXER_CONN_STR: %v", err)
+				u = &url.URL{}
+			}
+			Indexer.IssueConnAuth, _ = u.User.Password()
+			u.User = nil
+			Indexer.IssueConnStr = u.String()
 		}
-		Indexer.IssueConnAuth, _ = u.User.Password()
-		u.User = nil
-		Indexer.IssueConnStr = u.String()
 	}
 
 	Indexer.IssueIndexerName = sec.Key("ISSUE_INDEXER_NAME").MustString(Indexer.IssueIndexerName)
diff --git a/modules/setting/path.go b/modules/setting/path.go
index 0fdc305aa1..b2cca0acbf 100644
--- a/modules/setting/path.go
+++ b/modules/setting/path.go
@@ -66,8 +66,12 @@ func init() {
 		AppWorkPath = filepath.Dir(AppPath)
 	}
 
+	fatalDuplicatedPath("app_work_path", AppWorkPath)
+
 	appWorkPathBuiltin = AppWorkPath
 	customPathBuiltin = CustomPath
+
+	fatalDuplicatedPath("custom_path", CustomPath)
 	customConfBuiltin = CustomConf
 }
 
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index a6f0ed8833..7990021aaa 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -285,6 +285,9 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
 	} else {
 		RepoRootPath = filepath.Clean(RepoRootPath)
 	}
+
+	fatalDuplicatedPath("repository.ROOT", RepoRootPath)
+
 	defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder))
 	for _, charset := range Repository.DetectedCharsetsOrder {
 		defaultDetectedCharsetsOrder = append(defaultDetectedCharsetsOrder, strings.ToLower(strings.TrimSpace(charset)))
diff --git a/modules/setting/server.go b/modules/setting/server.go
index c09b91612a..0dea4e1ac7 100644
--- a/modules/setting/server.go
+++ b/modules/setting/server.go
@@ -7,7 +7,6 @@ import (
 	"encoding/base64"
 	"net"
 	"net/url"
-	"path"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -321,17 +320,19 @@ func loadServerFrom(rootCfg ConfigProvider) {
 	}
 	StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath)
 	StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour)
-	AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data"))
+	AppDataPath = sec.Key("APP_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data"))
 	if !filepath.IsAbs(AppDataPath) {
 		AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
 	}
+	fatalDuplicatedPath("app_data_path", AppDataPath)
 
 	EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
 	EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
-	PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(path.Join(AppWorkPath, "data/tmp/pprof"))
+	PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data/tmp/pprof"))
 	if !filepath.IsAbs(PprofDataPath) {
 		PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
 	}
+	fatalDuplicatedPath("pprof_data_path", PprofDataPath)
 
 	landingPage := sec.Key("LANDING_PAGE").MustString("home")
 	switch landingPage {
diff --git a/modules/setting/session.go b/modules/setting/session.go
index 664c66f869..8b9b754b38 100644
--- a/modules/setting/session.go
+++ b/modules/setting/session.go
@@ -5,7 +5,6 @@ package setting
 
 import (
 	"net/http"
-	"path"
 	"path/filepath"
 	"strings"
 
@@ -44,9 +43,10 @@ func loadSessionFrom(rootCfg ConfigProvider) {
 	sec := rootCfg.Section("session")
 	SessionConfig.Provider = sec.Key("PROVIDER").In("memory",
 		[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "db"})
-	SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
+	SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ")
 	if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
-		SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
+		SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig)
+		fatalDuplicatedPath("session", SessionConfig.ProviderConfig)
 	}
 	SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
 	SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 72aee2a092..6e7ce7e67f 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -226,3 +226,12 @@ func LoadSettingsForInstall() {
 	loadServiceFrom(CfgProvider)
 	loadMailerFrom(CfgProvider)
 }
+
+var uniquePaths = make(map[string]string)
+
+func fatalDuplicatedPath(name, p string) {
+	if targetName, ok := uniquePaths[p]; ok && targetName != name {
+		log.Fatal("storage path %q is being used by %q and %q and all storage paths must be unique to prevent data loss.", p, targetName, name)
+	}
+	uniquePaths[p] = name
+}
diff --git a/modules/setting/storage.go b/modules/setting/storage.go
index f937c7cff3..23b08df101 100644
--- a/modules/setting/storage.go
+++ b/modules/setting/storage.go
@@ -240,6 +240,8 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType,
 		}
 	}
 
+	fatalDuplicatedPath("storage."+name, storage.Path)
+
 	return &storage, nil
 }