@@ -262,15 +262,11 @@ func parseMIMEParts(hs textproto.MIMEHeader, b io.Reader) ([]*part, error) {
262262// Required parameters include an io.Reader, the desired filename for the attachment, and the Content-Type
263263// The function will return the created Attachment for reference, as well as nil for the error, if successful.
264264func (e * Email ) Attach (r io.Reader , filename string , c string ) (a * Attachment , err error ) {
265- var buffer bytes.Buffer
266- if _ , err = io .Copy (& buffer , r ); err != nil {
267- return
268- }
269265 at := & Attachment {
270266 Filename : filename ,
271267 ContentType : c ,
272268 Header : textproto.MIMEHeader {},
273- Content : buffer . Bytes () ,
269+ Content : r ,
274270 }
275271 e .Attachments = append (e .Attachments , at )
276272 return at , nil
@@ -472,7 +468,9 @@ func (e *Email) Bytes() ([]byte, error) {
472468 return nil , err
473469 }
474470 // Write the base64Wrapped content to the part
475- base64Wrap (ap , a .Content )
471+ if err = streamBase64Wrap (ap , a .Content ); err != nil {
472+ return nil , err
473+ }
476474 }
477475
478476 if isMixed || isAlternative {
@@ -494,7 +492,9 @@ func (e *Email) Bytes() ([]byte, error) {
494492 return nil , err
495493 }
496494 // Write the base64Wrapped content to the part
497- base64Wrap (ap , a .Content )
495+ if err = streamBase64Wrap (ap , a .Content ); err != nil {
496+ return nil , err
497+ }
498498 }
499499 if isMixed || isAlternative || isRelated {
500500 if err := w .Close (); err != nil {
@@ -702,7 +702,7 @@ type Attachment struct {
702702 Filename string
703703 ContentType string
704704 Header textproto.MIMEHeader
705- Content [] byte
705+ Content io. Reader
706706 HTMLRelated bool
707707}
708708
@@ -751,6 +751,46 @@ func base64Wrap(w io.Writer, b []byte) {
751751 }
752752}
753753
754+ // streamBase64Wrap encodes the attachment content, provided as stream, and wraps it according to RFC 2045 standards (every 76 chars)
755+ // The output is then written to the specified io.Writer
756+ func streamBase64Wrap (w io.Writer , r io.Reader ) error {
757+ // 57 raw bytes per 76-byte base64 line.
758+ const maxRaw = 57
759+ // Buffer for each line, including trailing CRLF.
760+ wrBuffer := make ([]byte , MaxLineLength + len ("\r \n " ))
761+ copy (wrBuffer [MaxLineLength :], "\r \n " )
762+ rdBuffer := make ([]byte , maxRaw )
763+ var lastRead int
764+ var err error
765+ cr := NewChunkedReader (r , maxRaw )
766+ for {
767+ //Reading next 57-bytes chunk.
768+ if lastRead , err = cr .Read (rdBuffer ); err != nil && ! errors .Is (err , io .EOF ) {
769+ return err
770+ }
771+ //In case of last chunk jump to last chunk processing
772+ if errors .Is (err , io .EOF ) {
773+ break
774+ }
775+ //normal chunk processing. It's len=maxRaw exactly
776+ base64 .StdEncoding .Encode (wrBuffer , rdBuffer )
777+ if _ , err := w .Write (wrBuffer ); err != nil {
778+ return err
779+ }
780+
781+ }
782+ //last chunk processing. It can be 0<=size<=maxRaw
783+ if lastRead > 0 {
784+ out := wrBuffer [:base64 .StdEncoding .EncodedLen (lastRead )]
785+ base64 .StdEncoding .Encode (out , rdBuffer [:lastRead ])
786+ out = append (out , "\r \n " ... )
787+ if _ , err := w .Write (out ); err != nil {
788+ return err
789+ }
790+ }
791+ return nil
792+ }
793+
754794// headerToBytes renders "header" to "buff". If there are multiple values for a
755795// field, multiple "Field: value\r\n" lines will be emitted.
756796func headerToBytes (buff io.Writer , header textproto.MIMEHeader ) {
0 commit comments