Skip to content

Commit

Permalink
Raw io.Reader is now io.ReadCloser (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
robsignorelli authored May 14, 2021
1 parent 40c4f8c commit eea229b
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 34 deletions.
14 changes: 6 additions & 8 deletions respond.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Redirector interface {
// we will send back to the caller.
type ContentReader interface {
// Content supplies the raw data that should be sent to the caller when responding.
Content() io.Reader
Content() io.ReadCloser
}

// ContentTypeReader provides details about a file-based response to indicate what we should
Expand Down Expand Up @@ -388,20 +388,17 @@ func writeJSON(res http.ResponseWriter, status int, value interface{}) {
// writeRaw accepts a reader containing the bytes of some file or raw set of data that the
// user wants to write to the caller.
func writeRaw(res http.ResponseWriter, status int, value ContentReader) {
content := value.Content()
if content == nil {
reader := value.Content()
if reader == nil {
res.WriteHeader(status)
return
}

if contentCloser, ok := content.(io.Closer); ok {
defer func() { _ = contentCloser.Close() }()
}

defer func() { _ = reader.Close() }()
res.Header().Set("Content-Type", rawContentType(value))
res.Header().Set("Content-Disposition", rawContentDisposition(value))
res.WriteHeader(status)
_, _ = io.Copy(res, content)
_, _ = io.Copy(res, reader)
}

// rawContentType assumes "application/octet-stream" unless the return value implements
Expand Down Expand Up @@ -436,6 +433,7 @@ func rawContentDisposition(value ContentReader) string {
return "inline"
}

fileName = strings.ReplaceAll(fileName, `"`, `\"`)
return `attachment; filename="` + fileName + `"`
}

Expand Down
84 changes: 58 additions & 26 deletions respond_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ func (suite RespondSuite) TestRaw_empty() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("")
result.reader = newRawString("")

respond.To(w, req).Ok(result)
suite.assertStatus(w, 200)
Expand All @@ -474,7 +474,7 @@ func (suite RespondSuite) TestRaw_status() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("")
result.reader = newRawString("")

respond.To(w, req).Created(result)
suite.assertStatus(w, 201)
Expand All @@ -493,7 +493,7 @@ func (suite RespondSuite) TestRaw_text() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("hello world")
result.reader = newRawString("hello world")

respond.To(w, req).Ok(result)
suite.assertStatus(w, 200)
Expand All @@ -512,7 +512,7 @@ func (suite RespondSuite) TestRaw_binary() {
req := newRequest()

result := R{}
result.reader = bytes.NewBuffer([]byte{0x42, 0x43, 0x44})
result.reader = newRawBytes([]byte{0x42, 0x43, 0x44})

respond.To(w, req).Ok(result)
suite.assertStatus(w, 200)
Expand All @@ -532,7 +532,7 @@ func (suite RespondSuite) TestRaw_contentType() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("Do you see what happens, Larry?")
result.reader = newRawString("Do you see what happens, Larry?")
result.contentType = "text/plain"

respond.To(w, req).Ok(result)
Expand All @@ -553,7 +553,7 @@ func (suite RespondSuite) TestRaw_contentTypeEmpty() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("Do you see what happens, Larry?")
result.reader = newRawString("Do you see what happens, Larry?")

respond.To(w, req).Ok(result)
suite.assertStatus(w, 200)
Expand All @@ -573,7 +573,7 @@ func (suite RespondSuite) TestRaw_contentDisposition() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("Do you see what happens, Larry?")
result.reader = newRawString("Do you see what happens, Larry?")
result.fileName = "stranger.log"

respond.To(w, req).Ok(result)
Expand All @@ -594,7 +594,7 @@ func (suite RespondSuite) TestRaw_contentDispositionEmpty() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("Do you see what happens, Larry?")
result.reader = newRawString("Do you see what happens, Larry?")

respond.To(w, req).Ok(result)
suite.assertStatus(w, 200)
Expand All @@ -603,6 +603,24 @@ func (suite RespondSuite) TestRaw_contentDispositionEmpty() {
suite.assertHeader(w, "Content-Disposition", "inline")
}

// Should allow you to specify the Content-Disposition file name even if it has quotes in it.
func (suite RespondSuite) TestRaw_contentDispositionQuoted() {
type R struct {
rawContentReader
rawContentFileName
}

w := newResponseWriter()
req := newRequest()

result := R{}
result.reader = newRawString("")
result.fileName = `the-"stranger".log`

respond.To(w, req).Ok(result)
suite.assertHeader(w, "Content-Disposition", `attachment; filename="the-\"stranger\".log"`)
}

// Should allow you to specify the Content-Type AND Content-Disposition by implementing
// all of the necessary types.
func (suite RespondSuite) TestRaw_contentTypeAndDisposition() {
Expand All @@ -616,7 +634,7 @@ func (suite RespondSuite) TestRaw_contentTypeAndDisposition() {
req := newRequest()

result := R{}
result.reader = bytes.NewBufferString("Do you see what happens, Larry?")
result.reader = newRawString("Do you see what happens, Larry?")
result.contentType = "text/plain"
result.fileName = "stranger.log"

Expand All @@ -636,7 +654,7 @@ func (suite RespondSuite) TestRaw_close() {
w := newResponseWriter()
req := newRequest()

closer := readCloser{reader: bytes.NewBufferString("Do you see what happens, Larry?")}
closer := rawContent{reader: bytes.NewBufferString("Do you see what happens, Larry?")}
result := R{}
result.reader = &closer

Expand Down Expand Up @@ -1321,27 +1339,13 @@ func (r fakeRedirector) Redirect() string {
}

type rawContentReader struct {
reader io.Reader
reader io.ReadCloser
}

func (r rawContentReader) Content() io.Reader {
func (r rawContentReader) Content() io.ReadCloser {
return r.reader
}

type readCloser struct {
reader io.Reader
closed bool
}

func (r *readCloser) Read(buf []byte) (int, error) {
return r.reader.Read(buf)
}

func (r *readCloser) Close() error {
r.closed = true
return nil
}

type rawContentType struct {
contentType string
}
Expand All @@ -1357,3 +1361,31 @@ type rawContentFileName struct {
func (r rawContentFileName) ContentFileName() string {
return r.fileName
}

type rawContent struct {
reader io.Reader
closed bool
}

func (r *rawContent) Read(buf []byte) (int, error) {
return r.reader.Read(buf)
}

func (r *rawContent) Close() error {
r.closed = true
return nil
}

func newRawBytes(content []byte) *rawContent {
return &rawContent{
reader: bytes.NewBuffer(content),
closed: false,
}
}

func newRawString(content string) *rawContent {
return &rawContent{
reader: bytes.NewBufferString(content),
closed: false,
}
}

0 comments on commit eea229b

Please sign in to comment.