From e50810a7ebcf93eb8cbc9a32809501e962f59116 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 19 May 2024 08:09:21 +0200 Subject: [PATCH 1/2] feat: add plain body payload --- .github/workflows/test.yml | 19 +++++++++++++- README.md | 9 ++++--- server/server.go | 51 +++++++++++++++++++++++++++----------- server/server_test.go | 37 +++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0aa4fa2..2305fb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + fetch-depth: 1 - name: Set up Go uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 @@ -25,3 +25,20 @@ jobs: - name: Run Go Tests run: go test -coverprofile=coverage.out ./... + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + with: + fetch-depth: 1 + + - name: Set up Go + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: ^1.22 + cache: true + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest diff --git a/README.md b/README.md index 1a44b72..092d742 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,11 @@ endpoints: This endpoint responds according to the received specification. The specification is a JSON document with the following fields: - status [integer]: the status code to respond with - headers [map of header definitions]: the headers to respond with - body [base64-encoded string]: body of the response, base64-encoded - logMessage [string]: message to log for the request; useful for matching requests to tests + status [integer]: the status code to respond with + headers [map of header definitions]: the headers to respond with + body [string]: body of the response + encodedBody [base64-encoded string]: body of the response, base64-encoded; useful for complex payloads where escaping is difficult + logMessage [string]: message to log for the request; useful for matching requests to tests While this endpoint essentially allows for freeform responses, some restrictions apply: - responses with status code 1xx don't have a body; if you specify a body together with a 1xx diff --git a/server/server.go b/server/server.go index e06eb4d..70ca010 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package server import ( "encoding/base64" "encoding/json" + "errors" "fmt" "io" "log" @@ -13,10 +14,11 @@ import ( ) type reflectionSpec struct { - Status int `json:"status"` - Headers map[string]string `json:"headers"` - Body string `json:"body"` - LogMessage string `json:"logMessage"` + Status int `json:"status"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` + EncodedBody string `json:"encodedBody"` + LogMessage string `json:"logMessage"` } type capabilitiesSpec struct { @@ -54,7 +56,8 @@ const capabilitiesDescription = ` status [integer]: the status code to respond with headers [map of header definitions]: the headers to respond with - body [base64-encoded string]: body of the response, base64-encoded + body [string]: body of the response + encodedBody [base64-encoded string]: body of the response, base64-encoded; useful for complex payloads where escaping is difficult logMessage [string]: message to log for the request; useful for matching requests to tests While this endpoint essentially allows for freeform responses, some restrictions apply: @@ -125,22 +128,24 @@ func handleReflect(w http.ResponseWriter, r *http.Request) { log.Printf("Reflecting status '%d'", status) w.WriteHeader(status) - decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(spec.Body)) - bodyBytes, err := io.ReadAll(decoder) + responseBody, err := decodeBody(spec) if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Invalid base64 encoding of response body")) - log.Println("Invalid base64 encoding of response body") + w.Write([]byte(err.Error())) + log.Println(err.Error()) return + } + if responseBody == "" { + return } - bodyString := string(bodyBytes) - if len(bodyString) > 200 { - bodyString = bodyString[:min(len(bodyString), 200)] + "..." + responseBodyBytes := []byte(responseBody) + if len(responseBody) > 200 { + responseBody = responseBody[:min(len(responseBody), 200)] + "..." } - log.Printf("Reflecting body '%s'", bodyString) - w.Write(bodyBytes) + log.Printf("Reflecting body '%s'", responseBody) + w.Write(responseBodyBytes) } func handleCapabilities(w http.ResponseWriter, r *http.Request) { @@ -166,3 +171,21 @@ func handleCapabilities(w http.ResponseWriter, r *http.Request) { } w.Write(body) } + +func decodeBody(spec *reflectionSpec) (string, error) { + if spec.Body != "" { + return spec.Body, nil + } + + if spec.EncodedBody == "" { + return "", nil + } + + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(spec.EncodedBody)) + bodyBytes, err := io.ReadAll(decoder) + if err != nil { + return "", errors.New("invalid base64 encoding of response body") + + } + return string(bodyBytes), nil +} diff --git a/server/server_test.go b/server/server_test.go index dd0eaf7..31ee1c3 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -37,7 +37,40 @@ func (s *serverTestSuite) TestDefaultRequest() { s.Equal("0", response.Header["Content-Length"][0]) } -func (s *serverTestSuite) TestReflect() { +func (s *serverTestSuite) TestReflect_Body() { + server := httptest.NewServer((http.HandlerFunc)(handleReflect)) + s.T().Cleanup(server.Close) + + responseBody := "a dummy body \t \n\r\r\n\r\n" + spec := &reflectionSpec{ + Status: 202, + Headers: map[string]string{ + "header1": "value 1", + "header_2": "value :2", + }, + Body: responseBody, + } + body, err := json.Marshal(spec) + s.Require().NoError(err) + request, err := http.NewRequest("POST", server.URL+"/reflect", bytes.NewReader(body)) + s.Require().NoError(err) + client := http.Client{} + response, err := client.Do(request) + s.Require().NoError(err) + + s.Equal(spec.Status, response.StatusCode) + s.Len(response.Header, 5) + s.Contains(response.Header, "Header1") + s.Equal("value 1", response.Header["Header1"][0]) + s.Contains(response.Header, "Header_2") + s.Equal("value :2", response.Header["Header_2"][0]) + + reflectedBody, err := io.ReadAll(response.Body) + s.Require().NoError(err) + s.Equal(responseBody, string(reflectedBody)) +} + +func (s *serverTestSuite) TestReflect_EncodedBody() { server := httptest.NewServer((http.HandlerFunc)(handleReflect)) s.T().Cleanup(server.Close) @@ -49,7 +82,7 @@ func (s *serverTestSuite) TestReflect() { "header1": "value 1", "header_2": "value :2", }, - Body: responseBody, + EncodedBody: responseBody, } body, err := json.Marshal(spec) s.Require().NoError(err) From 18f538202281eaff2367e23449b1a46e73d14de3 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 19 May 2024 08:16:24 +0200 Subject: [PATCH 2/2] chore: fix lint issues --- main.go | 6 +++++- server/server.go | 31 +++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 2f6e356..d675d25 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,13 @@ package main import ( + "log" + "github.com/coreruleset/albedo/cmd" ) func main() { - cmd.Execute() + if err := cmd.Execute(); err != nil { + log.Fatal(err.Error()) + } } diff --git a/server/server.go b/server/server.go index 70ca010..dfd18cf 100644 --- a/server/server.go +++ b/server/server.go @@ -94,14 +94,20 @@ func handleReflect(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Failed to parse request body")) + _, err = w.Write([]byte("Failed to parse request body")) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } log.Println("Failed to parse request body") return } spec := &reflectionSpec{} if err = json.Unmarshal(body, spec); err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Invalid JSON in request body")) + _, err = w.Write([]byte("Invalid JSON in request body")) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } log.Println("Invalid JSON in request body") return } @@ -117,7 +123,10 @@ func handleReflect(w http.ResponseWriter, r *http.Request) { if spec.Status > 0 && spec.Status < 100 || spec.Status >= 600 { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Invalid status code: %d", spec.Status))) + _, err = w.Write([]byte(fmt.Sprintf("Invalid status code: %d", spec.Status))) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } log.Printf("Invalid status code: %d", spec.Status) return } @@ -131,7 +140,10 @@ func handleReflect(w http.ResponseWriter, r *http.Request) { responseBody, err := decodeBody(spec) if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + _, err = w.Write([]byte(err.Error())) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } log.Println(err.Error()) return } @@ -145,7 +157,10 @@ func handleReflect(w http.ResponseWriter, r *http.Request) { responseBody = responseBody[:min(len(responseBody), 200)] + "..." } log.Printf("Reflecting body '%s'", responseBody) - w.Write(responseBodyBytes) + _, err = w.Write(responseBodyBytes) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } } func handleCapabilities(w http.ResponseWriter, r *http.Request) { @@ -169,7 +184,11 @@ func handleCapabilities(w http.ResponseWriter, r *http.Request) { if err != nil { log.Fatal("Failed to marshal capabilities") } - w.Write(body) + + _, err = w.Write(body) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } } func decodeBody(spec *reflectionSpec) (string, error) {