Browse Source

milestone: tree structure correct

flashmob 6 years ago
parent
commit
0ec041dc1d
2 changed files with 466 additions and 142 deletions
  1. 336 107
      backends/s_mime.go
  2. 130 35
      backends/s_mime_test.go

+ 336 - 107
backends/s_mime.go

@@ -58,6 +58,11 @@ type mimepart struct {
 	*/
 	*/
 }
 }
 
 
+const (
+	maxBoundaryLen = 70 + 10
+	doubleDash     = "--"
+)
+
 type parser struct {
 type parser struct {
 	state int
 	state int
 
 
@@ -75,7 +80,7 @@ type parser struct {
 	isHalting                   bool
 	isHalting                   bool
 
 
 	// mime variables
 	// mime variables
-	parts   []mimeHeader
+	parts   []*mimeHeader
 	msgPos  uint
 	msgPos  uint
 	msgLine uint
 	msgLine uint
 }
 }
@@ -94,7 +99,7 @@ type mimeHeader struct {
 	charset          string
 	charset          string
 	transferEncoding string
 	transferEncoding string
 	contentBoundary  string
 	contentBoundary  string
-	contentType      string
+	contentType      *contentType
 	contentBase      string
 	contentBase      string
 
 
 	dispositionFi1eName string
 	dispositionFi1eName string
@@ -102,6 +107,16 @@ type mimeHeader struct {
 	contentName         string
 	contentName         string
 }
 }
 
 
+type contentType struct {
+	superType  string
+	subType    string
+	parameters map[string]string
+}
+
+func (c *contentType) String() string {
+	return fmt.Sprintf("%s/%s", c.superType, c.subType)
+}
+
 func NewMimeHeader() *mimeHeader {
 func NewMimeHeader() *mimeHeader {
 	mh := new(mimeHeader)
 	mh := new(mimeHeader)
 	mh.headers = make(textproto.MIMEHeader, 1)
 	mh.headers = make(textproto.MIMEHeader, 1)
@@ -109,7 +124,8 @@ func NewMimeHeader() *mimeHeader {
 }
 }
 
 
 func (p *parser) addPart(mh *mimeHeader, id string) {
 func (p *parser) addPart(mh *mimeHeader, id string) {
-
+	mh.part = id
+	p.parts = append(p.parts, mh)
 }
 }
 
 
 func (p *parser) endBody(mh *mimeHeader) {
 func (p *parser) endBody(mh *mimeHeader) {
@@ -195,48 +211,76 @@ func (p *parser) set(input []byte) {
 }
 }
 
 
 // boundary scans until next boundary string, returns error if not found
 // boundary scans until next boundary string, returns error if not found
-func (p *parser) boundary(part *mimeHeader) (err error) {
-	if len(part.contentBoundary) < 5 {
+// syntax specified https://tools.ietf.org/html/rfc2046 p21
+func (p *parser) boundary(contentBoundary string) (end bool, err error) {
+	defer func() {
+		if err == nil {
+			if p.ch == '\n' {
+				p.next()
+			}
+		}
+		// todo: remove this
+		//temp := p.buf[p.pos-10:p.pos+45]
+		//_ = temp
+	}()
+	// gensen chosu
+	// shotoku chomen sho
+	if len(contentBoundary) < 1 {
 		err = errors.New("content boundary too short")
 		err = errors.New("content boundary too short")
 	}
 	}
+	boundary := doubleDash + contentBoundary
 	p.boundaryMatched = 0
 	p.boundaryMatched = 0
 	for {
 	for {
-		if i := bytes.Index(p.buf, []byte(part.contentBoundary)); i > -1 {
+		if i := bytes.Index(p.buf[p.pos:], []byte(boundary)); i > -1 {
 			// advance the pointer to 1 char before the end of the boundary
 			// advance the pointer to 1 char before the end of the boundary
 			// then let next() to advance the last char.
 			// then let next() to advance the last char.
 			// in case the boundary is the tail part of buffer, calling next()
 			// in case the boundary is the tail part of buffer, calling next()
 			// will wait until we get a new buffer
 			// will wait until we get a new buffer
-			p.pos = i + len(part.contentBoundary) - 1
+			p.pos = p.pos + i + len(boundary) - 1
 			p.next()
 			p.next()
-			return nil
+			end = p.boundaryEnd()
+			p.transportPadding()
+			if p.ch != '\n' {
+				err = errors.New("boundary new line expected")
+			}
+			return
 
 
 		} else {
 		} else {
 			// search the tail for partial match
 			// search the tail for partial match
 			// if one is found, load more data and continue the match
 			// if one is found, load more data and continue the match
 			// if matched, advance buffer in same way as above
 			// if matched, advance buffer in same way as above
-			start := len(p.buf) - len(part.contentBoundary) + 1
+			start := len(p.buf) - len(boundary) + 1
 			if start < 0 {
 			if start < 0 {
 				start = 0
 				start = 0
 			}
 			}
 			subject := p.buf[start:]
 			subject := p.buf[start:]
 
 
 			for i := 0; i < len(subject); i++ {
 			for i := 0; i < len(subject); i++ {
-				if subject[i] == part.contentBoundary[p.boundaryMatched] {
+				if subject[i] == boundary[p.boundaryMatched] {
 					p.boundaryMatched++
 					p.boundaryMatched++
 				} else {
 				} else {
 					p.boundaryMatched = 0
 					p.boundaryMatched = 0
 				}
 				}
 			}
 			}
 			p.pos = len(p.buf) - 1
 			p.pos = len(p.buf) - 1
-			p.next()
+			p.next() // this will block until new bytes come in
 			if p.ch == 0 {
 			if p.ch == 0 {
-				return io.EOF
+				return false, io.EOF
 			} else if p.boundaryMatched > 0 {
 			} else if p.boundaryMatched > 0 {
+				// check for a match by joining the match from the end of the last buf
+				// & the beginning of this buf
 				if bytes.Compare(
 				if bytes.Compare(
-					p.buf[0:len(part.contentBoundary)-p.boundaryMatched],
-					[]byte(part.contentBoundary[p.boundaryMatched:])) == 0 {
-					// todo: move the pointer
-					return nil
+					p.buf[0:len(boundary)-p.boundaryMatched],
+					[]byte(boundary[p.boundaryMatched:])) == 0 {
+					// advance the pointer
+					p.pos += len(boundary) - p.boundaryMatched - 1
+					p.next()
+					end = p.boundaryEnd()
+					p.transportPadding()
+					if p.ch != '\n' {
+						err = errors.New("boundary new line expected")
+					}
+					return
 				}
 				}
 				p.boundaryMatched = 0
 				p.boundaryMatched = 0
 			}
 			}
@@ -245,6 +289,31 @@ func (p *parser) boundary(part *mimeHeader) (err error) {
 	}
 	}
 }
 }
 
 
+// is it the end of a boundary?
+func (p *parser) boundaryEnd() bool {
+	if p.ch == '-' && p.peek() == '-' {
+		p.next()
+		p.next()
+		return true
+	}
+	return false
+}
+
+// *LWSP-char
+// = *(WSP / CRLF WSP)
+func (p *parser) transportPadding() {
+	for {
+		if p.ch == ' ' || p.ch == '\t' {
+			p.next()
+		} else if c := p.peek(); p.ch == '\n' && (c == ' ' || c == '\t') {
+			p.next()
+			p.next()
+		} else {
+			return
+		}
+	}
+}
+
 type parserMsg struct {
 type parserMsg struct {
 	err error
 	err error
 }
 }
@@ -292,6 +361,12 @@ func (p *parser) header(mh *mimeHeader) (err error) {
 	defer func() {
 	defer func() {
 		fmt.Println(mh.headers)
 		fmt.Println(mh.headers)
 		p.accept.Reset()
 		p.accept.Reset()
+		if val := mh.headers.Get("Content-Transfer-Encoding"); val != "" {
+			mh.transferEncoding = val
+		}
+		if val := mh.headers.Get("Content-Disposition"); val != "" {
+			mh.contentDisposition = val
+		}
 
 
 	}()
 	}()
 	mh.startingPos = p.msgPos
 	mh.startingPos = p.msgPos
@@ -335,7 +410,17 @@ func (p *parser) header(mh *mimeHeader) (err error) {
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
-				fmt.Println(contentType, err)
+				mh.contentType = &contentType
+				if val, ok := contentType.parameters["boundary"]; ok {
+					mh.contentBoundary = val
+				}
+				if val, ok := contentType.parameters["charset"]; ok {
+					mh.charset = val
+				}
+				if val, ok := contentType.parameters["name"]; ok {
+					mh.contentName = val
+				}
+				mh.headers.Add("Content-Type", contentType.String())
 				state = 0
 				state = 0
 			} else {
 			} else {
 				if (p.ch >= 33 && p.ch <= 126) || p.isWSP(p.ch) {
 				if (p.ch >= 33 && p.ch <= 126) || p.isWSP(p.ch) {
@@ -378,12 +463,6 @@ func (p *parser) isWSP(b byte) bool {
 // type "/" subtype
 // type "/" subtype
 // *(";" parameter)
 // *(";" parameter)
 
 
-type contentType struct {
-	superType  string
-	subType    string
-	parameters map[string]string
-}
-
 // content disposition
 // content disposition
 // The Content-Disposition Header Field (rfc2183)
 // The Content-Disposition Header Field (rfc2183)
 // https://stackoverflow.com/questions/48347574/do-rfc-standards-require-the-filename-value-for-mime-attachment-to-be-encapsulat
 // https://stackoverflow.com/questions/48347574/do-rfc-standards-require-the-filename-value-for-mime-attachment-to-be-encapsulat
@@ -430,14 +509,19 @@ func (p *parser) contentType() (result contentType, err error) {
 				continue
 				continue
 			}
 			}
 
 
-			if key, val, err := p.parameter(); err != nil {
-				return result, err
-			} else {
-				if result.parameters == nil {
-					result.parameters = make(map[string]string, 1)
+			if p.ch > 32 && p.ch < 128 && !isTokenSpecial[p.ch] {
+				if key, val, err := p.parameter(); err != nil {
+					return result, err
+				} else {
+					if result.parameters == nil {
+						result.parameters = make(map[string]string, 1)
+					}
+					result.parameters[key] = val
 				}
 				}
-				result.parameters[key] = val
+			} else {
+				break
 			}
 			}
+
 		}
 		}
 	}
 	}
 
 
@@ -461,62 +545,6 @@ var isTokenSpecial = [128]bool{
 	'?':  true,
 	'?':  true,
 	'=':  true,
 	'=':  true,
 }
 }
-var isTokenAlphaDash = [128]bool{
-
-	'-': true,
-	'A': true,
-	'B': true,
-	'C': true,
-	'D': true,
-	'E': true,
-	'F': true,
-	'G': true,
-	'H': true,
-	'I': true,
-	'J': true,
-	'K': true,
-	'L': true,
-	'M': true,
-	'N': true,
-	'O': true,
-	'P': true,
-	'Q': true,
-	'R': true,
-	'S': true,
-	'T': true,
-	'U': true,
-	'W': true,
-	'V': true,
-	'X': true,
-	'Y': true,
-	'Z': true,
-	'a': true,
-	'b': true,
-	'c': true,
-	'd': true,
-	'e': true,
-	'f': true,
-	'g': true,
-	'h': true,
-	'i': true,
-	'j': true,
-	'k': true,
-	'l': true,
-	'm': true,
-	'n': true,
-	'o': true,
-	'p': true,
-	'q': true,
-	'r': true,
-	's': true,
-	't': true,
-	'u': true,
-	'v': true,
-	'w': true,
-	'x': true,
-	'y': true,
-	'z': true,
-}
 
 
 func (p *parser) mimeType() (str string, err error) {
 func (p *parser) mimeType() (str string, err error) {
 
 
@@ -526,11 +554,11 @@ func (p *parser) mimeType() (str string, err error) {
 			p.accept.Reset()
 			p.accept.Reset()
 		}
 		}
 	}()
 	}()
-	if p.ch < 128 && isTokenAlphaDash[p.ch] {
+	if p.ch < 128 && p.ch > 32 && !isTokenSpecial[p.ch] {
 		for {
 		for {
 			p.accept.WriteByte(p.ch)
 			p.accept.WriteByte(p.ch)
 			p.next()
 			p.next()
-			if !(p.ch < 128 && isTokenAlphaDash[p.ch]) {
+			if !(p.ch < 128 && p.ch > 32 && !isTokenSpecial[p.ch]) {
 				return
 				return
 			}
 			}
 
 
@@ -668,28 +696,23 @@ func (p *parser) parameter() (attribute, value string, err error) {
 		if value, err = p.quotedString(); err != nil {
 		if value, err = p.quotedString(); err != nil {
 			return
 			return
 		}
 		}
-		// OK, return  the attribute and value
 		return
 		return
 	} else {
 	} else {
 		if value, err = p.token(); err != nil {
 		if value, err = p.token(); err != nil {
-			// err
 			return
 			return
 		}
 		}
-		// OK, return the attribute and value
 		return
 		return
 	}
 	}
 }
 }
 
 
 func (p *parser) body(mh *mimeHeader) (err error) {
 func (p *parser) body(mh *mimeHeader) (err error) {
 	var body bytes.Buffer
 	var body bytes.Buffer
-	//var ct contentType
-	//if content, ok := p.headers["Content-Type"]; ok {
-	//	_ = content
-	//}
 
 
 	if mh.contentBoundary != "" {
 	if mh.contentBoundary != "" {
-		if err = p.boundary(mh); err != nil {
+		if end, err := p.boundary(mh.contentBoundary); err != nil {
 			return err
 			return err
+		} else {
+			fmt.Println("boundary end:", end)
 		}
 		}
 		mh.endingPosBody = p.msgPos
 		mh.endingPosBody = p.msgPos
 		return
 		return
@@ -713,29 +736,235 @@ func (p *parser) body(mh *mimeHeader) (err error) {
 
 
 }
 }
 
 
-func (p *parser) mimeMsg(parent *mimeHeader, depth string) (err error) {
-	count := 0
+func (p *parser) mime(boundary string, depth string) (err error) {
+	count := 1
+	h := NewMimeHeader()
+
+	if p.ch >= 33 && p.ch <= 126 {
+		err = p.header(h)
+		if err != nil {
+			//				temp := p.buf[p.pos:p.pos+20]
+			//				_ = temp
+			return err
+		}
+	} else {
+		fmt.Println("empty header")
+	}
+
+	if p.ch == '\n' && p.peek() == '\n' {
+		p.next()
+		p.next()
+	}
+	if h.contentBoundary != "" {
+		boundary = h.contentBoundary
+
+	}
+
+	if end, bErr := p.boundary(boundary); bErr != nil {
+		return bErr
+	} else if end {
+		h.endingPosBody = p.msgPos
+		return
+	}
+
+	if depth == "1" {
+		p.addPart(h, depth)
+	} else {
+		p.addPart(h, depth+"."+strconv.Itoa(count))
+		if h.contentType != nil &&
+			(h.contentType.superType == "message" ||
+				h.contentType.superType == "multipart") {
+			return p.mime(boundary, depth+"."+strconv.Itoa(count))
+		} else {
+			count++
+		}
+	}
+
 	for {
 	for {
+
+		var part mimeHeader
+
+		part = *NewMimeHeader()
+		if p.ch >= 33 && p.ch <= 126 {
+			err = p.header(&part)
+			if err != nil {
+				return err
+			}
+		}
+		if p.ch == '\n' && p.peek() == '\n' {
+			p.next()
+			p.next()
+		}
+
+		p.addPart(&part, depth+"."+strconv.Itoa(count))
+		if part.contentType != nil &&
+			(part.contentType.superType == "message" ||
+				part.contentType.superType == "multipart") {
+			return p.mime(boundary, depth+"."+strconv.Itoa(count))
+		} else {
+			if end, bErr := p.boundary(boundary); bErr != nil {
+				return bErr
+			} else if end {
+				part.endingPosBody = p.msgPos
+				break
+			}
+		}
+
 		count++
 		count++
-		depth = depth + "." + strconv.Itoa(count)
-		{
-			h := NewMimeHeader()
-			err = p.header(h)
+
+	}
+	return
+
+}
+
+func (p *parser) mime2(boundary string, depth string) (err error) {
+	count := 1
+	h := NewMimeHeader()
+
+	if p.ch >= 33 && p.ch <= 126 {
+		err = p.header(h)
+		if err != nil {
+			//				temp := p.buf[p.pos:p.pos+20]
+			//				_ = temp
+			return err
+		}
+	} else {
+		fmt.Println("empty header")
+	}
+
+	if depth == "1" {
+		p.addPart(h, depth)
+	} else {
+		p.addPart(h, depth+"."+strconv.Itoa(count))
+		if h.contentType != nil &&
+			(h.contentType.superType == "message" ||
+				h.contentType.superType == "multipart") {
+			depth = depth + "." + strconv.Itoa(count)
+		}
+	}
+
+	if p.ch == '\n' && p.peek() == '\n' {
+		p.next()
+		p.next()
+	}
+	if h.contentBoundary != "" {
+		boundary = h.contentBoundary
+
+	}
+
+	if end, bErr := p.boundary(boundary); bErr != nil {
+		return bErr
+	} else if end {
+		h.endingPosBody = p.msgPos
+		return
+	}
+
+	for {
+
+		var part mimeHeader
+
+		part = *NewMimeHeader()
+		if p.ch >= 33 && p.ch <= 126 {
+			err = p.header(&part)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
-			p.addPart(h, depth)
-			if h.contentBoundary != "" && parent.contentBoundary != h.contentBoundary {
-				if err = p.boundary(h); err != nil {
+		}
+		if p.ch == '\n' && p.peek() == '\n' {
+			p.next()
+			p.next()
+		}
+
+		p.addPart(&part, depth+"."+strconv.Itoa(count))
+		if part.contentType != nil &&
+			(part.contentType.superType == "message" ||
+				part.contentType.superType == "multipart") {
+			return p.mime(boundary, depth+"."+strconv.Itoa(count))
+		} else {
+
+			if end, bErr := p.boundary(boundary); bErr != nil {
+				return bErr
+			} else if end {
+				part.endingPosBody = p.msgPos
+				break
+			}
+		}
+		count++
+
+	}
+	return
+
+}
+
+func (p *parser) mimeMsg(boundary string, depth string) (err error) {
+	count := 0
+	for {
+		count++
+		d := depth + "." + strconv.Itoa(count)
+		_ = d
+
+		{
+			h := NewMimeHeader()
+
+			if p.ch >= 33 && p.ch <= 126 {
+				err = p.header(h)
+				if err != nil {
+					//				temp := p.buf[p.pos:p.pos+20]
+					//				_ = temp
 					return err
 					return err
 				}
 				}
-				return p.mimeMsg(h, depth)
 			} else {
 			} else {
-				if err = p.body(parent); err != nil {
-					return err
+				fmt.Println("empty header")
+			}
+			if p.ch == '\n' && p.peek() == '\n' {
+				p.next()
+				p.next()
+			}
+
+			if h.contentBoundary != "" {
+				boundary = h.contentBoundary
+				if end, bErr := p.boundary(boundary); bErr != nil {
+					return bErr
+				} else if end {
+					return
+				}
+				err = p.mimeMsg(boundary, depth+"."+strconv.Itoa(count))
+				if err != nil {
+					return
+				}
+			} else {
+				// body-part end
+				for {
+					if end, bErr := p.boundary(boundary); bErr != nil {
+						return bErr
+					} else if end {
+						return
+					}
+
 				}
 				}
 
 
 			}
 			}
+
+			if p.ch == 0 {
+				return
+			}
+
+			if h.contentType == nil {
+				fmt.Println("nope")
+			}
+
+			p.addPart(h, depth+"."+strconv.Itoa(count))
+			if h.contentType != nil &&
+				(h.contentType.superType == "message" ||
+					h.contentType.superType == "multipart") {
+
+				return p.mimeMsg(boundary, depth+"."+strconv.Itoa(count))
+			} else if end, bErr := p.boundary(boundary); bErr != nil {
+				return bErr
+			} else if end {
+				return
+			}
+
 		}
 		}
 
 
 	}
 	}

+ 130 - 35
backends/s_mime_test.go

@@ -2,6 +2,7 @@ package backends
 
 
 import (
 import (
 	"bytes"
 	"bytes"
+	"fmt"
 	"testing"
 	"testing"
 )
 )
 
 
@@ -29,10 +30,6 @@ func TestInject(t *testing.T) {
 }
 }
 func TestMimeType(t *testing.T) {
 func TestMimeType(t *testing.T) {
 
 
-	if isTokenAlphaDash[byte('9')] {
-		t.Error("9 should not be in the set")
-	}
-
 	if isTokenSpecial['-'] {
 	if isTokenSpecial['-'] {
 		t.Error("- should not be in the set")
 		t.Error("- should not be in the set")
 	}
 	}
@@ -101,7 +98,7 @@ This
 	if err != nil {
 	if err != nil {
 		t.Error(err)
 		t.Error(err)
 	}
 	}
-	if err := p.boundary(h); err != nil {
+	if _, err := p.boundary(h.contentBoundary); err != nil {
 		t.Error(err)
 		t.Error(err)
 	} else {
 	} else {
 		//_ = part
 		//_ = part
@@ -115,32 +112,6 @@ This
 	}
 	}
 }
 }
 
 
-func msg() (err error) {
-	main := NewMimeHeader()
-	err = p.header(main)
-	if err != nil {
-		return err
-	}
-	p.addPart(main, "1")
-	if main.contentBoundary != "" {
-		// it's a message with mime parts
-		if err = p.boundary(main); err != nil {
-			return err
-		}
-		if err = p.mimeMsg(main, "1"); err != nil {
-			return err
-		}
-	} else {
-		// only contains one part (the body)
-		if err := p.body(main); err != nil {
-			return err
-		}
-	}
-	p.endBody(main)
-
-	return
-}
-
 func TestBoundary(t *testing.T) {
 func TestBoundary(t *testing.T) {
 	var err error
 	var err error
 	part := NewMimeHeader()
 	part := NewMimeHeader()
@@ -149,7 +120,7 @@ func TestBoundary(t *testing.T) {
 	// in the middle of the string
 	// in the middle of the string
 	p.inject([]byte("The quick brown fo-wololo-x jumped over the lazy dog"))
 	p.inject([]byte("The quick brown fo-wololo-x jumped over the lazy dog"))
 
 
-	err = p.boundary(part)
+	_, err = p.boundary(part.contentBoundary)
 	if err != nil {
 	if err != nil {
 		t.Error(err)
 		t.Error(err)
 	}
 	}
@@ -157,7 +128,7 @@ func TestBoundary(t *testing.T) {
 	//for c := p.next(); c != 0; c= p.next() {} // drain
 	//for c := p.next(); c != 0; c= p.next() {} // drain
 
 
 	p.inject([]byte("The quick brown fox jumped over the lazy dog-wololo-"))
 	p.inject([]byte("The quick brown fox jumped over the lazy dog-wololo-"))
-	err = p.boundary(part)
+	_, err = p.boundary(part.contentBoundary)
 	if err != nil {
 	if err != nil {
 		t.Error(err)
 		t.Error(err)
 	}
 	}
@@ -169,7 +140,7 @@ func TestBoundary(t *testing.T) {
 	p.inject(
 	p.inject(
 		[]byte("The quick brown fox jumped ov-wolo"),
 		[]byte("The quick brown fox jumped ov-wolo"),
 		[]byte("lo-er the lazy dog"))
 		[]byte("lo-er the lazy dog"))
-	err = p.boundary(part)
+	_, err = p.boundary(part.contentBoundary)
 	if err != nil {
 	if err != nil {
 		t.Error(err)
 		t.Error(err)
 	}
 	}
@@ -180,7 +151,7 @@ func TestBoundary(t *testing.T) {
 		[]byte("this is the middle"),
 		[]byte("this is the middle"),
 		[]byte("and thats the end-wololo-"))
 		[]byte("and thats the end-wololo-"))
 
 
-	err = p.boundary(part)
+	_, err = p.boundary(part.contentBoundary)
 	if err != nil {
 	if err != nil {
 		t.Error(err)
 		t.Error(err)
 	}
 	}
@@ -231,3 +202,127 @@ func TestMimeContentQuotedParams(t *testing.T) {
 	}
 	}
 
 
 }
 }
+
+func msg() (err error) {
+	main := NewMimeHeader()
+	err = p.header(main)
+	if err != nil {
+		return err
+	}
+	p.addPart(main, "1")
+
+	if main.contentBoundary != "" {
+		// it's a message with mime parts
+
+		if end, bErr := p.boundary(main.contentBoundary); bErr != nil {
+			return bErr
+		} else if end {
+			return
+		}
+
+		if err = p.mimeMsg("", "1"); err != nil {
+			return err
+		}
+	} else {
+		// only contains one part (the body)
+		if err := p.body(main); err != nil {
+			return err
+		}
+	}
+	p.endBody(main)
+
+	return
+}
+
+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 TestNestedEmail(t *testing.T) {
+	p.inject([]byte(email))
+
+	if err := p.mime("", "1"); err != nil {
+		t.Error(err)
+	}
+	for part := range p.parts {
+		fmt.Println(p.parts[part].part, " ", p.parts[part].contentType)
+	}
+
+}