Skip to content

Commit

Permalink
Merge pull request #2 from coreruleset/add-unencoded-body-payload
Browse files Browse the repository at this point in the history
feat: add plain body payload
  • Loading branch information
theseion authored May 19, 2024
2 parents cc7cd98 + 18f5382 commit 945e558
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 26 deletions.
19 changes: 18 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -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())
}
}
78 changes: 60 additions & 18 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package server
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
Expand All @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -91,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
}
Expand All @@ -114,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
}
Expand All @@ -125,22 +137,30 @@ 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")
_, err = w.Write([]byte(err.Error()))
if err != nil {
log.Printf("Failed to write response body: %s", 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'", responseBody)
_, err = w.Write(responseBodyBytes)
if err != nil {
log.Printf("Failed to write response body: %s", err.Error())
}
log.Printf("Reflecting body '%s'", bodyString)
w.Write(bodyBytes)
}

func handleCapabilities(w http.ResponseWriter, r *http.Request) {
Expand All @@ -164,5 +184,27 @@ 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) {
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
}
37 changes: 35 additions & 2 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down

0 comments on commit 945e558

Please sign in to comment.