mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
5.6 KiB
190 lines
5.6 KiB
// Copyright 2023 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package setting |
|
|
|
import ( |
|
"bytes" |
|
"os" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
|
|
"code.gitea.io/gitea/modules/log" |
|
) |
|
|
|
const ( |
|
EnvConfigKeyPrefixGitea = "GITEA__" |
|
EnvConfigKeySuffixFile = "__FILE" |
|
) |
|
|
|
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_" |
|
|
|
var escapeRegex = regexp.MustCompile(escapeRegexpString) |
|
|
|
func CollectEnvConfigKeys() (keys []string) { |
|
for _, env := range os.Environ() { |
|
if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) { |
|
k, _, _ := strings.Cut(env, "=") |
|
keys = append(keys, k) |
|
} |
|
} |
|
return keys |
|
} |
|
|
|
func ClearEnvConfigKeys() { |
|
for _, k := range CollectEnvConfigKeys() { |
|
_ = os.Unsetenv(k) |
|
} |
|
} |
|
|
|
// decodeEnvSectionKey will decode a portable string encoded Section__Key pair |
|
// Portable strings are considered to be of the form [A-Z0-9_]* |
|
// We will encode a disallowed value as the UTF8 byte string preceded by _0X and |
|
// followed by _. E.g. _0X2C_ for a '-' and _0X2E_ for '.' |
|
// Section and Key are separated by a plain '__'. |
|
// The entire section can be encoded as a UTF8 byte string |
|
func decodeEnvSectionKey(encoded string) (ok bool, section, key string) { |
|
inKey := false |
|
last := 0 |
|
escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1) |
|
for _, unescapeIdx := range escapeStringIndices { |
|
preceding := encoded[last:unescapeIdx[0]] |
|
if !inKey { |
|
if splitter := strings.Index(preceding, "__"); splitter > -1 { |
|
section += preceding[:splitter] |
|
inKey = true |
|
key += preceding[splitter+2:] |
|
} else { |
|
section += preceding |
|
} |
|
} else { |
|
key += preceding |
|
} |
|
toDecode := encoded[unescapeIdx[0]+3 : unescapeIdx[1]-1] |
|
decodedBytes := make([]byte, len(toDecode)/2) |
|
for i := 0; i < len(toDecode)/2; i++ { |
|
// Can ignore error here as we know these should be hexadecimal from the regexp |
|
byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 8) |
|
decodedBytes[i] = byte(byteInt) |
|
} |
|
if inKey { |
|
key += string(decodedBytes) |
|
} else { |
|
section += string(decodedBytes) |
|
} |
|
last = unescapeIdx[1] |
|
} |
|
remaining := encoded[last:] |
|
if !inKey { |
|
if splitter := strings.Index(remaining, "__"); splitter > -1 { |
|
section += remaining[:splitter] |
|
key += remaining[splitter+2:] |
|
} else { |
|
section += remaining |
|
} |
|
} else { |
|
key += remaining |
|
} |
|
section = strings.ToLower(section) |
|
ok = key != "" |
|
if !ok { |
|
section = "" |
|
key = "" |
|
} |
|
return ok, section, key |
|
} |
|
|
|
// decodeEnvironmentKey decode the environment key to section and key |
|
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE |
|
func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) { |
|
if !strings.HasPrefix(envKey, prefixGitea) { |
|
return false, "", "", false |
|
} |
|
if strings.HasSuffix(envKey, suffixFile) { |
|
useFileValue = true |
|
envKey = envKey[:len(envKey)-len(suffixFile)] |
|
} |
|
ok, section, key = decodeEnvSectionKey(envKey[len(prefixGitea):]) |
|
return ok, section, key, useFileValue |
|
} |
|
|
|
func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { |
|
for _, kv := range envs { |
|
idx := strings.IndexByte(kv, '=') |
|
if idx < 0 { |
|
continue |
|
} |
|
|
|
// parse the environment variable to config section name and key name |
|
envKey := kv[:idx] |
|
envValue := kv[idx+1:] |
|
ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey) |
|
if !ok { |
|
continue |
|
} |
|
|
|
// use environment value as config value, or read the file content as value if the key indicates a file |
|
keyValue := envValue |
|
if useFileValue { |
|
fileContent, err := os.ReadFile(envValue) |
|
if err != nil { |
|
log.Error("Error reading file for %s : %v", envKey, envValue, err) |
|
continue |
|
} |
|
if bytes.HasSuffix(fileContent, []byte("\r\n")) { |
|
fileContent = fileContent[:len(fileContent)-2] |
|
} else if bytes.HasSuffix(fileContent, []byte("\n")) { |
|
fileContent = fileContent[:len(fileContent)-1] |
|
} |
|
keyValue = string(fileContent) |
|
} |
|
|
|
// try to set the config value if necessary |
|
section, err := cfg.GetSection(sectionName) |
|
if err != nil { |
|
section, err = cfg.NewSection(sectionName) |
|
if err != nil { |
|
log.Error("Error creating section: %s : %v", sectionName, err) |
|
continue |
|
} |
|
} |
|
key := ConfigSectionKey(section, keyName) |
|
if key == nil { |
|
changed = true |
|
key, err = section.NewKey(keyName, keyValue) |
|
if err != nil { |
|
log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, keyValue, err) |
|
continue |
|
} |
|
} |
|
oldValue := key.Value() |
|
if !changed && oldValue != keyValue { |
|
changed = true |
|
} |
|
key.SetValue(keyValue) |
|
} |
|
return changed |
|
} |
|
|
|
// InitGiteaEnvVars initializes the environment variables for gitea |
|
func InitGiteaEnvVars() { |
|
// Ideally Gitea should only accept the environment variables which it clearly knows instead of unsetting the ones it doesn't want, |
|
// but the ideal behavior would be a breaking change, and it seems not bringing enough benefits to end users, |
|
// so at the moment we could still keep "unsetting the unnecessary environments" |
|
|
|
// HOME is managed by Gitea, Gitea's git should use "HOME/.gitconfig". |
|
// But git would try "XDG_CONFIG_HOME/git/config" first if "HOME/.gitconfig" does not exist, |
|
// then our git.InitFull would still write to "XDG_CONFIG_HOME/git/config" if XDG_CONFIG_HOME is set. |
|
_ = os.Unsetenv("XDG_CONFIG_HOME") |
|
} |
|
|
|
func InitGiteaEnvVarsForTesting() { |
|
InitGiteaEnvVars() |
|
_ = os.Unsetenv("GIT_AUTHOR_NAME") |
|
_ = os.Unsetenv("GIT_AUTHOR_EMAIL") |
|
_ = os.Unsetenv("GIT_AUTHOR_DATE") |
|
_ = os.Unsetenv("GIT_COMMITTER_NAME") |
|
_ = os.Unsetenv("GIT_COMMITTER_EMAIL") |
|
_ = os.Unsetenv("GIT_COMMITTER_DATE") |
|
}
|
|
|