@marcel91200 / caddy Mirror · read-only / commits / ad3a83fb
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 16:45 UTC

admin: expect quoted ETags (#4879)

* expect quoted etags * admin: Minor refactor of etag facilities Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>

jhwz committed Jul 13, 2022 at 06:23 UTC ad3a83fb9169899226ce12a61c16b5bf4d03c482
3 files changed +20 -7
admin.go
+7 -2
@@ -21,7 +21,6 @@ import (
21 "crypto/tls"
22 "crypto/x509"
23 "encoding/base64"
24 - "encoding/hex"
24 "encoding/json"
25 "errors"
26 "expvar"
@@ -901,6 +900,12 @@ func (h adminHandler) originAllowed(origin *url.URL) bool {
900 // produce and verify ETags.
901 func etagHasher() hash.Hash32 { return fnv.New32a() }
902
903 +// makeEtag returns an Etag header value (including quotes) for
904 +// the given config path and hash of contents at that path.
905 +func makeEtag(path string, hash hash.Hash) string {
906 + return fmt.Sprintf(`"%s %x"`, path, hash.Sum(nil))
907 +}
908 +
909 func handleConfig(w http.ResponseWriter, r *http.Request) error {
910 switch r.Method {
911 case http.MethodGet:
@@ -919,7 +924,7 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
924
925 // we could consider setting up a sync.Pool for the summed
926 // hashes to reduce GC pressure.
922 - w.Header().Set("ETag", r.URL.Path+" "+hex.EncodeToString(hash.Sum(nil)))
927 + w.Header().Set("Etag", makeEtag(r.URL.Path, hash))
928
929 return nil
930
admin_test.go
+4 -4
@@ -15,8 +15,8 @@
15 package caddy
16
17 import (
18 - "encoding/hex"
18 "encoding/json"
19 + "fmt"
20 "net/http"
21 "reflect"
22 "sync"
@@ -168,7 +168,7 @@ func TestETags(t *testing.T) {
168 const key = "/" + rawConfigKey + "/apps/foo"
169
170 // try update the config with the wrong etag
171 - err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), "/"+rawConfigKey+" not_an_etag", false)
171 + err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
172 if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
173 t.Fatalf("expected precondition failed; got %v", err)
174 }
@@ -180,13 +180,13 @@ func TestETags(t *testing.T) {
180 }
181
182 // do the same update with the correct key
183 - err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), key+" "+hex.EncodeToString(hash.Sum(nil)), false)
183 + err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
184 if err != nil {
185 t.Fatalf("expected update to work; got %v", err)
186 }
187
188 // now try another update. The hash should no longer match and we should get precondition failed
189 - err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), key+" "+hex.EncodeToString(hash.Sum(nil)), false)
189 + err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
190 if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
191 t.Fatalf("expected precondition failed; got %v", err)
192 }
caddy.go
+9 -1
@@ -145,8 +145,16 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
145 defer currentCfgMu.Unlock()
146
147 if ifMatchHeader != "" {
148 + // expect the first and last character to be quotes
149 + if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' {
150 + return APIError{
151 + HTTPStatus: http.StatusBadRequest,
152 + Err: fmt.Errorf("malformed If-Match header; expect quoted string"),
153 + }
154 + }
155 +
156 // read out the parts
149 - parts := strings.Fields(ifMatchHeader)
157 + parts := strings.Fields(ifMatchHeader[1 : len(ifMatchHeader)-1])
158 if len(parts) != 2 {
159 return APIError{
160 HTTPStatus: http.StatusBadRequest,