@ -8,36 +8,71 @@ import (
"os"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
ini "gopkg.in/ini.v1"
"gopkg.in/ini.v1" //nolint:depguard
)
type ConfigKey interface {
Name ( ) string
Value ( ) string
SetValue ( v string )
In ( defaultVal string , candidates [ ] string ) string
String ( ) string
Strings ( delim string ) [ ] string
MustString ( defaultVal string ) string
MustBool ( defaultVal ... bool ) bool
MustInt ( defaultVal ... int ) int
MustInt64 ( defaultVal ... int64 ) int64
MustDuration ( defaultVal ... time . Duration ) time . Duration
}
type ConfigSection interface {
Name ( ) string
MapTo ( interface { } ) error
MapTo ( any ) error
HasKey ( key string ) bool
NewKey ( name , value string ) ( * ini . Key , error )
Key ( key string ) * ini . Key
Keys ( ) [ ] * ini . Key
ChildSections ( ) [ ] * ini . Section
NewKey ( name , value string ) ( Config Key, error )
Key ( key string ) Config Key
Keys ( ) [ ] Config Key
ChildSections ( ) [ ] Config Section
}
// ConfigProvider represents a config provider
type ConfigProvider interface {
Section ( section string ) ConfigSection
Sections ( ) [ ] ConfigSection
NewSection ( name string ) ( ConfigSection , error )
GetSection ( name string ) ( ConfigSection , error )
Save ( ) error
SaveTo ( filename string ) error
}
type iniConfigProvider struct {
opts * Options
ini * ini . File
newFile bool // whether the file has not existed previously
}
type iniConfigSection struct {
sec * ini . Section
}
var (
_ ConfigProvider = ( * iniConfigProvider ) ( nil )
_ ConfigSection = ( * iniConfigSection ) ( nil )
_ ConfigKey = ( * ini . Key ) ( nil )
)
// ConfigSectionKey only searches the keys in the given section, but it is O(n).
// ini package has a special behavior: with "[sec] a=1" and an empty "[sec.sub]",
// then in "[sec.sub]", Key()/HasKey() can always see "a=1" because it always tries parent sections.
// It returns nil if the key doesn't exist.
func ConfigSectionKey ( sec ConfigSection , key string ) * ini . Key {
func ConfigSectionKey ( sec ConfigSection , key string ) Config Key {
if sec == nil {
return nil
}
@ -64,7 +99,7 @@ func ConfigSectionKeyString(sec ConfigSection, key string, def ...string) string
// and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values.
// Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys.
// It never returns nil.
func ConfigInheritedKey ( sec ConfigSection , key string ) * ini . Key {
func ConfigInheritedKey ( sec ConfigSection , key string ) Config Key {
k := sec . Key ( key )
if k != nil && k . String ( ) != "" {
newKey , _ := sec . NewKey ( k . Name ( ) , k . String ( ) )
@ -85,41 +120,64 @@ func ConfigInheritedKeyString(sec ConfigSection, key string, def ...string) stri
return ""
}
type iniFileConfigProvider struct {
opts * Options
* ini . File
newFile bool // whether the file has not existed previously
func ( s * iniConfigSection ) Name ( ) string {
return s . sec . Name ( )
}
func ( s * iniConfigSection ) MapTo ( v any ) error {
return s . sec . MapTo ( v )
}
func ( s * iniConfigSection ) HasKey ( key string ) bool {
return s . sec . HasKey ( key )
}
func ( s * iniConfigSection ) NewKey ( name , value string ) ( ConfigKey , error ) {
return s . sec . NewKey ( name , value )
}
func ( s * iniConfigSection ) Key ( key string ) ConfigKey {
return s . sec . Key ( key )
}
func ( s * iniConfigSection ) Keys ( ) ( keys [ ] ConfigKey ) {
for _ , k := range s . sec . Keys ( ) {
keys = append ( keys , k )
}
return keys
}
// NewConfigProviderFromData this function is only for testing
func ( s * iniConfigSection ) ChildSections ( ) ( sections [ ] ConfigSection ) {
for _ , s := range s . sec . ChildSections ( ) {
sections = append ( sections , & iniConfigSection { s } )
}
return sections
}
// NewConfigProviderFromData this function is mainly for testing purpose
func NewConfigProviderFromData ( configContent string ) ( ConfigProvider , error ) {
var cfg * ini . File
var err error
if configContent == "" {
cfg = ini . Empty ( )
} else {
cfg , err = ini . Load ( strings . NewReader ( configContent ) )
if err != nil {
return nil , err
}
cfg , err := ini . Load ( strings . NewReader ( configContent ) )
if err != nil {
return nil , err
}
cfg . NameMapper = ini . SnackCase
return & iniFileConfigProvider {
File : cfg ,
return & iniConfigProvider {
ini : cfg ,
newFile : true ,
} , nil
}
type Options struct {
CustomConf string // the ini file path
AllowEmpty bool // whether not finding configuration files is allowed (only true for the tests)
ExtraConfig string
DisableLoadCommonSettings bool
CustomConf string // the ini file path
AllowEmpty bool // whether not finding configuration files is allowed
ExtraConfig string
DisableLoadCommonSettings bool // only used by "Init()", not used by "NewConfigProvider()"
}
// n ewConfigProviderFromFile load configuration from file.
// N ewConfigProviderFromFile load configuration from file.
// NOTE: do not print any log except error.
func n ewConfigProviderFromFile( opts * Options ) ( * iniFile ConfigProvider, error ) {
func N ewConfigProviderFromFile( opts * Options ) ( ConfigProvider , error ) {
cfg := ini . Empty ( )
newFile := true
@ -147,61 +205,77 @@ func newConfigProviderFromFile(opts *Options) (*iniFileConfigProvider, error) {
}
cfg . NameMapper = ini . SnackCase
return & iniFile ConfigProvider {
return & iniConfigProvider {
opts : opts ,
File : cfg ,
ini : cfg ,
newFile : newFile ,
} , nil
}
func ( p * iniFileConfigProvider ) Section ( section string ) ConfigSection {
return p . File . Section ( section )
func ( p * iniConfigProvider ) Section ( section string ) ConfigSection {
return & iniConfigSection { sec : p . ini . Section ( section ) }
}
func ( p * iniConfigProvider ) Sections ( ) ( sections [ ] ConfigSection ) {
for _ , s := range p . ini . Sections ( ) {
sections = append ( sections , & iniConfigSection { s } )
}
return sections
}
func ( p * iniFileConfigProvider ) NewSection ( name string ) ( ConfigSection , error ) {
return p . File . NewSection ( name )
func ( p * iniConfigProvider ) NewSection ( name string ) ( ConfigSection , error ) {
sec , err := p . ini . NewSection ( name )
if err != nil {
return nil , err
}
return & iniConfigSection { sec : sec } , nil
}
func ( p * iniFileConfigProvider ) GetSection ( name string ) ( ConfigSection , error ) {
return p . File . GetSection ( name )
func ( p * iniConfigProvider ) GetSection ( name string ) ( ConfigSection , error ) {
sec , err := p . ini . GetSection ( name )
if err != nil {
return nil , err
}
return & iniConfigSection { sec : sec } , nil
}
// Save save the content into file
func ( p * iniFileConfigProvider ) Save ( ) error {
if p . opts . CustomConf == "" {
// Save saves the content into file
func ( p * iniConfigProvider ) Save ( ) error {
filename := p . opts . CustomConf
if filename == "" {
if ! p . opts . AllowEmpty {
return fmt . Errorf ( "custom config path must not be empty" )
}
return nil
}
if p . newFile {
if err := os . MkdirAll ( filepath . Dir ( CustomCon f) , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "failed to create '%s': %v" , CustomCon f, err )
if err := os . MkdirAll ( filepath . Dir ( filename ) , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "failed to create '%s': %v" , filename , err )
}
}
if err := p . SaveTo ( p . opts . CustomConf ) ; err != nil {
return fmt . Errorf ( "failed to save '%s': %v" , p . opts . CustomConf , err )
if err := p . ini . SaveTo ( filename ) ; err != nil {
return fmt . Errorf ( "failed to save '%s': %v" , filename , err )
}
// Change permissions to be more restrictive
fi , err := os . Stat ( CustomCon f)
fi , err := os . Stat ( filename )
if err != nil {
return fmt . Errorf ( "failed to determine current conf file permissions: %v" , err )
}
if fi . Mode ( ) . Perm ( ) > 0 o600 {
if err = os . Chmod ( CustomCon f, 0 o600 ) ; err != nil {
if err = os . Chmod ( filename , 0 o600 ) ; err != nil {
log . Warn ( "Failed changing conf file permissions to -rw-------. Consider changing them manually." )
}
}
return nil
}
// a file is an implementation ConfigProvider and other implementations are possible, i.e. from docker, k8s, …
var _ ConfigProvider = & iniFileConfigProvider { }
func ( p * iniConfigProvider ) SaveTo ( filename string ) error {
return p . ini . SaveTo ( filename )
}
func mustMapSetting ( rootCfg ConfigProvider , sectionName string , setting interface { } ) {
func mustMapSetting ( rootCfg ConfigProvider , sectionName string , setting any ) {
if err := rootCfg . Section ( sectionName ) . MapTo ( setting ) ; err != nil {
log . Fatal ( "Failed to map %s settings: %v" , sectionName , err )
}
@ -219,3 +293,23 @@ func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
log . Error ( "Deprecated `[%s]` `%s` present which has been copied to database table sys_setting" , oldSection , oldKey )
}
}
// NewConfigProviderForLocale loads locale configuration from source and others. "string" if for a local file path, "[]byte" is for INI content
func NewConfigProviderForLocale ( source any , others ... any ) ( ConfigProvider , error ) {
iniFile , err := ini . LoadSources ( ini . LoadOptions {
IgnoreInlineComment : true ,
UnescapeValueCommentSymbols : true ,
} , source , others ... )
if err != nil {
return nil , fmt . Errorf ( "unable to load locale ini: %w" , err )
}
iniFile . BlockMode = false
return & iniConfigProvider {
ini : iniFile ,
newFile : true ,
} , nil
}
func init ( ) {
ini . PrettyFormat = false
}