mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-22 12:43:58 +08:00
admin: expect quoted ETags (#4879)
* expect quoted etags * admin: Minor refactor of etag facilities Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
This commit is contained in:
parent
53c4d788d4
commit
ad3a83fb91
9
admin.go
9
admin.go
|
@ -21,7 +21,6 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"expvar"
|
"expvar"
|
||||||
|
@ -901,6 +900,12 @@ func (h adminHandler) originAllowed(origin *url.URL) bool {
|
||||||
// produce and verify ETags.
|
// produce and verify ETags.
|
||||||
func etagHasher() hash.Hash32 { return fnv.New32a() }
|
func etagHasher() hash.Hash32 { return fnv.New32a() }
|
||||||
|
|
||||||
|
// makeEtag returns an Etag header value (including quotes) for
|
||||||
|
// the given config path and hash of contents at that path.
|
||||||
|
func makeEtag(path string, hash hash.Hash) string {
|
||||||
|
return fmt.Sprintf(`"%s %x"`, path, hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
|
@ -919,7 +924,7 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
// we could consider setting up a sync.Pool for the summed
|
// we could consider setting up a sync.Pool for the summed
|
||||||
// hashes to reduce GC pressure.
|
// hashes to reduce GC pressure.
|
||||||
w.Header().Set("ETag", r.URL.Path+" "+hex.EncodeToString(hash.Sum(nil)))
|
w.Header().Set("Etag", makeEtag(r.URL.Path, hash))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -168,7 +168,7 @@ func TestETags(t *testing.T) {
|
||||||
const key = "/" + rawConfigKey + "/apps/foo"
|
const key = "/" + rawConfigKey + "/apps/foo"
|
||||||
|
|
||||||
// try update the config with the wrong etag
|
// try update the config with the wrong etag
|
||||||
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), "/"+rawConfigKey+" not_an_etag", false)
|
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
|
||||||
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
||||||
t.Fatalf("expected precondition failed; got %v", err)
|
t.Fatalf("expected precondition failed; got %v", err)
|
||||||
}
|
}
|
||||||
|
@ -180,13 +180,13 @@ func TestETags(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the same update with the correct key
|
// do the same update with the correct key
|
||||||
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), key+" "+hex.EncodeToString(hash.Sum(nil)), false)
|
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected update to work; got %v", err)
|
t.Fatalf("expected update to work; got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now try another update. The hash should no longer match and we should get precondition failed
|
// now try another update. The hash should no longer match and we should get precondition failed
|
||||||
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), key+" "+hex.EncodeToString(hash.Sum(nil)), false)
|
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
|
||||||
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
||||||
t.Fatalf("expected precondition failed; got %v", err)
|
t.Fatalf("expected precondition failed; got %v", err)
|
||||||
}
|
}
|
||||||
|
|
10
caddy.go
10
caddy.go
|
@ -145,8 +145,16 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||||
defer currentCfgMu.Unlock()
|
defer currentCfgMu.Unlock()
|
||||||
|
|
||||||
if ifMatchHeader != "" {
|
if ifMatchHeader != "" {
|
||||||
|
// expect the first and last character to be quotes
|
||||||
|
if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' {
|
||||||
|
return APIError{
|
||||||
|
HTTPStatus: http.StatusBadRequest,
|
||||||
|
Err: fmt.Errorf("malformed If-Match header; expect quoted string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// read out the parts
|
// read out the parts
|
||||||
parts := strings.Fields(ifMatchHeader)
|
parts := strings.Fields(ifMatchHeader[1 : len(ifMatchHeader)-1])
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusBadRequest,
|
HTTPStatus: http.StatusBadRequest,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user