Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
52 changes: 30 additions & 22 deletions client/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,43 +269,51 @@ func parserRequestBodyFile(req *Request) error {

defer fileBufPool.Put(fileBuf)

for i, v := range req.files {
if v.name == "" && v.path == "" {
for i, f := range req.files {
if f.name == "" && f.path == "" {
return ErrFileNoName
}

// Set the file name if not provided.
if v.name == "" && v.path != "" {
v.path = filepath.Clean(v.path)
v.name = filepath.Base(v.path)
if f.name == "" && f.path != "" {
f.path = filepath.Clean(f.path)
f.name = filepath.Base(f.path)
}

// Set the field name if not provided.
if v.fieldName == "" {
v.fieldName = "file" + strconv.Itoa(i+1)
if f.fieldName == "" {
f.fieldName = "file" + strconv.Itoa(i+1)
}

// If reader is not set, open the file.
if v.reader == nil {
v.reader, err = os.Open(v.path)
if err != nil {
return fmt.Errorf("open file error: %w", err)
}
if err := addFormFile(mw, f, fileBuf); err != nil {
return err
}
}

return nil
}

// Create form file and copy the content.
w, err := mw.CreateFormFile(v.fieldName, v.name)
func addFormFile(mw *multipart.Writer, f *File, fileBuf *[]byte) error {
// If reader is not set, open the file.
if f.reader == nil {
var err error
f.reader, err = os.Open(f.path)
if err != nil {
return fmt.Errorf("create file error: %w", err)
return fmt.Errorf("open file error: %w", err)
}
}

if _, err := io.CopyBuffer(w, v.reader, *fileBuf); err != nil {
return fmt.Errorf("failed to copy file data: %w", err)
}
// Ensure the file reader is always closed after copying.
defer f.reader.Close() //nolint:errcheck // not needed
Comment thread
ReneWerner87 marked this conversation as resolved.

if err := v.reader.Close(); err != nil {
return fmt.Errorf("close file error: %w", err)
}
// Create form file and copy the content.
w, err := mw.CreateFormFile(f.fieldName, f.name)
if err != nil {
return fmt.Errorf("create file error: %w", err)
}

if _, err := io.CopyBuffer(w, f.reader, *fileBuf); err != nil {
return fmt.Errorf("failed to copy file data: %w", err)
}

return nil
Comment on lines +296 to 319

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

While using defer is a great way to ensure the file is closed, ignoring the error from Close() is a regression from the previous behavior and is generally not recommended. The Close method can return important errors, for example, if there was an issue with the underlying file system. The nolint:errcheck comment states it's not needed, but the previous implementation did check this error.

I suggest modifying the function to handle the error from Close() while still benefiting from defer. This can be achieved using a named return value.

func addFormFile(mw *multipart.Writer, f *File, fileBuf *[]byte) (err error) {
	// If reader is not set, open the file.
	if f.reader == nil {
		f.reader, err = os.Open(f.path)
		if err != nil {
			return fmt.Errorf("open file error: %w", err)
		}
	}

	// Ensure the file reader is always closed after copying.
	defer func() {
		if closeErr := f.reader.Close(); closeErr != nil && err == nil {
			err = fmt.Errorf("close file error: %w", closeErr)
		}
	}()

	// Create form file and copy the content.
	var w io.Writer
	w, err = mw.CreateFormFile(f.fieldName, f.name)
	if err != nil {
		return fmt.Errorf("create file error: %w", err)
	}

	if _, err = io.CopyBuffer(w, f.reader, *fileBuf); err != nil {
		return fmt.Errorf("failed to copy file data: %w", err)
	}

	return nil
}

Expand Down
23 changes: 23 additions & 0 deletions client/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"net"
"net/url"
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -560,6 +561,28 @@ func Test_Parser_Request_Body(t *testing.T) {
require.Contains(t, string(req.RawRequest.Body()), "world")
})

t.Run("file body open error", func(t *testing.T) {
t.Parallel()
client := New()
missingPath := filepath.Join(t.TempDir(), "missing.txt")

req := AcquireRequest().AddFile(missingPath)

err := parserRequestBody(client, req)
require.ErrorContains(t, err, "open file error")
})

t.Run("file body missing path and name", func(t *testing.T) {
t.Parallel()
client := New()
file := AcquireFile(SetFileReader(io.NopCloser(strings.NewReader("world"))))

req := AcquireRequest().AddFiles(file)

err := parserRequestBody(client, req)
require.ErrorIs(t, err, ErrFileNoName)
})

t.Run("file and form data", func(t *testing.T) {
t.Parallel()
client := New()
Expand Down
Loading