|
@@ -1,14 +1,25 @@
|
|
|
package backends
|
|
|
|
|
|
import (
|
|
|
- "bufio"
|
|
|
- "bytes"
|
|
|
- "io"
|
|
|
- "net/textproto"
|
|
|
-
|
|
|
"github.com/flashmob/go-guerrilla/mail"
|
|
|
+ "github.com/flashmob/go-guerrilla/mail/mime"
|
|
|
)
|
|
|
|
|
|
+// ----------------------------------------------------------------------------------
|
|
|
+// Processor Name: HeadersParser
|
|
|
+// ----------------------------------------------------------------------------------
|
|
|
+// Description : Populates the envelope.Header value
|
|
|
+//-----------------------------------------------------------------------------------
|
|
|
+// Requires : "mime" stream stream processor to be enabled before it
|
|
|
+// ----------------------------------------------------------------------------------
|
|
|
+// Config Options: None
|
|
|
+// --------------:-------------------------------------------------------------------
|
|
|
+// Input : e.Values["MimeParts"] generated by the mime processor
|
|
|
+// ----------------------------------------------------------------------------------
|
|
|
+// Output : populates e.Header and e.Subject values of the envelope.
|
|
|
+// : Any encoded data in the subject is decoded to UTF-8
|
|
|
+// ----------------------------------------------------------------------------------
|
|
|
+
|
|
|
func init() {
|
|
|
streamers["headersparser"] = func() *StreamDecorator {
|
|
|
return StreamHeadersParser()
|
|
@@ -17,122 +28,48 @@ func init() {
|
|
|
|
|
|
const stateHeaderScanning = 0
|
|
|
const stateHeaderNotScanning = 1
|
|
|
-const headerMaxBytes = 1024 * 4
|
|
|
-
|
|
|
-type streamHeaderConfig struct {
|
|
|
- MaxBytes int64 `json:"s_header_max_bytes,omitempty"`
|
|
|
-}
|
|
|
|
|
|
func StreamHeadersParser() *StreamDecorator {
|
|
|
- var config *streamHeaderConfig
|
|
|
- initFunc := InitializeWith(func(backendConfig BackendConfig) error {
|
|
|
- configType := BaseConfig(&streamHeaderConfig{})
|
|
|
- bcfg, err := Svc.ExtractConfig(backendConfig, configType)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- config = bcfg.(*streamHeaderConfig)
|
|
|
- if config.MaxBytes == 0 {
|
|
|
- config.MaxBytes = headerMaxBytes
|
|
|
- }
|
|
|
- return nil
|
|
|
- })
|
|
|
- Svc.AddInitializer(initFunc)
|
|
|
+
|
|
|
sd := &StreamDecorator{}
|
|
|
sd.p =
|
|
|
-
|
|
|
func(sp StreamProcessor) StreamProcessor {
|
|
|
-
|
|
|
var (
|
|
|
- buf bytes.Buffer // buf buffers the header
|
|
|
state byte
|
|
|
- lastByte byte
|
|
|
- total int64
|
|
|
envelope *mail.Envelope
|
|
|
)
|
|
|
|
|
|
- parse := func() error {
|
|
|
- var err error
|
|
|
- // use a TeeReader to split the write to both sp and headerReader
|
|
|
- r := bufio.NewReader(io.TeeReader(&buf, sp))
|
|
|
- headerReader := textproto.NewReader(r)
|
|
|
- envelope.Header, err = headerReader.ReadMIMEHeader()
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- if subject, ok := envelope.Header["Subject"]; ok {
|
|
|
- envelope.Subject = mail.MimeHeaderDecode(subject[0])
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return err
|
|
|
- }
|
|
|
sd.Open = func(e *mail.Envelope) error {
|
|
|
- buf.Reset()
|
|
|
- state = 0
|
|
|
- lastByte = 0
|
|
|
- total = 0
|
|
|
+ state = stateHeaderScanning
|
|
|
envelope = e
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
sd.Close = func() error {
|
|
|
- // If header wasn't detected
|
|
|
- // pump out whatever is in the buffer to the underlying writer
|
|
|
- if state == stateHeaderScanning {
|
|
|
- _, err := io.Copy(sp, &buf)
|
|
|
- return err
|
|
|
- }
|
|
|
return nil
|
|
|
}
|
|
|
- return StreamProcessWith(func(p []byte) (int, error) {
|
|
|
|
|
|
+ return StreamProcessWith(func(p []byte) (int, error) {
|
|
|
switch state {
|
|
|
case stateHeaderScanning:
|
|
|
- // detect end of header \n\n
|
|
|
- headerEnd := bytes.Index(p, []byte{'\n', '\n'})
|
|
|
- if headerEnd == -1 && (lastByte == '\n' && p[0] == '\n') {
|
|
|
- headerEnd = 0
|
|
|
- }
|
|
|
- var remainder []byte // remainder are the non-header bytes after the \n\n
|
|
|
- if headerEnd > -1 {
|
|
|
-
|
|
|
- if len(p) > headerEnd {
|
|
|
- remainder = p[headerEnd:]
|
|
|
+ if mimeParts, ok := envelope.Values["MimeParts"].(*[]*mime.Part); ok {
|
|
|
+ // copy the the headers of the first mime-part to envelope.Header
|
|
|
+ // then call envelope.ParseHeaders()
|
|
|
+ if len(*mimeParts) > 0 {
|
|
|
+ headers := (*mimeParts)[0].Headers
|
|
|
+ if headers != nil && len(headers) > 0 {
|
|
|
+ state = stateHeaderNotScanning
|
|
|
+ envelope.Header = headers
|
|
|
+ _ = envelope.ParseHeaders()
|
|
|
+ }
|
|
|
}
|
|
|
- p = p[:headerEnd]
|
|
|
}
|
|
|
-
|
|
|
- // read in the header to a temp buffer
|
|
|
- n, err := io.Copy(&buf, bytes.NewReader(p))
|
|
|
- lastByte = p[n-1] // remember the last byte read
|
|
|
- if headerEnd > -1 {
|
|
|
- // header found, parse it
|
|
|
- parseErr := parse()
|
|
|
- if parseErr != nil {
|
|
|
- Log().WithError(parseErr).Error("cannot parse headers")
|
|
|
- }
|
|
|
- // flush the remainder to the underlying writer
|
|
|
- if remainder != nil {
|
|
|
- n1, _ := sp.Write(remainder)
|
|
|
- n = n + int64(n1)
|
|
|
- }
|
|
|
- state = stateHeaderNotScanning
|
|
|
- } else {
|
|
|
- total += n
|
|
|
- // give up if we didn't detect the header after x bytes
|
|
|
- if total > config.MaxBytes {
|
|
|
- state = stateHeaderNotScanning
|
|
|
- n, err = io.Copy(sp, &buf)
|
|
|
- return int(n), err
|
|
|
- }
|
|
|
- }
|
|
|
- return int(n), err
|
|
|
- case stateHeaderNotScanning:
|
|
|
- // just forward everything to the next writer without buffering
|
|
|
return sp.Write(p)
|
|
|
}
|
|
|
-
|
|
|
+ // state is stateHeaderNotScanning
|
|
|
+ // just forward everything to the underlying writer
|
|
|
return sp.Write(p)
|
|
|
+
|
|
|
})
|
|
|
}
|
|
|
|