mirror of
https://github.com/TwiN/gatus.git
synced 2026-02-04 09:31:45 +00:00
fix: handle HTTP header names case-insensitively (#1506)
Details: HTTP header names should be case-insensitive per RFC 7230. When the users configure the headers like 'user-agent' (lowercase), gatus was adding its own 'User-Agent' header because the case-sensitive map lookup failed to find the existing header. Fixed headers: - User-Agent - Content-Type - Host Fixes #1491" Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
@@ -221,12 +221,12 @@ func (e *Endpoint) ValidateAndSetDefaults() error {
|
|||||||
e.Headers = make(map[string]string)
|
e.Headers = make(map[string]string)
|
||||||
}
|
}
|
||||||
// Automatically add user agent header if there isn't one specified in the endpoint configuration
|
// Automatically add user agent header if there isn't one specified in the endpoint configuration
|
||||||
if _, userAgentHeaderExists := e.Headers[UserAgentHeader]; !userAgentHeaderExists {
|
if !hasHeader(e.Headers, UserAgentHeader) {
|
||||||
e.Headers[UserAgentHeader] = GatusUserAgent
|
e.Headers[UserAgentHeader] = GatusUserAgent
|
||||||
}
|
}
|
||||||
// Automatically add "Content-Type: application/json" header if there's no Content-Type set
|
// Automatically add "Content-Type: application/json" header if there's no Content-Type set
|
||||||
// and endpoint.GraphQL is set to true
|
// and endpoint.GraphQL is set to true
|
||||||
if _, contentTypeHeaderExists := e.Headers[ContentTypeHeader]; !contentTypeHeaderExists && e.GraphQL {
|
if !hasHeader(e.Headers, ContentTypeHeader) && e.GraphQL {
|
||||||
e.Headers[ContentTypeHeader] = "application/json"
|
e.Headers[ContentTypeHeader] = "application/json"
|
||||||
}
|
}
|
||||||
if len(e.Conditions) == 0 {
|
if len(e.Conditions) == 0 {
|
||||||
@@ -493,8 +493,8 @@ func (e *Endpoint) call(result *Result) {
|
|||||||
wsHeaders[k] = v
|
wsHeaders[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, exists := wsHeaders["User-Agent"]; !exists {
|
if !hasHeader(wsHeaders, UserAgentHeader) {
|
||||||
wsHeaders["User-Agent"] = GatusUserAgent
|
wsHeaders[UserAgentHeader] = GatusUserAgent
|
||||||
}
|
}
|
||||||
result.Connected, result.Body, err = client.QueryWebSocket(e.URL, e.getParsedBody(), wsHeaders, e.ClientConfig)
|
result.Connected, result.Body, err = client.QueryWebSocket(e.URL, e.getParsedBody(), wsHeaders, e.ClientConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -582,7 +582,7 @@ func (e *Endpoint) buildHTTPRequest() *http.Request {
|
|||||||
request, _ := http.NewRequest(e.Method, e.URL, bodyBuffer)
|
request, _ := http.NewRequest(e.Method, e.URL, bodyBuffer)
|
||||||
for k, v := range e.Headers {
|
for k, v := range e.Headers {
|
||||||
request.Header.Set(k, v)
|
request.Header.Set(k, v)
|
||||||
if k == HostHeader {
|
if strings.EqualFold(k, HostHeader) {
|
||||||
request.Host = v
|
request.Host = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,3 +626,13 @@ func (e *Endpoint) needsToRetrieveIP() bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasHeader checks if a header exists in the map using a case-insensitive lookup
|
||||||
|
func hasHeader(headers map[string]string, name string) bool {
|
||||||
|
for k := range headers {
|
||||||
|
if strings.EqualFold(k, name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,30 @@ import (
|
|||||||
"github.com/TwiN/gatus/v5/test"
|
"github.com/TwiN/gatus/v5/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestHasHeader(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
headers map[string]string
|
||||||
|
lookup string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{name: "exact-match", headers: map[string]string{"User-Agent": "test"}, lookup: "User-Agent", expected: true},
|
||||||
|
{name: "lowercase-lookup", headers: map[string]string{"User-Agent": "test"}, lookup: "user-agent", expected: true},
|
||||||
|
{name: "uppercase-lookup", headers: map[string]string{"user-agent": "test"}, lookup: "USER-AGENT", expected: true},
|
||||||
|
{name: "mixed-case", headers: map[string]string{"UsEr-AgEnT": "test"}, lookup: "uSeR-aGeNt", expected: true},
|
||||||
|
{name: "not-found", headers: map[string]string{"Content-Type": "test"}, lookup: "User-Agent", expected: false},
|
||||||
|
{name: "empty-headers", headers: map[string]string{}, lookup: "User-Agent", expected: false},
|
||||||
|
{name: "nil-headers", headers: nil, lookup: "User-Agent", expected: false},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
if result := hasHeader(scenario.headers, scenario.lookup); result != scenario.expected {
|
||||||
|
t.Errorf("expected %v, got %v", scenario.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEndpoint(t *testing.T) {
|
func TestEndpoint(t *testing.T) {
|
||||||
defer client.InjectHTTPClient(nil)
|
defer client.InjectHTTPClient(nil)
|
||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
@@ -722,6 +746,76 @@ func TestEndpoint_buildHTTPRequestWithHostHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEndpoint_buildHTTPRequestWithLowercaseUserAgent(t *testing.T) {
|
||||||
|
condition := Condition("[STATUS] == 200")
|
||||||
|
endpoint := Endpoint{
|
||||||
|
Name: "website-health",
|
||||||
|
URL: "https://twin.sh/health",
|
||||||
|
Conditions: []Condition{condition},
|
||||||
|
Headers: map[string]string{
|
||||||
|
"user-agent": "CustomAgent/1.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := endpoint.ValidateAndSetDefaults()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("did not expect an error, got", err)
|
||||||
|
}
|
||||||
|
if _, exists := endpoint.Headers[UserAgentHeader]; exists {
|
||||||
|
t.Error("User-Agent header should not have been added since user-agent was already specified")
|
||||||
|
}
|
||||||
|
request := endpoint.buildHTTPRequest()
|
||||||
|
if userAgent := request.Header.Get("User-Agent"); userAgent != "CustomAgent/1.0" {
|
||||||
|
t.Errorf("request.Header.Get(User-Agent) should've been CustomAgent/1.0, but was %s", userAgent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpoint_buildHTTPRequestWithLowercaseContentType(t *testing.T) {
|
||||||
|
condition := Condition("[STATUS] == 200")
|
||||||
|
endpoint := Endpoint{
|
||||||
|
Name: "website-graphql",
|
||||||
|
URL: "https://twin.sh/graphql",
|
||||||
|
Method: "POST",
|
||||||
|
Conditions: []Condition{condition},
|
||||||
|
GraphQL: true,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"content-type": "application/graphql",
|
||||||
|
},
|
||||||
|
Body: `{ users { id } }`,
|
||||||
|
}
|
||||||
|
err := endpoint.ValidateAndSetDefaults()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("did not expect an error, got", err)
|
||||||
|
}
|
||||||
|
if _, exists := endpoint.Headers[ContentTypeHeader]; exists {
|
||||||
|
t.Error("Content-Type header should not have been added since content-type was already specified")
|
||||||
|
}
|
||||||
|
request := endpoint.buildHTTPRequest()
|
||||||
|
if contentType := request.Header.Get("Content-Type"); contentType != "application/graphql" {
|
||||||
|
t.Errorf("request.Header.Get(Content-Type) should've been application/graphql, but was %s", contentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpoint_buildHTTPRequestWithLowercaseHostHeader(t *testing.T) {
|
||||||
|
condition := Condition("[STATUS] == 200")
|
||||||
|
endpoint := Endpoint{
|
||||||
|
Name: "website-health",
|
||||||
|
URL: "https://twin.sh/health",
|
||||||
|
Method: "POST",
|
||||||
|
Conditions: []Condition{condition},
|
||||||
|
Headers: map[string]string{
|
||||||
|
"host": "example.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := endpoint.ValidateAndSetDefaults()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("did not expect an error, got", err)
|
||||||
|
}
|
||||||
|
request := endpoint.buildHTTPRequest()
|
||||||
|
if request.Host != "example.com" {
|
||||||
|
t.Error("request.Host should've been example.com, but was", request.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEndpoint_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
|
func TestEndpoint_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
endpoint := Endpoint{
|
endpoint := Endpoint{
|
||||||
|
|||||||
Reference in New Issue
Block a user