diff --git a/agent/txn_endpoint.go b/agent/txn_endpoint.go index 204fd3025b2a..52803ae29c42 100644 --- a/agent/txn_endpoint.go +++ b/agent/txn_endpoint.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "fmt" "net/http" + "strconv" "strings" "time" @@ -98,6 +99,18 @@ func isWrite(op api.KVOp) bool { // a boolean, that if false means an error response has been generated and // processing should stop. func (s *HTTPServer) convertOps(resp http.ResponseWriter, req *http.Request) (structs.TxnOps, int, bool) { + + sizeStr := req.Header.Get("Content-Length") + if sizeStr != "" { + if size, err := strconv.Atoi(sizeStr); err != nil { + fmt.Fprintf(resp, "Failed to parse Content-Length: %v", err) + return nil, 0, false + } else if size > int(s.agent.config.KVMaxValueSize) { + fmt.Fprintf(resp, "Request body too large, max size: %v bytes", s.agent.config.KVMaxValueSize) + return nil, 0, false + } + } + // Note the body is in API format, and not the RPC format. If we can't // decode it, we will return a 400 since we don't have enough context to // associate the error with a given operation. diff --git a/agent/txn_endpoint_test.go b/agent/txn_endpoint_test.go index b95ab4462875..ebfe83613d98 100644 --- a/agent/txn_endpoint_test.go +++ b/agent/txn_endpoint_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "strconv" "strings" "testing" "time" @@ -602,3 +603,50 @@ func TestTxnEndpoint_UpdateCheck(t *testing.T) { } verify.Values(t, "", txnResp, expected) } + +func TestConvertOps_ContentLength(t *testing.T) { + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + + jsonBody := `[ + { + "KV": { + "Verb": "set", + "Key": "key1", + "Value": "aGVsbG8gd29ybGQ=" + } + } + ]` + + tests := []struct { + contentLength string + ok bool + }{ + {"", true}, + {strconv.Itoa(len(jsonBody)), true}, + {strconv.Itoa(raft.SuggestedMaxDataSize), true}, + {strconv.Itoa(raft.SuggestedMaxDataSize + 100), false}, + } + + for _, tc := range tests { + t.Run("contentLength: "+tc.contentLength, func(t *testing.T) { + resp := httptest.NewRecorder() + var body bytes.Buffer + + // Doesn't matter what the request body size actually is, as we only + // check 'Content-Length' header in this test anyway. + body.WriteString(jsonBody) + + req := httptest.NewRequest("POST", "http://foo.com", &body) + req.Header.Add("Content-Length", tc.contentLength) + + _, _, ok := a.srv.convertOps(resp, req) + if ok != tc.ok { + t.Fatal("ok != tc.ok") + } + + }) + + } + +}