Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions command/v7/curl_command.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package v7

import (
"bytes"
"net/http"
"net/http/httputil"
"os"
"strings"

"code.cloudfoundry.org/cli/command/flag"
"code.cloudfoundry.org/cli/command/translatableerror"
Expand Down Expand Up @@ -39,9 +42,9 @@ func (cmd CurlCommand) Execute(args []string) error {
}

var bytesToWrite []byte

if cmd.IncludeResponseHeaders {
headerBytes, _ := httputil.DumpResponse(httpResponse, false)
var headerBytes []byte
if cmd.IncludeResponseHeaders && httpResponse != nil {
headerBytes, _ = httputil.DumpResponse(httpResponse, false)
bytesToWrite = append(bytesToWrite, headerBytes...)
}

Expand All @@ -54,9 +57,38 @@ func (cmd CurlCommand) Execute(args []string) error {
}

cmd.UI.DisplayOK()
return nil
}

// Check if the response contains binary data
if isBinary(httpResponse, responseBodyBytes) {
// For binary data, write response headers with string conversion
// and the response body without string conversion
if cmd.IncludeResponseHeaders {
cmd.UI.DisplayTextLiteral(string(headerBytes))
}
cmd.UI.GetOut().Write(responseBodyBytes)
} else {
cmd.UI.DisplayText(string(bytesToWrite))
}

return nil
}

// isBinary determines if the provided `data` is likely binary content.
// It first checks if the given `contentType` (e.g., from an HTTP header) is a known binary MIME type.
// If not, it then scans the `data` byte slice for the presence of null bytes (0x00),
// which are a strong heuristic for binary data.
// Returns `true` if identified as binary, `false` otherwise.
func isBinary(response *http.Response, data []byte) bool {
responseContextType := ""
if response != nil && response.Header != nil {
responseContextType = response.Header.Get("Content-Type")
}
if strings.Contains(responseContextType, "image/") ||
strings.Contains(responseContextType, "application/octet-stream") ||
strings.Contains(responseContextType, "application/pdf") {
return true
}
return bytes.ContainsRune(data, 0x00) // Check for null byte
}
84 changes: 84 additions & 0 deletions command/v7/curl_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,88 @@ var _ = Describe("curl Command", func() {
})
})

When("the response contains binary data", func() {
var binaryData []byte

BeforeEach(func() {
// Create binary data with null bytes (like a droplet file)
binaryData = []byte{0x50, 0x4B, 0x03, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00}
fakeActor.MakeCurlRequestReturns(binaryData, &http.Response{
Header: http.Header{
"Content-Type": []string{"application/octet-stream"},
},
}, nil)
})

It("writes binary data directly to stdout without string conversion", func() {
Expect(executeErr).NotTo(HaveOccurred())
Expect(testUI.Out).To(Say(string(binaryData)))
})

When("Content-Type is not a known binary MIME type", func() {
BeforeEach(func() {
// Create binary data with null bytes (like a droplet file)
binaryData = []byte{0x50, 0x4B, 0x03, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00}
fakeActor.MakeCurlRequestReturns(binaryData, &http.Response{
Header: http.Header{
"Content-Type": []string{"text/plain"},
},
}, nil)
})
It("inspects the response data and writes binary data directly to stdout without string conversion", func() {
Expect(executeErr).NotTo(HaveOccurred())
Expect(testUI.Out).To(Say(string(binaryData)))
})
})

When("include-response-headers flag is set", func() {
BeforeEach(func() {
cmd.IncludeResponseHeaders = true
})

It("writes headers as text and binary data separately", func() {
Expect(executeErr).NotTo(HaveOccurred())

// Check that headers are written as text (using DisplayTextLiteral)
Expect(testUI.Out).To(Say("Content-Type: application/octet-stream"))

// Check that binary data is preserved
Expect(testUI.Out).To(Say(string(binaryData)))
})
})

When("output file is specified", func() {
BeforeEach(func() {
outputFile, err := os.CreateTemp("", "binary-output")
Expect(err).NotTo(HaveOccurred())
cmd.OutputFile = flag.Path(outputFile.Name())
})

AfterEach(func() {
os.RemoveAll(string(cmd.OutputFile))
})

It("writes binary data to the file correctly", func() {
Expect(executeErr).NotTo(HaveOccurred())

fileContents, err := os.ReadFile(string(cmd.OutputFile))
Expect(err).ToNot(HaveOccurred())
Expect(fileContents).To(Equal(binaryData))
})
})
})

When("the response is empty", func() {
BeforeEach(func() {
fakeActor.MakeCurlRequestReturns([]byte{}, &http.Response{
Header: http.Header{},
}, nil)
})

It("handles empty response correctly", func() {
Expect(executeErr).NotTo(HaveOccurred())
Expect(testUI.Out).To(Say(""))
})
})

})
Loading