2
0
flashmob 6 жил өмнө
parent
commit
3184a6d635

+ 3 - 3
backends/s_debug.go

@@ -3,7 +3,6 @@ package backends
 import (
 	"fmt"
 	"github.com/flashmob/go-guerrilla/mail"
-	"strings"
 )
 
 func init() {
@@ -23,8 +22,9 @@ func StreamDebug() *StreamDecorator {
 			}
 			return StreamProcessWith(func(p []byte) (int, error) {
 				str := string(p)
-				str = strings.Replace(str, "\n", "<LF>\n", -1)
-				fmt.Println(str)
+				//str = strings.Replace(str, "\n", "<LF>\n", -1)
+				//fmt.Println(str)
+				fmt.Print(str)
 				Log().WithField("p", string(p)).Info("Debug stream")
 				return sp.Write(p)
 			})

+ 1 - 1
backends/s_headers_parser.go

@@ -52,7 +52,7 @@ func StreamHeadersParser() *StreamDecorator {
 			return StreamProcessWith(func(p []byte) (int, error) {
 				switch state {
 				case stateHeaderScanning:
-					if mimeParts, ok := envelope.Values["MimeParts"].(*[]*mime.Part); ok {
+					if mimeParts, ok := envelope.Values["MimeParts"].(*mime.Parts); ok {
 						// copy the the headers of the first mime-part to envelope.Header
 						// then call envelope.ParseHeaders()
 						if len(*mimeParts) > 0 {

+ 1 - 12
backends/s_mime.go

@@ -15,7 +15,7 @@ import (
 // --------------:-------------------------------------------------------------------
 // Input         :
 // ----------------------------------------------------------------------------------
-// Output        : MimeParts (of type *[]*mime.Part) stored in the envelope.Values map
+// Output        : MimeParts (of type *mime.Parts) stored in the envelope.Values map
 // ----------------------------------------------------------------------------------
 
 func init() {
@@ -53,17 +53,6 @@ func StreamMimeAnalyzer() *StreamDecorator {
 			}
 
 			sd.Close = func() error {
-				/*
-					defer func() {
-						// todo remove, for debugging only
-						if parts, ok := envelope.Values["MimeParts"].(*[]*mime.Part); ok {
-							for _, v := range *parts {
-								fmt.Println(v.Node + " " + strconv.Itoa(int(v.StartingPos)) + " " + strconv.Itoa(int(v.StartingPosBody)) + " " + strconv.Itoa(int(v.EndingPosBody)))
-							}
-						}
-					}()
-
-				*/
 
 				if parseErr == nil {
 					_ = parser.Close()

+ 252 - 0
backends/s_transformer.go

@@ -0,0 +1,252 @@
+package backends
+
+import (
+	"bytes"
+	"github.com/flashmob/go-guerrilla/chunk/transfer"
+	"github.com/flashmob/go-guerrilla/mail"
+	"github.com/flashmob/go-guerrilla/mail/mime"
+	"io"
+	"regexp"
+	"sync"
+)
+
+// ----------------------------------------------------------------------------------
+// Processor Name: transformer
+// ----------------------------------------------------------------------------------
+// Description   : Transforms from base64 / q-printable to 8bit and converts charset to utf-8
+// ----------------------------------------------------------------------------------
+// Config Options:
+// --------------:-------------------------------------------------------------------
+// Input         :
+// ----------------------------------------------------------------------------------
+// Output        : 8bit mime message
+// ----------------------------------------------------------------------------------
+
+func init() {
+	Streamers["transformer"] = func() *StreamDecorator {
+		return Transformer()
+	}
+}
+
+type TransformerConfig struct {
+	// we can add any config here
+
+}
+
+type Transform struct {
+	sp                  io.Writer
+	isBody              bool // the next bytes to be sent are body?
+	buf                 bytes.Buffer
+	current             *mime.Part
+	decoder             io.Reader
+	pr                  *io.PipeReader
+	pw                  *io.PipeWriter
+	parts               *mime.Parts
+	partsCachedOriginal *mime.Parts
+	envelope            *mail.Envelope
+
+	state   int
+	matched int
+
+	parser *mime.Parser
+}
+
+// cache the original parts from envelope.Values
+// and point them to our parts
+func (t *Transform) swap() *mime.Parts {
+
+	if parts, ok := t.envelope.Values["MimeParts"].(*mime.Parts); ok {
+		t.partsCachedOriginal = parts
+		parts = &t.parser.Parts
+		return parts
+	}
+	return nil
+
+}
+
+// point the parts from envelope.Values back to the original ones
+func (t *Transform) unswap() {
+	if parts, ok := t.envelope.Values["MimeParts"].(*mime.Parts); ok {
+		_ = parts
+		parts = t.partsCachedOriginal
+	}
+}
+
+func (t *Transform) ReWrite(b []byte) (count int, err error) {
+
+	if !t.isBody {
+		count = len(b)
+		if i, err := io.Copy(&t.buf, bytes.NewReader(b)); err != nil {
+			return int(i), err
+		}
+		// *
+		for {
+			line, rErr := t.buf.ReadBytes('\n')
+			if rErr == nil {
+				if bytes.Contains(line, []byte("Content-Transfer-Encoding: base64")) {
+					line = bytes.Replace(line, []byte("base64"), []byte("8bit"), 1)
+					t.current.TransferEncoding = "8bit"
+					t.current.Charset = "utf8"
+				} else if bytes.Contains(line, []byte("charset=")) {
+					rx := regexp.MustCompile("charset=\".+?\"")
+					_ = rx
+					//line = rx.ReplaceAll(line, []byte("charset=\"utf8\"") )
+				}
+
+			} else {
+				break
+			}
+		}
+		// */
+
+	} else {
+
+		// do body decode here
+		t.pr, t.pw = io.Pipe()
+		if t.decoder == nil {
+			t.buf.Reset()
+			// the decoder will be reading from an underlying pipe
+			t.decoder, err = transfer.NewBodyDecoder(t.pr, transfer.Base64, "iso-8859-1")
+		}
+
+		wg := sync.WaitGroup{}
+		wg.Add(1)
+
+		go func() {
+			// stream our slice to the pipe
+			defer wg.Done()
+			_, pRrr := io.Copy(t.pw, bytes.NewReader(b))
+			if pRrr != nil {
+				_ = t.pw.CloseWithError(err)
+				return
+			}
+			_ = t.pw.Close()
+		}()
+		// do the decoding
+		var i int64
+		i, err = io.Copy(t.parser, t.decoder)
+		// wait for the pipe to finish
+		_ = i
+		wg.Wait()
+		_ = t.pr.Close()
+		count = len(b)
+	}
+	return count, err
+}
+
+func (t *Transform) Reset() {
+	t.decoder = nil
+
+}
+
+func Transformer() *StreamDecorator {
+
+	var conf *TransformerConfig
+
+	Svc.AddInitializer(InitializeWith(func(backendConfig BackendConfig) error {
+		configType := BaseConfig(&HeaderConfig{})
+		bcfg, err := Svc.ExtractConfig(backendConfig, configType)
+		if err != nil {
+			return err
+		}
+		conf = bcfg.(*TransformerConfig)
+		_ = conf
+		return nil
+	}))
+
+	var msgPos uint
+	var progress int
+	reWriter := Transform{}
+
+	sd := &StreamDecorator{}
+	sd.Decorate =
+
+		func(sp StreamProcessor, a ...interface{}) StreamProcessor {
+			var envelope *mail.Envelope
+			if reWriter.sp == nil {
+				reWriter.sp = sp
+			}
+
+			sd.Open = func(e *mail.Envelope) error {
+				//charset_pos = 0
+				envelope = e
+				_ = envelope
+				if reWriter.parser == nil {
+					reWriter.parser = mime.NewMimeParserWriter(sp)
+					reWriter.parser.Open()
+				}
+				reWriter.envelope = envelope
+				return nil
+			}
+
+			return StreamProcessWith(func(p []byte) (count int, err error) {
+				var total int
+				if parts, ok := envelope.Values["MimeParts"].(*mime.Parts); ok && len(*parts) > 0 {
+
+					// we are going to change envelope.Values["MimeParts"] to our own copy with our own counts
+					envelope.Values["MimeParts"] = reWriter.swap()
+
+					defer reWriter.unswap()
+					var pos int
+
+					offset := msgPos
+					reWriter.current = (*parts)[0]
+					for i := progress; i < len(*parts); i++ {
+						part := (*parts)[i]
+
+						// break chunk on new part
+						if part.StartingPos > 0 && part.StartingPos > msgPos {
+							reWriter.isBody = false
+							count, err = reWriter.ReWrite(p[pos : part.StartingPos-offset])
+
+							total += count
+							if err != nil {
+								break
+							}
+							reWriter.current = part
+
+							pos += count
+							msgPos = part.StartingPos
+						}
+						// break chunk on header (found the body)
+						if part.StartingPosBody > 0 && part.StartingPosBody >= msgPos {
+							//reWriter.recalculate()
+							count, err = reWriter.ReWrite(p[pos : part.StartingPosBody-offset])
+
+							total += count
+							if err != nil {
+								break
+							}
+							_, _ = reWriter.parser.Write([]byte{'\n'}) // send an end of header to the parser
+							reWriter.isBody = true
+							reWriter.current = part
+
+							pos += count
+							msgPos = part.StartingPosBody
+						}
+						// if on the latest (last) part, and yet there is still data to be written out
+						if len(*parts)-1 == i && len(p)-1 > pos {
+							count, err = reWriter.ReWrite(p[pos:])
+							total += count
+							if err != nil {
+								break
+							}
+							pos += count
+							msgPos += uint(count)
+						}
+						// if there's no more data
+						if pos >= len(p) {
+							break
+						}
+					}
+					if len(*parts) > 2 {
+						progress = len(*parts) - 2 // skip to 2nd last part, assume previous parts are already processed
+					}
+				}
+
+				return total, err
+			})
+		}
+
+	return sd
+}

+ 19 - 11
chunk/chunk_test.go

@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	"github.com/flashmob/go-guerrilla/backends"
+	"github.com/flashmob/go-guerrilla/chunk/transfer"
 	"github.com/flashmob/go-guerrilla/mail"
 )
 
@@ -377,11 +378,12 @@ func TestChunkSaverReader(t *testing.T) {
 			t.Error(err)
 		}
 		part := email.partsInfo.Parts[0]
-		encoding := transportQuotedPrintable
+
+		encoding := transfer.QuotedPrintable
 		if strings.Contains(part.TransferEncoding, "base") {
-			encoding = transportBase64
+			encoding = transfer.Base64
 		}
-		dr, err := NewDecoder(r, encoding, part.Charset)
+		dr, err := transfer.NewDecoder(r, encoding, part.Charset)
 		_ = dr
 		if err != nil {
 			t.Error(err)
@@ -404,11 +406,11 @@ func TestChunkSaverReader(t *testing.T) {
 			t.Error(err)
 		}
 		part = email.partsInfo.Parts[0]
-		encoding = transportQuotedPrintable
+		encoding = transfer.QuotedPrintable
 		if strings.Contains(part.TransferEncoding, "base") {
-			encoding = transportBase64
+			encoding = transfer.Base64
 		}
-		dr, err = NewDecoder(r, encoding, part.Charset)
+		dr, err = transfer.NewDecoder(r, encoding, part.Charset)
 		_ = dr
 		if err != nil {
 			t.Error(err)
@@ -486,11 +488,11 @@ func TestChunkSaverWrite(t *testing.T) {
 			t.Error(err)
 		}
 		part := email.partsInfo.Parts[0]
-		encoding := transportQuotedPrintable
+		encoding := transfer.QuotedPrintable
 		if strings.Contains(part.TransferEncoding, "base") {
-			encoding = transportBase64
+			encoding = transfer.Base64
 		}
-		dr, err := NewDecoder(r, encoding, part.Charset)
+		dr, err := transfer.NewDecoder(r, encoding, part.Charset)
 		_ = dr
 		if err != nil {
 			t.Error(err)
@@ -515,12 +517,18 @@ func initTestStream() (*StoreMemory, *backends.StreamDecorator, *backends.Stream
 	// instantiate the chunk saver
 	chunksaver := backends.Streamers["chunksaver"]()
 	mimeanalyzer := backends.Streamers["mimeanalyzer"]()
+	transformer := backends.Streamers["transformer"]()
+	debug := backends.Streamers["debug"]()
 	// add the default processor as the underlying processor for chunksaver
 	// and chain it with mimeanalyzer.
 	// Call order: mimeanalyzer -> chunksaver -> default (terminator)
 	// This will also set our Open, Close and Initialize functions
 	// we also inject a Storage and a ChunkingBufferMime
-	stream := mimeanalyzer.Decorate(chunksaver.Decorate(backends.DefaultStreamProcessor{}, store, chunkBuffer))
+	stream := mimeanalyzer.Decorate(
+		transformer.Decorate(
+			debug.Decorate(
+				chunksaver.Decorate(
+					backends.DefaultStreamProcessor{}, store, chunkBuffer))))
 
 	// configure the buffer cap
 	bc := backends.BackendConfig{}
@@ -532,6 +540,6 @@ func initTestStream() (*StoreMemory, *backends.StreamDecorator, *backends.Stream
 	// give it the envelope with the parse results
 	_ = chunksaver.Open(e)
 	_ = mimeanalyzer.Open(e)
-
+	_ = transformer.Open(e)
 	return store, chunksaver, mimeanalyzer, stream
 }

+ 9 - 5
chunk/processor.go

@@ -27,7 +27,7 @@ import (
 // ----------------------------------------------------------------------------------
 // Config Options: chunksaver_chunk_size - maximum chunk size, in bytes
 // --------------:-------------------------------------------------------------------
-// Input         : e.Values["MimeParts"] Which is of type *[]*mime.Part, as populated by "mimeanalyzer"
+// Input         : e.Values["MimeParts"] Which is of type *mime.Parts, as populated by "mimeanalyzer"
 // ----------------------------------------------------------------------------------
 // Output        :
 // ----------------------------------------------------------------------------------
@@ -77,7 +77,7 @@ func Chunksaver() *backends.StreamDecorator {
 			)
 
 			var config *Config
-			// optional dependency injection
+			// optional dependency injection (you can pass your own instance of Storage or ChunkingBufferMime)
 			for i := range a {
 				if db, ok := a[i].(Storage); ok {
 					database = db
@@ -178,7 +178,7 @@ func Chunksaver() *backends.StreamDecorator {
 				return nil
 			}
 
-			fillVars := func(parts *[]*mime.Part, subject, to, from string) (string, string, string) {
+			fillVars := func(parts *mime.Parts, subject, to, from string) (string, string, string) {
 				if len(*parts) > 0 {
 					if subject == "" {
 						if val, ok := (*parts)[0].Headers["Subject"]; ok {
@@ -210,7 +210,7 @@ func Chunksaver() *backends.StreamDecorator {
 				if envelope.Values == nil {
 					return count, errors.New("no message headers found")
 				}
-				if parts, ok := envelope.Values["MimeParts"].(*[]*mime.Part); ok && len(*parts) > 0 {
+				if parts, ok := envelope.Values["MimeParts"].(*mime.Parts); ok && len(*parts) > 0 {
 					var pos int
 
 					subject, to, from = fillVars(parts, subject, to, from)
@@ -236,7 +236,11 @@ func Chunksaver() *backends.StreamDecorator {
 						}
 						// break chunk on header
 						if part.StartingPosBody > 0 && part.StartingPosBody >= msgPos {
-							count, _ = chunkBuffer.Write(p[pos : part.StartingPosBody-offset])
+							to := part.StartingPosBody - offset
+							if lenp := len(p); int(to) > lenp {
+								to = uint(lenp)
+							}
+							count, _ = chunkBuffer.Write(p[pos:to])
 							written += int64(count)
 
 							err = chunkBuffer.Flush()

+ 8 - 6
chunk/store_memory.go

@@ -14,7 +14,7 @@ type StoreMemory struct {
 	chunks        map[HashKey]*memoryChunk
 	emails        []*memoryEmail
 	nextID        uint64
-	IDOffset      uint64
+	offset        uint64
 	CompressLevel int
 }
 
@@ -69,7 +69,7 @@ func (m *StoreMemory) OpenMessage(from string, helo string, recipient string, ip
 
 // CloseMessage implements the Storage interface
 func (m *StoreMemory) CloseMessage(mailID uint64, size int64, partsInfo *PartsInfo, subject string, deliveryID string, to string, from string) error {
-	if email := m.emails[mailID-m.IDOffset]; email == nil {
+	if email := m.emails[mailID-m.offset]; email == nil {
 		return errors.New("email not found")
 	} else {
 		email.size = size
@@ -123,8 +123,8 @@ func (m *StoreMemory) AddChunk(data []byte, hash []byte) error {
 
 // Initialize implements the Storage interface
 func (m *StoreMemory) Initialize(cfg backends.BackendConfig) error {
-	m.IDOffset = 1
-	m.nextID = m.IDOffset
+	m.offset = 1
+	m.nextID = m.offset
 	m.emails = make([]*memoryEmail, 0, 100)
 	m.chunks = make(map[HashKey]*memoryChunk, 1000)
 	m.CompressLevel = zlib.NoCompression
@@ -140,10 +140,12 @@ func (m *StoreMemory) Shutdown() (err error) {
 
 // GetEmail implements the Storage interface
 func (m *StoreMemory) GetEmail(mailID uint64) (*Email, error) {
-	if size := uint64(len(m.emails)) - m.IDOffset; size > mailID-m.IDOffset {
+	if count := len(m.emails); count == 0 {
+		return nil, errors.New("storage is empty")
+	} else if overflow := uint64(count) - m.offset; overflow > mailID-m.offset {
 		return nil, errors.New("mail not found")
 	}
-	email := m.emails[mailID-m.IDOffset]
+	email := m.emails[mailID-m.offset]
 	pi := NewPartsInfo()
 	if err := pi.UnmarshalJSONZlib(email.partsInfo); err != nil {
 		return nil, err

+ 19 - 10
chunk/decoder.go → chunk/transfer/decoder.go

@@ -1,4 +1,4 @@
-package chunk
+package transfer
 
 import (
 	"bytes"
@@ -11,13 +11,13 @@ import (
 	_ "github.com/flashmob/go-guerrilla/mail/encoding"
 )
 
-type transportEncoding int
+type Encoding int
 
 const (
-	transportBase64 transportEncoding = iota
-	transportQuotedPrintable
-	transport7bit // default, 1-127, 13 & 10 at line endings
-	transport8bit // 998 octets per line,  13 & 10 at line endings
+	Base64 Encoding = iota
+	QuotedPrintable
+	SevenBit // default, 1-127, 13 & 10 at line endings
+	EightBit // 998 octets per line,  13 & 10 at line endings
 
 )
 
@@ -25,13 +25,13 @@ const (
 type decoder struct {
 	state     int
 	charset   string
-	transport transportEncoding
+	transport Encoding
 	r         io.Reader
 }
 
 // NewDecoder reads a MIME-part from an underlying reader r
 // then decodes base64 or quoted-printable to 8bit, and converts encoding to UTF-8
-func NewDecoder(r io.Reader, transport transportEncoding, charset string) (*decoder, error) {
+func NewDecoder(r io.Reader, transport Encoding, charset string) (*decoder, error) {
 	decoder := new(decoder)
 	decoder.transport = transport
 	decoder.charset = strings.ToLower(charset)
@@ -39,6 +39,15 @@ func NewDecoder(r io.Reader, transport transportEncoding, charset string) (*deco
 	return decoder, nil
 }
 
+func NewBodyDecoder(r io.Reader, transport Encoding, charset string) (*decoder, error) {
+	d, err := NewDecoder(r, transport, charset)
+	if err != nil {
+		return d, err
+	}
+	d.state = decoderStateDecodeSetup
+	return d, nil
+}
+
 const chunkSaverNL = '\n'
 
 const (
@@ -90,9 +99,9 @@ func (r *decoder) Read(p []byte) (n int, err error) {
 				r.r = io.MultiReader(bytes.NewBuffer(buf[start:]), r.r)
 			}
 			switch r.transport {
-			case transportQuotedPrintable:
+			case QuotedPrintable:
 				r.r = quotedprintable.NewReader(r.r)
-			case transportBase64:
+			case Base64:
 				r.r = base64.NewDecoder(base64.StdEncoding, r.r)
 			default:
 

+ 22 - 1
mail/mime/mime.go

@@ -73,7 +73,7 @@ type Parser struct {
 	// each node. The name of the node is the *path* of the node. The root node is always
 	// "1". The child would be "1.1", the next sibling would be "1.2", while the child of
 	// "1.2" would be "1.2.1"
-	Parts []*Part
+	Parts Parts
 
 	msgPos uint // global position in the message
 
@@ -84,6 +84,8 @@ type Parser struct {
 	w io.Writer // underlying io.Writer
 }
 
+type Parts []*Part
+
 type Part struct {
 
 	// Headers contain the header names and values in a map data-structure
@@ -99,6 +101,12 @@ type Part struct {
 	EndingPos uint
 	// EndingPosBody is thr ending position for the body. Typically identical to EndingPos
 	EndingPosBody uint
+
+	StartingPosDelta     uint
+	StartingPosBodyDelta uint
+	EndingPosDelta       uint
+	EndingPosBodyDelta   uint
+
 	// Charset holds the character-set the part is encoded in, eg. us-ascii
 	Charset string
 	// TransferEncoding holds the transfer encoding that was used to pack the message eg. base64
@@ -883,6 +891,19 @@ func (p *Parser) mime(part *Part, cb string) (err error) {
 				}
 			}
 		}
+	} else if part.ContentBoundary == "" {
+		for {
+			p.skip(len(p.buf))
+			if p.ch == 0 {
+				if part.StartingPosBody > 0 {
+					part.EndingPosBody = p.msgPos
+					part.EndingPos = p.msgPos
+				}
+				err = io.EOF
+				return
+			}
+		}
+
 	}
 	return
 

+ 72 - 0
mail/mime/mime_test.go

@@ -594,3 +594,75 @@ func TestNonMineEmail(t *testing.T) {
 	}
 
 }
+
+var email6 = `Delivered-To: [email protected]
+Received: from bb_dyn_pb-146-88-38-36.violin.co.th (bb_dyn_pb-146-88-38-36.violin.co.th  [146.88.38.36])
+	by sharklasers.com with SMTP id [email protected];
+	Tue, 17 Sep 2019 01:13:00 +0000
+Received: from mx03.listsystemsf.net [100.20.38.85] by mxs.perenter.com with SMTP; Tue, 17 Sep 2019 04:57:59 +0500
+Received: from mts.locks.grgtween.net ([Tue, 17 Sep 2019 04:52:27 +0500])
+	by webmail.halftomorrow.com with LOCAL; Tue, 17 Sep 2019 04:52:27 +0500
+Received: from mail.naihautsui.co.kr ([61.220.30.1]) by mtu67.syds.piswix.net with ASMTP; Tue, 17 Sep 2019 04:47:25 +0500
+Received: from unknown (HELO mx03.listsystemsf.net) (Tue, 17 Sep 2019 04:41:45 +0500)
+	by smtp-server1.cfdenselr.com with LOCAL; Tue, 17 Sep 2019 04:41:45 +0500
+Message-ID: <[email protected]>
+Date: Tue, 17 Sep 2019 04:14:56 +0500
+Reply-To: "Richard" <[email protected]>
+From: "Rick" <[email protected]>
+User-Agent: Mozilla 4.73 [de]C-CCK-MCD DT  (Win98; U)
+X-Accept-Language: en-us
+MIME-Version: 1.0
+To: "Nevaeh" <[email protected]>
+Subject: Cesść tereska
+Content-Type: text/html;
+	charset="iso-8859-1"
+Content-Transfer-Encoding: base64
+
+iVBORw0KGgoAAAANSUhEUgAAAG4AAAAyCAIAAAAydXkgAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA
+B3RJTUUH1gYEExgGfYkXIAAAAAd0RVh0QXV0aG9yAKmuzEgAAAAMdEVYdERlc2NyaXB0aW9uABMJ
+ISMAAAAKdEVYdENvcHlyaWdodACsD8w6AAAADnRFWHRDcmVhdGlvbiB0aW1lADX3DwkAAAAJdEVY
+dFNvZnR3YXJlAF1w/zoAAAALdEVYdERpc2NsYWltZXIAt8C0jwAAAAh0RVh0V2FybmluZwDAG+aH
+AAAAB3RFWHRTb3VyY2UA9f+D6wAAAAh0RVh0Q29tbWVudAD2zJa/AAAABnRFWHRUaXRsZQCo7tIn
+AAABAElEQVR4nO2ZUY6DIBCG66YH88FGvQLHEI+hHsMriPFw7AMJIYAwoO269v+eSDPDmKn5HOXx
+AAAAAAAAAPxblmWRZJZlSU3RCCE451Z1IUQ00c1ScM7p15zHT1J0URSpwUkpmrquh2HY60uA1+vl
+/b2qKkp63tUCcA8otrK8k+dKr7+I1V0tEEUppRRCZDcnzZUZHLdP6g6uFomiBACYeHUTTnF9ZwV4
+3dp1HaW0V5dRUR6ZJU3e7kqLaK+9ZpymKamKOV3uTZrhigCAU1wZhV7aRE2IlKn2tq60WNeVHtz3
+vV7Xdc05b5pmL0ADVwLg5QOu3BNZhhxVwH1cmYoluwDqX2zbj2bPFgAAAMdJREFUNnUruBIALxmu
+dF1mBXhlSimtPzW6O5hfIQOJB7mcK72NSzrk2bYt+ku0IvhL8PCKwxhTi3meT9s06aBGOSjjpduF
+Ut1UnlnUUmG4kDtj6j5aa5c3noOfhX4ND1eXhvJMOYZFGYYxNs8zY6wsS73O3u2rUY1jjOkOBlp5
+uSf4NTn/fsw4Bz/oSnMMCm9laU4FuzMj5ZpN6K58JrVSfnAEW9d127ZxHInVLZM2TSOlpL/C72He
+j2c+wQEAAAAAAAAAfB2/3ihTGANzPd8AAAAASUVORK5CYII=
+`
+
+func TestNonMineEmailBigBody(t *testing.T) {
+	p = NewMimeParser()
+	b := []byte(email6)
+	to := 74
+	var in [][]byte
+	for i := 0; ; i += to {
+		if to+i > len(b) {
+			in = append(in, b[i:])
+			break
+		}
+		in = append(in, b[i:to+i])
+	}
+	p.inject(in...)
+	if err := p.mime(nil, ""); err != nil && err != NotMime && err != io.EOF {
+		t.Error(err)
+	} else {
+		for part := range p.Parts {
+			fmt.Println(p.Parts[part].Node + "  " + strconv.Itoa(int(p.Parts[part].StartingPos)) + "  " + strconv.Itoa(int(p.Parts[part].StartingPosBody)) + "  " + strconv.Itoa(int(p.Parts[part].EndingPosBody)))
+		}
+	}
+	err := p.Close()
+	if err != nil {
+		t.Error(err)
+	}
+
+	// what if we pass an empty string?
+	p.inject([]byte{' '})
+	if err := p.mime(nil, ""); err == nil || err == NotMime || err == io.EOF {
+		t.Error("unexpected error", err)
+	}
+
+}

+ 1 - 1
mail/reader.go

@@ -50,7 +50,7 @@ func (r MimeDotReader) Close() (err error) {
 }
 
 // Parts returns the mime-header parts built by the parser
-func (r *MimeDotReader) Parts() []*mime.Part {
+func (r *MimeDotReader) Parts() mime.Parts {
 	return r.p.Parts
 }