Skip to content
Open
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
14 changes: 9 additions & 5 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,9 +517,13 @@

// SaveFileToStorage saves any multipart file to an external storage system.
func (c *DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error {
if fileheader == nil {
return ErrFileHeaderNil
}

file, err := fileheader.Open()
if err != nil {
return fmt.Errorf("failed to open: %w", err)
return fmt.Errorf("%w: %q: %v", ErrFileOpen, fileheader.Filename, err)

Check failure on line 526 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i think we better sanitize filename for security reasons

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The filename comes from the Client Request, so they already know it.

}
defer file.Close() //nolint:errcheck // not needed

Expand All @@ -529,25 +533,25 @@
}

if fileheader.Size > 0 && fileheader.Size > int64(maxUploadSize) {
return fmt.Errorf("failed to read: %w", fasthttp.ErrBodyTooLarge)
return fmt.Errorf("%w: %q: %v", ErrFileRead, fileheader.Filename, fasthttp.ErrBodyTooLarge)

Check failure on line 536 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}

buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

limitedReader := io.LimitReader(file, int64(maxUploadSize)+1)
if _, err = buf.ReadFrom(limitedReader); err != nil {
return fmt.Errorf("failed to read: %w", err)
return fmt.Errorf("%w: %q: %v", ErrFileRead, fileheader.Filename, err)

Check failure on line 544 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}

if buf.Len() > maxUploadSize {
return fmt.Errorf("failed to read: %w", fasthttp.ErrBodyTooLarge)
return fmt.Errorf("%w: %q: %v", ErrFileRead, fileheader.Filename, fasthttp.ErrBodyTooLarge)

Check failure on line 548 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}

data := append([]byte(nil), buf.Bytes()...)

if err := storage.SetWithContext(c.Context(), path, data, 0); err != nil {
return fmt.Errorf("failed to store: %w", err)
return fmt.Errorf("%w: %q to %q: %v", ErrFileStore, fileheader.Filename, path, err)

Check failure on line 554 in ctx.go

View workflow job for this annotation

GitHub Actions / lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}

return nil
Expand Down
36 changes: 36 additions & 0 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5105,6 +5105,42 @@ func (s *mockContextAwareStorage) Close() error {
return nil
}

func Test_Ctx_SaveFileToStorage_NilFileHeader(t *testing.T) {
t.Parallel()

app := New()
storage := memory.New()

ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)

err := ctx.SaveFileToStorage(nil, "test", storage)

require.Error(t, err)
require.ErrorIs(t, err, ErrFileHeaderNil)
}

func Test_Ctx_SaveFileToStorage_ErrorMessageContainsFilename(t *testing.T) {
t.Parallel()

app := New(Config{BodyLimit: 10}) // small limit to force error
storage := memory.New()

ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)

fileHeader := createMultipartFileHeader(
t,
"test-file.png",
bytes.Repeat([]byte{'a'}, 100), // bigger than limit
)

err := ctx.SaveFileToStorage(fileHeader, "test-path", storage)

require.Error(t, err)
require.Contains(t, err.Error(), "test-file.png")
}

// go test -run Test_Ctx_SaveFileToStorage_ContextPropagation
func Test_Ctx_SaveFileToStorage_ContextPropagation(t *testing.T) {
t.Parallel()
Expand Down
8 changes: 8 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,11 @@ type (
// UnsupportedValueError exposes json.UnsupportedValueError to describe unsupported values encountered during encoding.
UnsupportedValueError = json.UnsupportedValueError
)

// File errors
var (
ErrFileHeaderNil = errors.New("file: file header is nil")
ErrFileOpen = errors.New("file: failed to open file")
ErrFileRead = errors.New("file: failed to read file")
ErrFileStore = errors.New("file: failed to store file")
)
Loading