Browse Source

- chunksaver is taking shape!
- added some tests to the Makefile that were left out

flashmob 6 years ago
parent
commit
1a14ab3f41

+ 4 - 0
Makefile

@@ -35,6 +35,10 @@ test: *.go */*.go */*/*.go
 	$(GO_VARS) $(GO) test -v ./response
 	$(GO_VARS) $(GO) test -v ./backends
 	$(GO_VARS) $(GO) test -v ./mail
+	$(GO_VARS) $(GO) test -v ./mail/mime
+	$(GO_VARS) $(GO) test -v ./mail/encoding
+	$(GO_VARS) $(GO) test -v ./mail/iconv
+	$(GO_VARS) $(GO) test -v ./mail/rfc5321
 
 testrace: *.go */*.go */*/*.go
 	$(GO_VARS) $(GO) test -v . -race

+ 139 - 4
api_test.go

@@ -445,7 +445,7 @@ func talkToServer(address string, body string) (err error) {
 	if err != nil {
 		return err
 	}
-	_, err = fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n")
+	_, err = fmt.Fprint(conn, "MAIL FROM:<[email protected]>\r\n")
 	if err != nil {
 		return err
 	}
@@ -504,6 +504,12 @@ func talkToServer(address string, body string) (err error) {
 	if err != nil {
 		return err
 	}
+
+	_, err = fmt.Fprint(conn, "QUIT\r\n")
+	if err != nil {
+		return err
+	}
+
 	_ = str
 	return nil
 }
@@ -1049,9 +1055,6 @@ func TestStreamMimeProcessor(t *testing.T) {
 
 	go func() {
 		time.Sleep(time.Second * 15)
-		//panic("here")
-		//		*moo = *moo + 6
-
 		// for debugging deadlocks
 		//pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
 		//os.Exit(1)
@@ -1082,3 +1085,135 @@ func TestStreamMimeProcessor(t *testing.T) {
 	}
 
 }
+
+var email = `From:  Al Gore <[email protected]>
+To:  White House Transportation Coordinator <[email protected]>
+Subject: [Fwd: Map of Argentina with Description]
+MIME-Version: 1.0
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; s=ncr424; d=reliancegeneral.co.in;
+ h=List-Unsubscribe:MIME-Version:From:To:Reply-To:Date:Subject:Content-Type:Content-Transfer-Encoding:Message-ID; [email protected];
+ bh=F4UQPGEkpmh54C7v3DL8mm2db1QhZU4gRHR1jDqffG8=;
+ b=MVltcq6/I9b218a370fuNFLNinR9zQcdBSmzttFkZ7TvV2mOsGrzrwORT8PKYq4KNJNOLBahswXf
+   GwaMjDKT/5TXzegdX/L3f/X4bMAEO1einn+nUkVGLK4zVQus+KGqm4oP7uVXjqp70PWXScyWWkbT
+   1PGUwRfPd/HTJG5IUqs=
+Content-Type: multipart/mixed;
+ boundary="D7F------------D7FD5A0B8AB9C65CCDBFA872"
+
+This is a multi-part message in MIME format.
+--D7F------------D7FD5A0B8AB9C65CCDBFA872
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Fred,
+
+Fire up Air Force One!  We're going South!
+
+Thanks,
+Al
+--D7F------------D7FD5A0B8AB9C65CCDBFA872
+Content-Type: message/rfc822
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+Return-Path: <[email protected]>
+Received: from mailhost.whitehouse.gov ([192.168.51.200])
+ by heartbeat.whitehouse.gov (8.8.8/8.8.8) with ESMTP id SAA22453
+ for <[email protected]>;
+ Mon, 13 Aug 1998 l8:14:23 +1000
+Received: from the_big_box.whitehouse.gov ([192.168.51.50])
+ by mailhost.whitehouse.gov (8.8.8/8.8.7) with ESMTP id RAA20366
+ for [email protected]; Mon, 13 Aug 1998 17:42:41 +1000
+ Date: Mon, 13 Aug 1998 17:42:41 +1000
+Message-Id: <[email protected]>
+From: Bill Clinton <[email protected]>
+To: A1 (The Enforcer) Gore <[email protected]>
+Subject:  Map of Argentina with Description
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="DC8------------DC8638F443D87A7F0726DEF7"
+
+This is a multi-part message in MIME format.
+--DC8------------DC8638F443D87A7F0726DEF7
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Hi A1,
+
+I finally figured out this MIME thing.  Pretty cool.  I'll send you
+some sax music in .au files next week!
+
+Anyway, the attached image is really too small to get a good look at
+Argentina.  Try this for a much better map:
+
+http://www.1one1yp1anet.com/dest/sam/graphics/map-arg.htm
+
+Then again, shouldn't the CIA have something like that?
+
+Bill
+--DC8------------DC8638F443D87A7F0726DEF7
+Content-Type: image/gif; name="map_of_Argentina.gif"
+Content-Transfer-Encoding: base64
+Content-Disposition: in1ine; fi1ename="map_of_Argentina.gif"
+
+R01GOD1hJQA1AKIAAP/////78P/omn19fQAAAAAAAAAAAAAAACwAAAAAJQA1AAAD7Qi63P5w
+wEmjBCLrnQnhYCgM1wh+pkgqqeC9XrutmBm7hAK3tP31gFcAiFKVQrGFR6kscnonTe7FAAad
+GugmRu3CmiBt57fsVq3Y0VFKnpYdxPC6M7Ze4crnnHum4oN6LFJ1bn5NXTN7OF5fQkN5WYow
+BEN2dkGQGWJtSzqGTICJgnQuTJN/WJsojad9qXMuhIWdjXKjY4tenjo6tjVssk2gaWq3uGNX
+U6ZGxseyk8SasGw3J9GRzdTQky1iHNvcPNNI4TLeKdfMvy0vMqLrItvuxfDW8ubjueDtJufz
+7itICBxISKDBgwgTKjyYAAA7
+--DC8------------DC8638F443D87A7F0726DEF7--
+
+--D7F------------D7FD5A0B8AB9C65CCDBFA872--
+
+`
+
+func TestStreamChunkSaver(t *testing.T) {
+	if err := os.Truncate("tests/testlog", 0); err != nil {
+		t.Error(err)
+	}
+
+	go func() {
+		time.Sleep(time.Second * 15)
+		// for debugging deadlocks
+		//pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
+		//os.Exit(1)
+	}()
+
+	cfg := &AppConfig{
+		LogFile:      "tests/testlog",
+		AllowedHosts: []string{"grr.la"},
+		BackendConfig: backends.BackendConfig{
+			"stream_save_process":   "mimeanalyzer|chunksaver",
+			"chunksaver_chunk_size": 1024 * 32,
+			"stream_buffer_size":    1024 * 16,
+		},
+	}
+
+	d := Daemon{Config: cfg}
+
+	if err := d.Start(); err != nil {
+		t.Error(err)
+	}
+
+	// change \n to \r\n
+	email = strings.Replace(email, "\n", "\r\n", -1)
+	// lets have a talk with the server
+	if err := talkToServer("127.0.0.1:2525", email); err != nil {
+		t.Error(err)
+	}
+	time.Sleep(time.Second * 1)
+	d.Shutdown()
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	fmt.Println(string(b))
+
+	// lets check for fingerprints
+	if strings.Index(string(b), "Debug stream") < 0 {
+		//	t.Error("did not log: Debug stream")
+	}
+
+}

+ 0 - 1
backends/gateway.go

@@ -392,7 +392,6 @@ func (gw *BackendGateway) newStreamStack(stackConfig string) (streamer, error) {
 	var decorators []*StreamDecorator
 	cfg := strings.ToLower(strings.TrimSpace(stackConfig))
 	if len(cfg) == 0 {
-
 		return streamer{NoopStreamProcessor{}, decorators}, nil
 	}
 	items := strings.Split(cfg, "|")

+ 1 - 1
backends/processor.go

@@ -68,7 +68,7 @@ func (f StreamProcessWith) Write(p []byte) (n int, err error) {
 type DefaultStreamProcessor struct{}
 
 func (w DefaultStreamProcessor) Write(p []byte) (n int, err error) {
-	return 0, nil
+	return len(p), nil
 }
 
 type NoopStreamProcessor struct{ DefaultStreamProcessor }

+ 134 - 32
backends/s_chunksaver.go

@@ -1,13 +1,40 @@
 package backends
 
+// ----------------------------------------------------------------------------------
+// Processor Name: ChunkSaver
+// ----------------------------------------------------------------------------------
+// Description   : Takes the stream and saves it in chunks. Chunks are split on the
+//               : chunksaver_chunk_size config setting, and also at the end of MIME parts,
+//               : and after a header. This allows for basic de-duplication: we can take a
+//               : hash of each chunk, then check the database to see if we have it already.
+//               : We don't need to write it to the database, but take the reference of the
+//               : previously saved chunk and only increment the reference count.
+//               : The rationale to put headers and bodies into separate chunks is
+//               : due to headers often containing more unique data, while the bodies are
+//               : often duplicated, especially for messages that are CC'd or forwarded
+// ----------------------------------------------------------------------------------
+// Requires      : "mimeanalyzer" stream processor to be enabled before it
+// ----------------------------------------------------------------------------------
+// Config Options: chunksaver_chunk_size - maximum chunk size, in bytes
+// --------------:-------------------------------------------------------------------
+// Input         : e.Values["MimeParts"] Which is of type *[]*mime.Part, as populated by "mimeanalyzer"
+// ----------------------------------------------------------------------------------
+// Output        :
+// ----------------------------------------------------------------------------------
+
 import (
-	"bytes"
+	"errors"
 	"fmt"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mail/mime"
-	"strconv"
 )
 
+type chunkSaverConfig struct {
+	// ChunkMaxBytes controls the maximum buffer size for saving
+	// 16KB default. The smallest possible size is 64 bytes to to bytes.Buffer limitation
+	ChunkMaxBytes int `json:"chunksaver_chunk_size"`
+}
+
 func init() {
 	streamers["chunksaver"] = func() *StreamDecorator {
 		return Chunksaver()
@@ -31,78 +58,153 @@ type chunkedParts struct {
 	ContentDisposition string
 }
 
+type chunkedBytesBuffer struct {
+	buf []byte
+}
+
+// flush signals that it's time to write the buffer out to disk
+func (c *chunkedBytesBuffer) flush() {
+	fmt.Print(string(c.buf))
+	c.Reset()
+}
+
+// Reset sets the length back to 0, making it re-usable
+func (c *chunkedBytesBuffer) Reset() {
+	c.buf = c.buf[:0] // set the length back to 0
+}
+
+// Write takes a p slice of bytes and writes it to the buffer.
+// It will never grow the buffer, flushing it as
+// soon as it's full. It will also flush it after the entire slice is written
+func (c *chunkedBytesBuffer) Write(p []byte) (i int, err error) {
+	remaining := len(p)
+	bufCap := cap(c.buf)
+	for {
+		free := bufCap - len(c.buf)
+		if free > remaining {
+			// enough of room in the buffer
+			c.buf = append(c.buf, p[i:i+remaining]...)
+			i += remaining
+			c.flush()
+			return
+		} else {
+			// fill the buffer to the 'brim' with a slice from p
+			c.buf = append(c.buf, p[i:i+bufCap]...)
+			remaining -= bufCap
+			i += bufCap
+			c.flush()
+		}
+	}
+}
+
+// CapTo caps the internal buffer to specified number of bytes, sets the length back to 0
+func (c *chunkedBytesBuffer) CapTo(n int) {
+	if cap(c.buf) == n {
+		return
+	}
+	c.buf = make([]byte, 0, n)
+}
+
+const chunkMaxBytes = 1024 * 16 // 16Kb is the default, change using chunksaver_chunk_size config setting
 /**
- * messages: mid, part_tree, part_count, has_attach, created_at
- * parts: mid, part_id, chunk_md5, header_data, seq
- * chunk: md5, references, data, created_at
- * A chunk ends ether: after 64KB or after end of a part
+*
+ * A chunk ends ether:
+ * after xKB or after end of a part, or end of header
  *
  * - buffer first chunk
  * - if didn't receive first chunk for more than x bytes, save normally
  *
- */
+*/
 func Chunksaver() *StreamDecorator {
 
 	sd := &StreamDecorator{}
 	sd.p =
 
 		func(sp StreamProcessor) StreamProcessor {
-
 			var (
 				envelope    *mail.Envelope
-				currentPart int
-				chunkBuffer bytes.Buffer
+				chunkBuffer chunkedBytesBuffer
+				info        partsInfo
+				msgPos      uint
 			)
+
+			var config *chunkSaverConfig
+
 			Svc.AddInitializer(InitializeWith(func(backendConfig BackendConfig) error {
+				configType := BaseConfig(&chunkSaverConfig{})
+				bcfg, err := Svc.ExtractConfig(backendConfig, configType)
+				if err != nil {
+					return err
+				}
+				config = bcfg.(*chunkSaverConfig)
+				if config.ChunkMaxBytes > 0 {
+					chunkBuffer.CapTo(config.ChunkMaxBytes)
+				} else {
+					chunkBuffer.CapTo(chunkMaxBytes)
+				}
 				return nil
 			}))
 
 			Svc.AddShutdowner(ShutdownWith(func() error {
-
 				return nil
 			}))
 
 			sd.Open = func(e *mail.Envelope) error {
 				// create a new entry & grab the id
 				envelope = e
-				currentPart = 0
+				info = partsInfo{Parts: make([]chunkedParts, 0, 3)}
+				_ = info.Count
 				return nil
 			}
 
 			sd.Close = func() error {
-				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)))
-					}
-				}
 				chunkBuffer.Reset()
-
-				// finalize the message
-
 				return nil
 			}
 
 			return StreamProcessWith(func(p []byte) (int, error) {
-				_ = envelope
-				if len(envelope.Header) > 0 {
 
+				if envelope.Values == nil {
+					return 0, errors.New("no message headers found")
 				}
-				var parts []*mime.Part
-				if val, ok := envelope.Values["MimeParts"]; !ok {
-					//envelope.Values["MimeParts"] = &parser.Parts
-					parts = val.([]*mime.Part)
-					size := len(parts)
-					if currentPart != size {
-						currentPart = size
-						// a new part! todo: code to start a new part
-						if currentPart == 0 {
 
+				if parts, ok := envelope.Values["MimeParts"].(*[]*mime.Part); ok {
+					var pos int
+					offset := msgPos
+					for i := range *parts {
+						part := (*parts)[i]
+
+						// break chunk on new part
+						if part.StartingPos > 0 && part.StartingPos > msgPos {
+							count, _ := chunkBuffer.Write(p[pos : part.StartingPos-offset])
+							pos += count
+							msgPos = part.StartingPos
+						}
+
+						// break chunk on header
+						if part.StartingPosBody > 0 && part.StartingPosBody >= msgPos {
+							count, _ := chunkBuffer.Write(p[pos : part.StartingPosBody-offset])
+							chunkBuffer.flush()
+							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, _ := chunkBuffer.Write(p[pos:])
+							pos += count
+							msgPos += uint(count)
+						}
+
+						// break out if there's no more data to write out
+						if pos >= len(p) {
+							break
 						}
 					}
 				}
-
 				return sp.Write(p)
 			})
+
 		}
 
 	return sd

+ 184 - 0
backends/s_chunksaver_test.go

@@ -0,0 +1,184 @@
+package backends
+
+import (
+	"fmt"
+	"github.com/flashmob/go-guerrilla/mail"
+	"github.com/flashmob/go-guerrilla/mail/mime"
+	"testing"
+)
+
+func TestChunkedBytesBuffer(t *testing.T) {
+	var in string
+
+	var buf chunkedBytesBuffer
+	buf.CapTo(64)
+
+	// the data to write is over-aligned
+	in = `123456789012345678901234567890123456789012345678901234567890abcde12345678901234567890123456789012345678901234567890123456789abcdef` // len == 130
+	i, _ := buf.Write([]byte(in[:]))
+	if i != len(in) {
+		t.Error("did not write", len(in), "bytes")
+	}
+
+	// the data to write is aligned
+	var buf2 chunkedBytesBuffer
+	buf2.CapTo(64)
+	in = `123456789012345678901234567890123456789012345678901234567890abcde12345678901234567890123456789012345678901234567890123456789abcd` // len == 128
+	i, _ = buf2.Write([]byte(in[:]))
+	if i != len(in) {
+		t.Error("did not write", len(in), "bytes")
+	}
+
+	// the data to write is under-aligned
+	var buf3 chunkedBytesBuffer
+	buf3.CapTo(64)
+	in = `123456789012345678901234567890123456789012345678901234567890abcde12345678901234567890123456789012345678901234567890123456789ab` // len == 126
+	i, _ = buf3.Write([]byte(in[:]))
+	if i != len(in) {
+		t.Error("did not write", len(in), "bytes")
+	}
+
+	// the data to write is smaller than the buffer
+	var buf4 chunkedBytesBuffer
+	buf4.CapTo(64)
+	in = `1234567890` // len == 10
+	i, _ = buf4.Write([]byte(in[:]))
+	if i != len(in) {
+		t.Error("did not write", len(in), "bytes")
+	}
+}
+
+var email = `From:  Al Gore <[email protected]>
+To:  White House Transportation Coordinator <[email protected]>
+Subject: [Fwd: Map of Argentina with Description]
+MIME-Version: 1.0
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; s=ncr424; d=reliancegeneral.co.in;
+ h=List-Unsubscribe:MIME-Version:From:To:Reply-To:Date:Subject:Content-Type:Content-Transfer-Encoding:Message-ID; [email protected];
+ bh=F4UQPGEkpmh54C7v3DL8mm2db1QhZU4gRHR1jDqffG8=;
+ b=MVltcq6/I9b218a370fuNFLNinR9zQcdBSmzttFkZ7TvV2mOsGrzrwORT8PKYq4KNJNOLBahswXf
+   GwaMjDKT/5TXzegdX/L3f/X4bMAEO1einn+nUkVGLK4zVQus+KGqm4oP7uVXjqp70PWXScyWWkbT
+   1PGUwRfPd/HTJG5IUqs=
+Content-Type: multipart/mixed;
+ boundary="D7F------------D7FD5A0B8AB9C65CCDBFA872"
+
+This is a multi-part message in MIME format.
+--D7F------------D7FD5A0B8AB9C65CCDBFA872
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Fred,
+
+Fire up Air Force One!  We're going South!
+
+Thanks,
+Al
+--D7F------------D7FD5A0B8AB9C65CCDBFA872
+Content-Type: message/rfc822
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+Return-Path: <[email protected]>
+Received: from mailhost.whitehouse.gov ([192.168.51.200])
+ by heartbeat.whitehouse.gov (8.8.8/8.8.8) with ESMTP id SAA22453
+ for <[email protected]>;
+ Mon, 13 Aug 1998 l8:14:23 +1000
+Received: from the_big_box.whitehouse.gov ([192.168.51.50])
+ by mailhost.whitehouse.gov (8.8.8/8.8.7) with ESMTP id RAA20366
+ for [email protected]; Mon, 13 Aug 1998 17:42:41 +1000
+ Date: Mon, 13 Aug 1998 17:42:41 +1000
+Message-Id: <[email protected]>
+From: Bill Clinton <[email protected]>
+To: A1 (The Enforcer) Gore <[email protected]>
+Subject:  Map of Argentina with Description
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="DC8------------DC8638F443D87A7F0726DEF7"
+
+This is a multi-part message in MIME format.
+--DC8------------DC8638F443D87A7F0726DEF7
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Hi A1,
+
+I finally figured out this MIME thing.  Pretty cool.  I'll send you
+some sax music in .au files next week!
+
+Anyway, the attached image is really too small to get a good look at
+Argentina.  Try this for a much better map:
+
+http://www.1one1yp1anet.com/dest/sam/graphics/map-arg.htm
+
+Then again, shouldn't the CIA have something like that?
+
+Bill
+--DC8------------DC8638F443D87A7F0726DEF7
+Content-Type: image/gif; name="map_of_Argentina.gif"
+Content-Transfer-Encoding: base64
+Content-Disposition: in1ine; fi1ename="map_of_Argentina.gif"
+
+R01GOD1hJQA1AKIAAP/////78P/omn19fQAAAAAAAAAAAAAAACwAAAAAJQA1AAAD7Qi63P5w
+wEmjBCLrnQnhYCgM1wh+pkgqqeC9XrutmBm7hAK3tP31gFcAiFKVQrGFR6kscnonTe7FAAad
+GugmRu3CmiBt57fsVq3Y0VFKnpYdxPC6M7Ze4crnnHum4oN6LFJ1bn5NXTN7OF5fQkN5WYow
+BEN2dkGQGWJtSzqGTICJgnQuTJN/WJsojad9qXMuhIWdjXKjY4tenjo6tjVssk2gaWq3uGNX
+U6ZGxseyk8SasGw3J9GRzdTQky1iHNvcPNNI4TLeKdfMvy0vMqLrItvuxfDW8ubjueDtJufz
+7itICBxISKDBgwgTKjyYAAA7
+--DC8------------DC8638F443D87A7F0726DEF7--
+
+--D7F------------D7FD5A0B8AB9C65CCDBFA872--
+
+`
+
+func TestChunkSaverWrite(t *testing.T) {
+
+	// parse an email
+	parser := mime.NewMimeParser()
+
+	// place the parse result in an envelope
+	e := mail.NewEnvelope("127.0.0.1", 1)
+	e.Values["MimeParts"] = &parser.Parts
+
+	// instantiate the chunk saver
+	chunksaver := streamers["chunksaver"]()
+
+	// add the default processor as the underlying processor for chunksaver
+	// this will also set our Open, Close and Initialize functions
+	stream := chunksaver.p(DefaultStreamProcessor{})
+	// configure the buffer cap
+	bc := BackendConfig{}
+	bc["chunksaver_chunk_size"] = 64
+	_ = Svc.initialize(bc)
+
+	// give it the envelope with the parse results
+	_ = chunksaver.Open(e)
+
+	// let's test it
+
+	writeIt(parser, t, stream, 128)
+	//writeIt(parser, t, stream, 128000)
+}
+
+func writeIt(parser *mime.Parser, t *testing.T, stream StreamProcessor, size int) {
+
+	if size > len(email) {
+		size = len(email)
+	}
+	total := 0
+	for msgPos := 0; msgPos < len(email); msgPos += size {
+		err := parser.Parse([]byte(email)[msgPos : msgPos+size])
+		if err != nil {
+			t.Error(err)
+			t.Fail()
+		}
+		cut := msgPos + size
+		if cut > len(email) {
+			fmt.Println("* ", msgPos+cut-len(email))
+			cut -= cut - len(email)
+		}
+		i, _ := stream.Write([]byte(email)[msgPos:cut])
+		total += i
+	}
+	if total != len(email) {
+		t.Error("short write, total is", total, "but len(email) is", len(email))
+	}
+}

+ 1 - 1
backends/s_headers_parser.go

@@ -10,7 +10,7 @@ import (
 // ----------------------------------------------------------------------------------
 // Description   : Populates the envelope.Header value
 //-----------------------------------------------------------------------------------
-// Requires      : "mime" stream stream processor to be enabled before it
+// Requires      : "mimeanalyzer" stream processor to be enabled before it
 // ----------------------------------------------------------------------------------
 // Config Options: None
 // --------------:-------------------------------------------------------------------

+ 10 - 9
backends/s_mime.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mail/mime"
-	"strconv"
 )
 
 // ----------------------------------------------------------------------------------
@@ -54,15 +53,17 @@ func StreamMimeAnalyzer() *StreamDecorator {
 			}
 
 			sd.Close = func() error {
-
-				defer func() {
-					// todo remove debugging
-					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)))
+				/*
+					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()

+ 3 - 5
mail/mime/mime.go

@@ -357,7 +357,7 @@ func (p *Parser) boundary(contentBoundary string) (end bool, err error) {
 					// advance the pointer
 					p.skip(len(boundary) - p.boundaryMatched)
 
-					p.lastBoundaryPos = p.msgPos - uint(len(boundary)) // -1
+					p.lastBoundaryPos = p.msgPos - uint(len(boundary))
 					end, err = p.boundaryEnd()
 					if err != nil {
 						return
@@ -593,7 +593,6 @@ func (p *Parser) contentType() (result contentType, err error) {
 			}
 			continue
 		}
-
 		if p.ch > 32 && p.ch < 128 && !isTokenSpecial[p.ch] {
 			if key, val, err := p.parameter(); err != nil {
 				return result, err
@@ -714,7 +713,6 @@ func (p *Parser) quotedString() (str string, err error) {
 	for {
 		switch state {
 		case 0: // inside quotes
-
 			if p.ch == '"' {
 				p.next()
 				return
@@ -843,7 +841,7 @@ func (p *Parser) mime(part *Part, cb string) (err error) {
 	}
 	if ct != nil && ct.superType == multipart &&
 		part.ContentBoundary != "" &&
-		part.ContentBoundary != cb /* content-boundary must be different to previous */ {
+		part.ContentBoundary != cb { /* content-boundary must be different to previous */
 		var subPart *Part
 		subPart = newPart()
 		for {
@@ -949,7 +947,7 @@ func (p *Parser) Close() error {
 // The parser doesn't decode any input. All it does
 // is collect information about where the different MIME parts
 // start and end, and other meta-data. This data can be used
-// later down the stack to determine how to store/display
+// later down the stack to determine how to store/decode/display
 // the messages
 // returns error if there's a parse error, except io.EOF when no
 // error occurred.

+ 0 - 1
mail/mime/mime_message_test.go

@@ -1 +0,0 @@
-package mime

+ 2 - 2
server_test.go

@@ -406,7 +406,7 @@ func TestGatewayTimeout(t *testing.T) {
 		// perform 2 transactions
 		// both should panic.
 		for i := 0; i < 2; i++ {
-			if _, err := fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n"); err != nil {
+			if _, err := fmt.Fprint(conn, "MAIL FROM:<[email protected]>\r\n"); err != nil {
 				t.Error(err)
 			}
 			if str, err = in.ReadString('\n'); err != nil {
@@ -496,7 +496,7 @@ func TestGatewayPanic(t *testing.T) {
 		// sure that the client waits until processing finishes, and the
 		// timeout event is captured.
 		for i := 0; i < 2; i++ {
-			if _, err := fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n"); err != nil {
+			if _, err := fmt.Fprint(conn, "MAIL FROM:<[email protected]>\r\n"); err != nil {
 				t.Error(err)
 			}
 			if _, err = in.ReadString('\n'); err != nil {