118 lines
2.7 KiB
Go
118 lines
2.7 KiB
Go
package backendutil
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"mime"
|
|
"strings"
|
|
|
|
"github.com/emersion/go-imap"
|
|
"github.com/emersion/go-message/textproto"
|
|
)
|
|
|
|
type countReader struct {
|
|
r io.Reader
|
|
bytes uint32
|
|
newlines uint32
|
|
endsWithLF bool
|
|
}
|
|
|
|
func (r *countReader) Read(b []byte) (int, error) {
|
|
n, err := r.r.Read(b)
|
|
r.bytes += uint32(n)
|
|
if n != 0 {
|
|
r.newlines += uint32(bytes.Count(b[:n], []byte{'\n'}))
|
|
r.endsWithLF = b[n-1] == '\n'
|
|
}
|
|
// If the stream does not end with a newline - count missing newline.
|
|
if err == io.EOF {
|
|
if !r.endsWithLF {
|
|
r.newlines++
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// FetchBodyStructure computes a message's body structure from its content.
|
|
func FetchBodyStructure(header textproto.Header, body io.Reader, extended bool) (*imap.BodyStructure, error) {
|
|
bs := new(imap.BodyStructure)
|
|
|
|
mediaType, mediaParams, err := mime.ParseMediaType(header.Get("Content-Type"))
|
|
if err == nil {
|
|
typeParts := strings.SplitN(mediaType, "/", 2)
|
|
bs.MIMEType = typeParts[0]
|
|
if len(typeParts) == 2 {
|
|
bs.MIMESubType = typeParts[1]
|
|
}
|
|
bs.Params = mediaParams
|
|
} else {
|
|
bs.MIMEType = "text"
|
|
bs.MIMESubType = "plain"
|
|
}
|
|
|
|
bs.Id = header.Get("Content-Id")
|
|
bs.Description = header.Get("Content-Description")
|
|
bs.Encoding = header.Get("Content-Transfer-Encoding")
|
|
|
|
if mr := multipartReader(header, body); mr != nil {
|
|
var parts []*imap.BodyStructure
|
|
for {
|
|
p, err := mr.NextPart()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pbs, err := FetchBodyStructure(p.Header, p, extended)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parts = append(parts, pbs)
|
|
}
|
|
bs.Parts = parts
|
|
} else {
|
|
countedBody := countReader{r: body}
|
|
needLines := false
|
|
if bs.MIMEType == "message" && bs.MIMESubType == "rfc822" {
|
|
// This will result in double-buffering if body is already a
|
|
// bufio.Reader (most likely it is). :\
|
|
bufBody := bufio.NewReader(&countedBody)
|
|
subMsgHdr, err := textproto.ReadHeader(bufBody)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bs.Envelope, err = FetchEnvelope(subMsgHdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bs.BodyStructure, err = FetchBodyStructure(subMsgHdr, bufBody, extended)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
needLines = true
|
|
} else if bs.MIMEType == "text" {
|
|
needLines = true
|
|
}
|
|
if _, err := io.Copy(ioutil.Discard, &countedBody); err != nil {
|
|
return nil, err
|
|
}
|
|
bs.Size = countedBody.bytes
|
|
if needLines {
|
|
bs.Lines = countedBody.newlines
|
|
}
|
|
}
|
|
|
|
if extended {
|
|
bs.Extended = true
|
|
bs.Disposition, bs.DispositionParams, _ = mime.ParseMediaType(header.Get("Content-Disposition"))
|
|
|
|
// TODO: bs.Language, bs.Location
|
|
// TODO: bs.MD5
|
|
}
|
|
|
|
return bs, nil
|
|
}
|