diff --git a/parser/helpers.go b/parser/helpers.go new file mode 100644 index 0000000..cc3c86d --- /dev/null +++ b/parser/helpers.go @@ -0,0 +1,130 @@ +package parser + +import ( + "github.com/Jeffail/gabs/v2" + "github.com/buger/jsonparser" + "github.com/iancoleman/strcase" + "github.com/pkg/errors" + "io/ioutil" + "os" + "strconv" + "strings" +) + +// Gets the []byte representation of a configuration file to be passed through to other +// handler functions. If the file does not currently exist, it will be created. +func readFileBytes(path string) ([]byte, error) { + file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + return nil, err + } + defer file.Close() + + return ioutil.ReadAll(file) +} + +// Helper function to set the value of the JSON key item based on the jsonparser value +// type returned. +func setPathway(c *gabs.Container, path string, value []byte, dt jsonparser.ValueType) error { + var err error + + switch dt { + case jsonparser.Number: + { + v, _ := strconv.Atoi(string(value)) + _, err = c.SetP(v, path) + } + break + case jsonparser.Boolean: + { + v, _ := strconv.ParseBool(string(value)) + _, err = c.SetP(v, path) + } + break + default: + _, err = c.SetP(string(value), path) + } + + return err +} + +// Iterate over an unstructured JSON/YAML/etc. interface and set all of the required +// key/value pairs for the configuration file. +// +// We need to support wildcard characters in key searches, this allows you to make +// modifications to multiple keys at once, especially useful for games with multiple +// configurations per-world (such as Spigot and Bungeecord) where we'll need to make +// adjustments to the bind address for the user. +// +// This does not currently support nested matches. container.*.foo.*.bar will not work. +func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) { + parsed, err := gabs.ParseJSON(data) + if err != nil { + return nil, err + } + + for _, v := range f.Replace { + value, dt, err := f.LookupConfigurationValue(v) + if err != nil { + return nil, err + } + + // Check for a wildcard character, and if found split the key on that value to + // begin doing a search and replace in the data. + if strings.Contains(v.Match, ".*") { + parts := strings.SplitN(v.Match, ".*", 2) + + // Iterate over each matched child and set the remaining path to the value + // that is passed through in the loop. + // + // If the child is a null value, nothing will happen. Seems reasonable as of the + // time this code is being written. + for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() { + if err := setPathway(child, strings.Trim(parts[1], "."), value, dt); err != nil { + return nil, err + } + } + } else { + if err = setPathway(parsed, v.Match, value, dt); err != nil { + return nil, err + } + } + } + + return parsed, nil +} + +// Looks up a configuration value on the Daemon given a dot-notated syntax. +func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplacement) ([]byte, jsonparser.ValueType, error) { + if !configMatchRegex.Match([]byte(cfr.Value)) { + return []byte(cfr.Value), cfr.ValueType, nil + } + + // If there is a match, lookup the value in the configuration for the Daemon. If no key + // is found, just return the string representation, otherwise use the value from the + // daemon configuration here. + huntPath := configMatchRegex.ReplaceAllString(cfr.Value, "$1") + + var path []string + // The camel casing is important here, the configuration for the Daemon does not use + // JSON, and as such all of the keys will be generated in CamelCase format, rather than + // the expected snake_case from the old Daemon. + for _, value := range strings.Split(huntPath, ".") { + path = append(path, strcase.ToCamel(value)) + } + + // Look for the key in the configuration file, and if found return that value to the + // calling function. + match, dt, _, err := jsonparser.Get(f.configuration, path...) + if err != nil { + if err != jsonparser.KeyPathNotFoundError { + return match, dt, errors.WithStack(err) + } + + // If there is no key, keep the original value intact, that way it is obvious there + // is a replace issue at play. + return []byte(cfr.Value), cfr.ValueType, nil + } else { + return match, cfr.ValueType, nil + } +} diff --git a/parser/parser.go b/parser/parser.go index e615965..c4d1de9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -4,10 +4,8 @@ import ( "bufio" "encoding/json" "fmt" - "github.com/Jeffail/gabs/v2" "github.com/buger/jsonparser" "github.com/ghodss/yaml" - "github.com/iancoleman/strcase" "github.com/magiconair/properties" "github.com/pkg/errors" "github.com/pterodactyl/wings/config" @@ -15,7 +13,6 @@ import ( "io/ioutil" "os" "regexp" - "strconv" "strings" ) @@ -110,89 +107,6 @@ func (f *ConfigurationFile) Parse(path string) error { return err } -// Gets the []byte representation of a configuration file to be passed through to other -// handler functions. If the file does not currently exist, it will be created. -func readFileBytes(path string) ([]byte, error) { - file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644) - if err != nil { - return nil, err - } - defer file.Close() - - return ioutil.ReadAll(file) -} - -// Helper function to set the value of the JSON key item based on the jsonparser value -// type returned. -func setPathway(c *gabs.Container, path string, value []byte, dt jsonparser.ValueType) error { - var err error - - switch dt { - case jsonparser.Number: - { - v, _ := strconv.Atoi(string(value)) - _, err = c.SetP(v, path) - } - break - case jsonparser.Boolean: - { - v, _ := strconv.ParseBool(string(value)) - _, err = c.SetP(v, path) - } - break - default: - _, err = c.SetP(string(value), path) - } - - return err -} - -// Iterate over an unstructured JSON/YAML/etc. interface and set all of the required -// key/value pairs for the configuration file. -// -// We need to support wildcard characters in key searches, this allows you to make -// modifications to multiple keys at once, especially useful for games with multiple -// configurations per-world (such as Spigot and Bungeecord) where we'll need to make -// adjustments to the bind address for the user. -// -// This does not currently support nested matches. container.*.foo.*.bar will not work. -func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) { - parsed, err := gabs.ParseJSON(data) - if err != nil { - return nil, err - } - - for _, v := range f.Replace { - value, dt, err := f.lookupConfigurationValue(v) - if err != nil { - return nil, err - } - - // Check for a wildcard character, and if found split the key on that value to - // begin doing a search and replace in the data. - if strings.Contains(v.Match, ".*") { - parts := strings.SplitN(v.Match, ".*", 2) - - // Iterate over each matched child and set the remaining path to the value - // that is passed through in the loop. - // - // If the child is a null value, nothing will happen. Seems reasonable as of the - // time this code is being written. - for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() { - if err := setPathway(child, strings.Trim(parts[1], "."), value, dt); err != nil { - return nil, err - } - } - } else { - if err = setPathway(parsed, v.Match, value, dt); err != nil { - return nil, err - } - } - } - - return parsed, nil -} - // Prases a json file updating any matching key/value pairs. If a match is not found, the // value is set regardless in the file. See the commentary in parseYamlFile for more details // about what is happening during this process. @@ -294,7 +208,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error { } for _, replace := range f.Replace { - data, _, err := f.lookupConfigurationValue(replace) + data, _, err := f.LookupConfigurationValue(replace) if err != nil { return errors.WithStack(err) } @@ -313,38 +227,3 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error { return err } - -// Looks up a configuration value on the Daemon given a dot-notated syntax. -func (f *ConfigurationFile) lookupConfigurationValue(cfr ConfigurationFileReplacement) ([]byte, jsonparser.ValueType, error) { - if !configMatchRegex.Match([]byte(cfr.Value)) { - return []byte(cfr.Value), cfr.ValueType, nil - } - - // If there is a match, lookup the value in the configuration for the Daemon. If no key - // is found, just return the string representation, otherwise use the value from the - // daemon configuration here. - huntPath := configMatchRegex.ReplaceAllString(cfr.Value, "$1") - - var path []string - // The camel casing is important here, the configuration for the Daemon does not use - // JSON, and as such all of the keys will be generated in CamelCase format, rather than - // the expected snake_case from the old Daemon. - for _, value := range strings.Split(huntPath, ".") { - path = append(path, strcase.ToCamel(value)) - } - - // Look for the key in the configuration file, and if found return that value to the - // calling function. - match, dt, _, err := jsonparser.Get(f.configuration, path...) - if err != nil { - if err != jsonparser.KeyPathNotFoundError { - return match, dt, errors.WithStack(err) - } - - // If there is no key, keep the original value intact, that way it is obvious there - // is a replace issue at play. - return []byte(cfr.Value), cfr.ValueType, nil - } else { - return match, cfr.ValueType, nil - } -}