This is the v1 version, had the v2 before.
This commit is contained in:
117
backend/backendutil/bodystructure.go
Normal file
117
backend/backendutil/bodystructure.go
Normal file
@@ -0,0 +1,117 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user