189 lines · 5.6 KB · Go
RawDownload
// Copyright 2023 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package setting import (	"bytes"	"os"	"regexp"	"strconv"	"strings" 	"forgejo.org/modules/log") const (	EnvConfigKeyPrefixGitea = "^(FORGEJO|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 stringfunc 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__FILEfunc decodeEnvironmentKey(prefixRegexp *regexp.Regexp, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) { //nolint:unparam	if strings.HasSuffix(envKey, suffixFile) {		useFileValue = true		envKey = envKey[:len(envKey)-len(suffixFile)]	}	loc := prefixRegexp.FindStringIndex(envKey)	if loc == nil {		return false, "", "", false	}	ok, section, key = decodeEnvSectionKey(envKey[loc[1]:])	return ok, section, key, useFileValue} func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {	prefixRegexp := regexp.MustCompile(EnvConfigKeyPrefixGitea)	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(prefixRegexp, 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 giteafunc 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") 	_ = 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")}