diff options
author | jhwz <[email protected]> | 2022-07-07 07:50:07 +1200 |
---|---|---|
committer | GitHub <[email protected]> | 2022-07-06 13:50:07 -0600 |
commit | f259ed52bb3764ce4fd5d88f1712cb43247c2639 (patch) | |
tree | a0f7e7a68117b81d757e1a6daddc3e257b97f29b /caddy.go | |
parent | 8bac134f26874b25aa22f8fee325a05d39157a4c (diff) | |
download | caddy-f259ed52bb3764ce4fd5d88f1712cb43247c2639.tar.gz caddy-f259ed52bb3764ce4fd5d88f1712cb43247c2639.zip |
admin: support ETag on config endpoints (#4579)
* admin: support ETags
* support etags
Co-authored-by: Matt Holt <[email protected]>
Diffstat (limited to 'caddy.go')
-rw-r--r-- | caddy.go | 38 |
1 files changed, 35 insertions, 3 deletions
@@ -17,6 +17,7 @@ package caddy import ( "bytes" "context" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -111,7 +112,7 @@ func Load(cfgJSON []byte, forceReload bool) error { } }() - err := changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload) + err := changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload) if errors.Is(err, errSameConfig) { err = nil // not really an error } @@ -125,7 +126,12 @@ func Load(cfgJSON []byte, forceReload bool) error { // occur unless forceReload is true. If the config is unchanged and not // forcefully reloaded, then errConfigUnchanged This function is safe for // concurrent use. -func changeConfig(method, path string, input []byte, forceReload bool) error { +// The ifMatchHeader can optionally be given a string of the format: +// "<path> <hash>" +// where <path> is the absolute path in the config and <hash> is the expected hash of +// the config at that path. If the hash in the ifMatchHeader doesn't match +// the hash of the config, then an APIError with status 412 will be returned. +func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error { switch method { case http.MethodGet, http.MethodHead, @@ -138,6 +144,32 @@ func changeConfig(method, path string, input []byte, forceReload bool) error { currentCfgMu.Lock() defer currentCfgMu.Unlock() + if ifMatchHeader != "" { + // read out the parts + parts := strings.Fields(ifMatchHeader) + if len(parts) != 2 { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("malformed If-Match header; expect format \"<path> <hash>\""), + } + } + + // get the current hash of the config + // at the given path + hash := etagHasher() + err := unsyncedConfigAccess(http.MethodGet, parts[0], nil, hash) + if err != nil { + return err + } + + if hex.EncodeToString(hash.Sum(nil)) != parts[1] { + return APIError{ + HTTPStatus: http.StatusPreconditionFailed, + Err: fmt.Errorf("If-Match header did not match current config hash"), + } + } + } + err := unsyncedConfigAccess(method, path, input, nil) if err != nil { return err @@ -500,7 +532,7 @@ func finishSettingUp(ctx Context, cfg *Config) error { runLoadedConfig := func(config []byte) error { logger.Info("applying dynamically-loaded config") - err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, false) + err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, "", false) if errors.Is(err, errSameConfig) { return err } |