diff --git a/go.mod b/go.mod index f7a8bac..591753a 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/gorilla/websocket v1.4.0 github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 + github.com/icza/dyno v0.0.0-20200205103839-49cb13720835 github.com/imdario/mergo v0.3.8 github.com/klauspost/pgzip v1.2.3 github.com/magiconair/properties v1.8.1 diff --git a/go.sum b/go.sum index 6d9502e..89d3e3e 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDG github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/icza/dyno v0.0.0-20200205103839-49cb13720835 h1:f1irK5f03uGGj+FjgQfZ5VhdKNVQVJ4skHsedzVohQ4= +github.com/icza/dyno v0.0.0-20200205103839-49cb13720835/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= diff --git a/parser/helpers.go b/parser/helpers.go index afee5ad..a7c4fc8 100644 --- a/parser/helpers.go +++ b/parser/helpers.go @@ -9,7 +9,6 @@ import ( "go.uber.org/zap" "io/ioutil" "os" - "reflect" "regexp" "strconv" "strings" @@ -48,13 +47,14 @@ func readFileBytes(path string) ([]byte, error) { } // Gets the value of a key based on the value type defined. -func getKeyValue(value []byte) interface{} { - if reflect.ValueOf(value).Kind() == reflect.Bool { +func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} { + if cfr.ReplaceWith.Type() == jsonparser.Boolean { v, _ := strconv.ParseBool(string(value)) return v } - // Try to parse into an int, if this fails just ignore the error and + // Try to parse into an int, if this fails just ignore the error and continue + // through, returning the string. if v, err := strconv.Atoi(string(value)); err == nil { return v } @@ -70,7 +70,9 @@ func getKeyValue(value []byte) interface{} { // 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. +// This does not currently support nested wildcard matches. For example, foo.*.bar +// will work, however foo.*.bar.*.baz will not, since we'll only be splitting at the +// first wildcard, and not subsequent ones. func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) { parsed, err := gabs.ParseJSON(data) if err != nil { @@ -143,7 +145,7 @@ func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path st } } - _, err := c.SetP(getKeyValue(value), path) + _, err := c.SetP(cfr.getKeyValue(value), path) return err } diff --git a/parser/parser.go b/parser/parser.go index 75b4aa2..71177b7 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -5,14 +5,16 @@ import ( "encoding/json" "github.com/beevik/etree" "github.com/buger/jsonparser" - "github.com/ghodss/yaml" + "github.com/icza/dyno" "github.com/magiconair/properties" "github.com/pkg/errors" "github.com/pterodactyl/wings/config" "go.uber.org/zap" "gopkg.in/ini.v1" + "gopkg.in/yaml.v2" "io/ioutil" "os" + "regexp" "strings" ) @@ -73,6 +75,11 @@ func (f *ConfigurationFile) UnmarshalJSON(data []byte) error { return nil } +// Regex to match paths such as foo[1].bar[2] and convert them into a format that +// gabs can work with, such as foo.1.bar.2 in this case. This is applied when creating +// the struct for the configuration file replacements. +var cfrMatchReplacement = regexp.MustCompile(`\[(\d+)]`) + // Defines a single find/replace instance for a given server configuration file. type ConfigurationFileReplacement struct { Match string `json:"match"` @@ -87,7 +94,9 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error { if err != nil { return err } - cfr.Match = m + + // See comment on the replacement regex to understand what exactly this is doing. + cfr.Match = cfrMatchReplacement.ReplaceAllString(m, ".$1") iv, err := jsonparser.GetString(data, "if_value") // We only check keypath here since match & replace_with should be present on all of @@ -130,7 +139,6 @@ func (f *ConfigurationFile) Parse(path string, internal bool) error { f.configuration = mb } - var err error switch f.Parser { @@ -351,10 +359,15 @@ func (f *ConfigurationFile) parseYamlFile(path string) error { return err } + i := make(map[string]interface{}) + if err := yaml.Unmarshal(b, &i); err != nil { + return err + } + // Unmarshal the yaml data into a JSON interface such that we can work with // any arbitrary data structure. If we don't do this, I can't use gabs which // makes working with unknown JSON signficiantly easier. - jsonBytes, err := yaml.YAMLToJSON(b) + jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i)) if err != nil { return err } @@ -367,7 +380,7 @@ func (f *ConfigurationFile) parseYamlFile(path string) error { } // Remarshal the JSON into YAML format before saving it back to the disk. - marshaled, err := yaml.JSONToYAML(data.Bytes()) + marshaled, err := yaml.Marshal(data.Data()) if err != nil { return err }