// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting import ( "os" "regexp" "strconv" "strings" "code.gitea.io/gitea/modules/log" ) const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_" var escapeRegex = regexp.MustCompile(escapeRegexpString) // 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 = section != "" && 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, prefixGitea, suffixFile string, 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(prefixGitea, suffixFile, 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 } 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 := section.Key(keyName) if key == nil { 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 }