diff --git a/Dockerfile b/Dockerfile index d2ed739..b4d8e2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,18 +15,10 @@ FROM alpine:3 AS stage RUN apk update && apk add --no-cache ca-certificates -# Create non-root user -# RUN adduser -D -g '' appuser - WORKDIR /app COPY --from=builder /smaila ./ -# Optional: Copy static files, configs, etc. (but not .env) -# COPY static/ ./static/ - -# USER appuser - EXPOSE 8080 ENTRYPOINT ["./smaila"] diff --git a/handler/setup.go b/handler/setup.go index 47e44e8..bf90a3f 100644 --- a/handler/setup.go +++ b/handler/setup.go @@ -3,14 +3,24 @@ package handler import ( "fmt" "io" + "log/slog" + "mime/multipart" "net/http" + "os" + "path/filepath" "strings" "github.com/CactusBros/smaila/config" + _ "github.com/CactusBros/smaila/docs" "github.com/CactusBros/smaila/internal/mail" "github.com/gofiber/fiber/v2" "github.com/gofiber/swagger" - _ "github.com/CactusBros/smaila/docs" +) + +const ( + KiB = 1024 << (iota * 10) + MiB + GiB ) // Run initial routes and serve HTTP requests. @@ -61,26 +71,18 @@ func newMailHandler(cfg config.SMTPConfig) fiber.Handler { return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form") } - var attachments []mail.Attachment + var attachments []string if form != nil { files := form.File["attachments"] for _, fileHeader := range files { - file, err := fileHeader.Open() - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, "Failed to open attachment") + if fileHeader.Size > 25*MiB { + return fiber.NewError(fiber.StatusBadRequest, "attachment must be less than 25MiB") } - defer file.Close() - - content, err := io.ReadAll(file) + filePath, err := SaveFileTemp(fileHeader) if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, "Failed to read attachment") + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } - - attachments = append(attachments, mail.Attachment{ - Filename: fileHeader.Filename, - ContentType: fileHeader.Header.Get("Content-Type"), - Content: content, - }) + attachments = append(attachments, filePath) } } @@ -100,6 +102,13 @@ func newMailHandler(cfg config.SMTPConfig) fiber.Handler { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } + // Clean temp files + for _, file := range attachments { + if err = os.RemoveAll(filepath.Dir(file)); err != nil { + slog.Error("failed to remove temp file", "file path", file, "error", err) + } + } + return c.SendStatus(fiber.StatusOK) } } @@ -114,3 +123,27 @@ func filterEmpty(input []string) []string { } return result } + +func SaveFileTemp(header *multipart.FileHeader) (path string, err error) { + file, err := header.Open() + if err != nil { + return "", fmt.Errorf("failed to open attachment: %v", header.Filename) + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return "", fmt.Errorf("failed to read attachment: %v", header.Filename) + } + + dir, err := os.MkdirTemp(os.TempDir(), "*") + if err != nil { + return "", fmt.Errorf("failed to make temp dir") + } + + path = filepath.Join(dir, header.Filename) + if err = os.WriteFile(path, data, 0644); err != nil { + return "", fmt.Errorf("failed to save attachment: %v", header.Filename) + } + return +} diff --git a/internal/mail/mail.go b/internal/mail/mail.go index 872c025..28875e1 100644 --- a/internal/mail/mail.go +++ b/internal/mail/mail.go @@ -6,13 +6,13 @@ import ( ) type Message struct { - To []string // primary recipients - Cc []string // carbon copy - Bcc []string // blind carbon copy - Subject string // email subject - Body string // main message body (usually plain text or HTML) - IsHTML bool // true if Body is HTML - Attachments []Attachment // optional file attachments + To []string // primary recipients + Cc []string // carbon copy + Bcc []string // blind carbon copy + Subject string // email subject + Body string // main message body (usually plain text or HTML) + IsHTML bool // true if Body is HTML + Attachments []string // optional path to file attachments } type Attachment struct { @@ -42,8 +42,8 @@ func Send(cfg config.SMTPConfig, msg Message) error { } // Attach files - for _, a := range msg.Attachments { - m.Attach(a.Filename) + for _, att := range msg.Attachments { + m.Attach(att) } // Send email