@@ -41,7 +41,7 @@ import (
4141 "github.com/robfig/cron/v3"
4242 "github.com/rs/xid"
4343 "github.com/sftpgo/sdk"
44- mail "github.com/xhit /go-simple- mail/v2 "
44+ "github.com/wneessen /go-mail"
4545
4646 "github.com/drakkan/sftpgo/v2/internal/dataprovider"
4747 "github.com/drakkan/sftpgo/v2/internal/logger"
@@ -563,45 +563,59 @@ func (p *EventParams) getCompressedDataRetentionReport() ([]byte, error) {
563563 return nil , errors .New ("no data retention report available" )
564564 }
565565 var b bytes.Buffer
566- wr := zip .NewWriter (& b )
566+ if _ , err := p .writeCompressedDataRetentionReports (& b ); err != nil {
567+ return nil , err
568+ }
569+ return b .Bytes (), nil
570+ }
571+
572+ func (p * EventParams ) writeCompressedDataRetentionReports (w io.Writer ) (int64 , error ) {
573+ var n int64
574+ wr := zip .NewWriter (w )
575+
567576 for _ , check := range p .retentionChecks {
568- if size := int64 (len (b .Bytes ())); size > maxAttachmentsSize {
569- eventManagerLog (logger .LevelError , "unable to get retention report, size too large: %s" , util .ByteCountIEC (size ))
570- return nil , fmt .Errorf ("unable to get retention report, size too large: %s" , util .ByteCountIEC (size ))
571- }
572577 data , err := getCSVRetentionReport (check .Results )
573578 if err != nil {
574- return nil , fmt .Errorf ("unable to get CSV report: %w" , err )
579+ return n , fmt .Errorf ("unable to get CSV report: %w" , err )
580+ }
581+ dataSize := int64 (len (data ))
582+ n += dataSize
583+ // we suppose a 3:1 compression ratio
584+ if n > (maxAttachmentsSize * 3 ) {
585+ eventManagerLog (logger .LevelError , "unable to get retention report, size too large: %s" ,
586+ util .ByteCountIEC (n ))
587+ return n , fmt .Errorf ("unable to get retention report, size too large: %s" , util .ByteCountIEC (n ))
575588 }
589+
576590 fh := & zip.FileHeader {
577591 Name : fmt .Sprintf ("%s-%s.csv" , check .ActionName , check .Username ),
578592 Method : zip .Deflate ,
579593 Modified : time .Now ().UTC (),
580594 }
581595 f , err := wr .CreateHeader (fh )
582596 if err != nil {
583- return nil , fmt .Errorf ("unable to create zip header for file %q: %w" , fh .Name , err )
597+ return n , fmt .Errorf ("unable to create zip header for file %q: %w" , fh .Name , err )
584598 }
585- _ , err = io .Copy (f , bytes .NewBuffer (data ))
599+ _ , err = io .CopyN (f , bytes .NewBuffer (data ), dataSize )
586600 if err != nil {
587- return nil , fmt .Errorf ("unable to write content to zip file %q: %w" , fh .Name , err )
601+ return n , fmt .Errorf ("unable to write content to zip file %q: %w" , fh .Name , err )
588602 }
589603 }
590604 if err := wr .Close (); err != nil {
591- return nil , fmt .Errorf ("unable to close zip writer: %w" , err )
605+ return n , fmt .Errorf ("unable to close zip writer: %w" , err )
592606 }
593- return b . Bytes () , nil
607+ return n , nil
594608}
595609
596- func (p * EventParams ) getRetentionReportsAsMailAttachment () (mail.File , error ) {
597- var result mail.File
598- data , err := p .getCompressedDataRetentionReport ()
599- if err != nil {
600- return result , err
610+ func (p * EventParams ) getRetentionReportsAsMailAttachment () (* mail.File , error ) {
611+ if len (p .retentionChecks ) == 0 {
612+ return nil , errors .New ("no data retention report available" )
601613 }
602- result .Name = "retention-reports.zip"
603- result .Data = data
604- return result , nil
614+ return & mail.File {
615+ Name : "retention-reports.zip" ,
616+ Header : make (map [string ][]string ),
617+ Writer : p .writeCompressedDataRetentionReports ,
618+ }, nil
605619}
606620
607621func (p * EventParams ) getStringReplacements (addObjectData bool ) []string {
@@ -905,34 +919,24 @@ func writeFileContent(conn *BaseConnection, virtualPath string, w io.Writer) err
905919 return err
906920}
907921
908- func getFileContent (conn * BaseConnection , virtualPath string , expectedSize int ) ([]byte , error ) {
909- reader , cancelFn , err := getFileReader (conn , virtualPath )
910- if err != nil {
911- return nil , err
912- }
922+ func getFileContentFn (conn * BaseConnection , virtualPath string , size int64 ) func (w io.Writer ) (int64 , error ) {
923+ return func (w io.Writer ) (int64 , error ) {
924+ reader , cancelFn , err := getFileReader (conn , virtualPath )
925+ if err != nil {
926+ return 0 , err
927+ }
913928
914- defer cancelFn ()
915- defer reader .Close ()
929+ defer cancelFn ()
930+ defer reader .Close ()
916931
917- data := make ([]byte , expectedSize )
918- _ , err = io .ReadFull (reader , data )
919- return data , err
932+ return io .CopyN (w , reader , size )
933+ }
920934}
921935
922- func getMailAttachments (user dataprovider.User , attachments []string , replacer * strings.Replacer ) ([]mail.File , error ) {
923- var files []mail.File
924- user , err := getUserForEventAction (user )
925- if err != nil {
926- return nil , err
927- }
928- connectionID := fmt .Sprintf ("%s_%s" , protocolEventAction , xid .New ().String ())
929- err = user .CheckFsRoot (connectionID )
930- defer user .CloseFs () //nolint:errcheck
931- if err != nil {
932- return nil , fmt .Errorf ("error getting email attachments, unable to check root fs for user %q: %w" , user .Username , err )
933- }
934- conn := NewBaseConnection (connectionID , protocolEventAction , "" , "" , user )
936+ func getMailAttachments (conn * BaseConnection , attachments []string , replacer * strings.Replacer ) ([]* mail.File , error ) {
937+ var files []* mail.File
935938 totalSize := int64 (0 )
939+
936940 for _ , virtualPath := range replacePathsPlaceholders (attachments , replacer ) {
937941 info , err := conn .DoStat (virtualPath , 0 , false )
938942 if err != nil {
@@ -945,13 +949,10 @@ func getMailAttachments(user dataprovider.User, attachments []string, replacer *
945949 if totalSize > maxAttachmentsSize {
946950 return nil , fmt .Errorf ("unable to send files as attachment, size too large: %s" , util .ByteCountIEC (totalSize ))
947951 }
948- data , err := getFileContent (conn , virtualPath , int (info .Size ()))
949- if err != nil {
950- return nil , fmt .Errorf ("unable to get content for file %q, user %q: %w" , virtualPath , conn .User .Username , err )
951- }
952- files = append (files , mail.File {
953- Name : path .Base (virtualPath ),
954- Data : data ,
952+ files = append (files , & mail.File {
953+ Name : path .Base (virtualPath ),
954+ Header : make (map [string ][]string ),
955+ Writer : getFileContentFn (conn , virtualPath , info .Size ()),
955956 })
956957 }
957958 return files , nil
@@ -1265,7 +1266,7 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event
12651266 body := replaceWithReplacer (c .Body , replacer )
12661267 subject := replaceWithReplacer (c .Subject , replacer )
12671268 startTime := time .Now ()
1268- var files []mail.File
1269+ var files []* mail.File
12691270 fileAttachments := make ([]string , 0 , len (c .Attachments ))
12701271 for _ , attachment := range c .Attachments {
12711272 if attachment == dataprovider .RetentionReportPlaceHolder {
@@ -1283,7 +1284,18 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event
12831284 if err != nil {
12841285 return err
12851286 }
1286- res , err := getMailAttachments (user , fileAttachments , replacer )
1287+ user , err = getUserForEventAction (user )
1288+ if err != nil {
1289+ return err
1290+ }
1291+ connectionID := fmt .Sprintf ("%s_%s" , protocolEventAction , xid .New ().String ())
1292+ err = user .CheckFsRoot (connectionID )
1293+ defer user .CloseFs () //nolint:errcheck
1294+ if err != nil {
1295+ return fmt .Errorf ("error getting email attachments, unable to check root fs for user %q: %w" , user .Username , err )
1296+ }
1297+ conn := NewBaseConnection (connectionID , protocolEventAction , "" , "" , user )
1298+ res , err := getMailAttachments (conn , fileAttachments , replacer )
12871299 if err != nil {
12881300 return err
12891301 }
0 commit comments