| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // 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, 0)
- 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")
- }
|