Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
admin: Preserve "@id" fields through partial changes (fixes #2902)
Matthew Holt committed
Nov 27, 2019 at 11:49 UTC
6e10586303fd90f0298c8086e754b35dfc3f01ba
2 files changed
+33
-11
admin.go
+7
@@ -27,6 +27,7 @@ import (
27
"net/url"
28
"os"
29
"path"
30
+ "regexp"
31
"strconv"
32
"strings"
33
"sync"
@@ -764,6 +765,12 @@ var (
765
}
766
)
767
768
+// idRegexp is used to match ID fields and their associated values
769
+// in the config. It also matches adjacent commas so that syntax
770
+// can be preserved no matter where in the object the field appears.
771
+// It supports string and most numeric values.
772
+var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `":\s?(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`)
773
+
774
const (
775
rawConfigKey = "config"
776
idKey = "@id"
caddy.go
+26
-11
@@ -102,16 +102,6 @@ func changeConfig(method, path string, input []byte, forceReload bool) error {
102
return err
103
}
104
105
- // find any IDs in this config and index them
106
- idx := make(map[string]string)
107
- err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx)
108
- if err != nil {
109
- return APIError{
110
- Code: http.StatusInternalServerError,
111
- Err: fmt.Errorf("indexing config: %v", err),
112
- }
113
- }
114
-
105
// the mutation is complete, so encode the entire config as JSON
106
newCfg, err := json.Marshal(rawCfg[rawConfigKey])
107
if err != nil {
@@ -127,6 +117,32 @@ func changeConfig(method, path string, input []byte, forceReload bool) error {
117
return nil
118
}
119
120
+ // find any IDs in this config and index them
121
+ idx := make(map[string]string)
122
+ err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx)
123
+ if err != nil {
124
+ return APIError{
125
+ Code: http.StatusInternalServerError,
126
+ Err: fmt.Errorf("indexing config: %v", err),
127
+ }
128
+ }
129
+
130
+ // remove any @id fields from the JSON, which would cause
131
+ // loading to break since the field wouldn't be recognized
132
+ // (an alternate way to do this would be to delete them from
133
+ // rawCfg as they are indexed, then iterate the index we made
134
+ // and add them back after encoding as JSON)
135
+ newCfg = idRegexp.ReplaceAllFunc(newCfg, func(in []byte) []byte {
136
+ // matches with a comma on both sides (when "@id" property is
137
+ // not the first or last in the object) need to keep exactly
138
+ // one comma for correct JSON syntax
139
+ comma := []byte{','}
140
+ if bytes.HasPrefix(in, comma) && bytes.HasSuffix(in, comma) {
141
+ return comma
142
+ }
143
+ return []byte{}
144
+ })
145
+
146
// load this new config; if it fails, we need to revert to
147
// our old representation of caddy's actual config
148
err = unsyncedDecodeAndRun(newCfg)
@@ -183,7 +199,6 @@ func indexConfigObjects(ptr interface{}, configPath string, index map[string]str
199
default:
200
return fmt.Errorf("%s: %s field must be a string or number", configPath, idKey)
201
}
186
- delete(val, idKey) // field is no longer needed, and will break config if not removed
202
continue
203
}
204
// traverse this object property recursively