diff options
author | Matthew Holt <mholt@users.noreply.github.com> | 2019-11-27 11:49:49 -0700 |
---|---|---|
committer | Matthew Holt <mholt@users.noreply.github.com> | 2019-11-27 11:49:49 -0700 |
commit | 6e10586303fd90f0298c8086e754b35dfc3f01ba (patch) | |
tree | 68dc2ba044debe5f4a75cebfc29d044a6064d1bd | |
parent | 8de1a762273323608d9d9080c42057d1814070b6 (diff) |
admin: Preserve "@id" fields through partial changes (fixes #2902)
-rw-r--r-- | admin.go | 7 | ||||
-rw-r--r-- | caddy.go | 37 |
2 files changed, 33 insertions, 11 deletions
@@ -27,6 +27,7 @@ import ( "net/url" "os" "path" + "regexp" "strconv" "strings" "sync" @@ -764,6 +765,12 @@ var ( } ) +// idRegexp is used to match ID fields and their associated values +// in the config. It also matches adjacent commas so that syntax +// can be preserved no matter where in the object the field appears. +// It supports string and most numeric values. +var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `":\s?(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`) + const ( rawConfigKey = "config" idKey = "@id" @@ -102,16 +102,6 @@ func changeConfig(method, path string, input []byte, forceReload bool) error { return err } - // find any IDs in this config and index them - idx := make(map[string]string) - err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx) - if err != nil { - return APIError{ - Code: http.StatusInternalServerError, - Err: fmt.Errorf("indexing config: %v", err), - } - } - // the mutation is complete, so encode the entire config as JSON newCfg, err := json.Marshal(rawCfg[rawConfigKey]) if err != nil { @@ -127,6 +117,32 @@ func changeConfig(method, path string, input []byte, forceReload bool) error { return nil } + // find any IDs in this config and index them + idx := make(map[string]string) + err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx) + if err != nil { + return APIError{ + Code: http.StatusInternalServerError, + Err: fmt.Errorf("indexing config: %v", err), + } + } + + // remove any @id fields from the JSON, which would cause + // loading to break since the field wouldn't be recognized + // (an alternate way to do this would be to delete them from + // rawCfg as they are indexed, then iterate the index we made + // and add them back after encoding as JSON) + newCfg = idRegexp.ReplaceAllFunc(newCfg, func(in []byte) []byte { + // matches with a comma on both sides (when "@id" property is + // not the first or last in the object) need to keep exactly + // one comma for correct JSON syntax + comma := []byte{','} + if bytes.HasPrefix(in, comma) && bytes.HasSuffix(in, comma) { + return comma + } + return []byte{} + }) + // load this new config; if it fails, we need to revert to // our old representation of caddy's actual config err = unsyncedDecodeAndRun(newCfg) @@ -183,7 +199,6 @@ func indexConfigObjects(ptr interface{}, configPath string, index map[string]str default: return fmt.Errorf("%s: %s field must be a string or number", configPath, idKey) } - delete(val, idKey) // field is no longer needed, and will break config if not removed continue } // traverse this object property recursively |