code.gitea.io/gitea@v1.22.3/modules/setting/config_env.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"bytes"
     8  	"os"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/modules/log"
    14  )
    15  
    16  const (
    17  	EnvConfigKeyPrefixGitea = "GITEA__"
    18  	EnvConfigKeySuffixFile  = "__FILE"
    19  )
    20  
    21  const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
    22  
    23  var escapeRegex = regexp.MustCompile(escapeRegexpString)
    24  
    25  func CollectEnvConfigKeys() (keys []string) {
    26  	for _, env := range os.Environ() {
    27  		if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) {
    28  			k, _, _ := strings.Cut(env, "=")
    29  			keys = append(keys, k)
    30  		}
    31  	}
    32  	return keys
    33  }
    34  
    35  func ClearEnvConfigKeys() {
    36  	for _, k := range CollectEnvConfigKeys() {
    37  		_ = os.Unsetenv(k)
    38  	}
    39  }
    40  
    41  // decodeEnvSectionKey will decode a portable string encoded Section__Key pair
    42  // Portable strings are considered to be of the form [A-Z0-9_]*
    43  // We will encode a disallowed value as the UTF8 byte string preceded by _0X and
    44  // followed by _. E.g. _0X2C_ for a '-' and _0X2E_ for '.'
    45  // Section and Key are separated by a plain '__'.
    46  // The entire section can be encoded as a UTF8 byte string
    47  func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
    48  	inKey := false
    49  	last := 0
    50  	escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1)
    51  	for _, unescapeIdx := range escapeStringIndices {
    52  		preceding := encoded[last:unescapeIdx[0]]
    53  		if !inKey {
    54  			if splitter := strings.Index(preceding, "__"); splitter > -1 {
    55  				section += preceding[:splitter]
    56  				inKey = true
    57  				key += preceding[splitter+2:]
    58  			} else {
    59  				section += preceding
    60  			}
    61  		} else {
    62  			key += preceding
    63  		}
    64  		toDecode := encoded[unescapeIdx[0]+3 : unescapeIdx[1]-1]
    65  		decodedBytes := make([]byte, len(toDecode)/2)
    66  		for i := 0; i < len(toDecode)/2; i++ {
    67  			// Can ignore error here as we know these should be hexadecimal from the regexp
    68  			byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 0)
    69  			decodedBytes[i] = byte(byteInt)
    70  		}
    71  		if inKey {
    72  			key += string(decodedBytes)
    73  		} else {
    74  			section += string(decodedBytes)
    75  		}
    76  		last = unescapeIdx[1]
    77  	}
    78  	remaining := encoded[last:]
    79  	if !inKey {
    80  		if splitter := strings.Index(remaining, "__"); splitter > -1 {
    81  			section += remaining[:splitter]
    82  			key += remaining[splitter+2:]
    83  		} else {
    84  			section += remaining
    85  		}
    86  	} else {
    87  		key += remaining
    88  	}
    89  	section = strings.ToLower(section)
    90  	ok = key != ""
    91  	if !ok {
    92  		section = ""
    93  		key = ""
    94  	}
    95  	return ok, section, key
    96  }
    97  
    98  // decodeEnvironmentKey decode the environment key to section and key
    99  // The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
   100  func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) {
   101  	if !strings.HasPrefix(envKey, prefixGitea) {
   102  		return false, "", "", false
   103  	}
   104  	if strings.HasSuffix(envKey, suffixFile) {
   105  		useFileValue = true
   106  		envKey = envKey[:len(envKey)-len(suffixFile)]
   107  	}
   108  	ok, section, key = decodeEnvSectionKey(envKey[len(prefixGitea):])
   109  	return ok, section, key, useFileValue
   110  }
   111  
   112  func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
   113  	for _, kv := range envs {
   114  		idx := strings.IndexByte(kv, '=')
   115  		if idx < 0 {
   116  			continue
   117  		}
   118  
   119  		// parse the environment variable to config section name and key name
   120  		envKey := kv[:idx]
   121  		envValue := kv[idx+1:]
   122  		ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey)
   123  		if !ok {
   124  			continue
   125  		}
   126  
   127  		// use environment value as config value, or read the file content as value if the key indicates a file
   128  		keyValue := envValue
   129  		if useFileValue {
   130  			fileContent, err := os.ReadFile(envValue)
   131  			if err != nil {
   132  				log.Error("Error reading file for %s : %v", envKey, envValue, err)
   133  				continue
   134  			}
   135  			if bytes.HasSuffix(fileContent, []byte("\r\n")) {
   136  				fileContent = fileContent[:len(fileContent)-2]
   137  			} else if bytes.HasSuffix(fileContent, []byte("\n")) {
   138  				fileContent = fileContent[:len(fileContent)-1]
   139  			}
   140  			keyValue = string(fileContent)
   141  		}
   142  
   143  		// try to set the config value if necessary
   144  		section, err := cfg.GetSection(sectionName)
   145  		if err != nil {
   146  			section, err = cfg.NewSection(sectionName)
   147  			if err != nil {
   148  				log.Error("Error creating section: %s : %v", sectionName, err)
   149  				continue
   150  			}
   151  		}
   152  		key := ConfigSectionKey(section, keyName)
   153  		if key == nil {
   154  			changed = true
   155  			key, err = section.NewKey(keyName, keyValue)
   156  			if err != nil {
   157  				log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, keyValue, err)
   158  				continue
   159  			}
   160  		}
   161  		oldValue := key.Value()
   162  		if !changed && oldValue != keyValue {
   163  			changed = true
   164  		}
   165  		key.SetValue(keyValue)
   166  	}
   167  	return changed
   168  }