diff --git a/internal/smtp/http.go b/internal/smtp/http.go index d610265..e752081 100644 --- a/internal/smtp/http.go +++ b/internal/smtp/http.go @@ -101,14 +101,13 @@ func (h *smtpHTTPHandler) handleMessageIndex(w http.ResponseWriter, r *http.Requ if headerSearchRgx == nil { receivedMessages = h.server.ReceivedMessages() } else { - // FIXME: This does not preserve the correct indexes! receivedMessages = h.server.SearchByHeader(headerSearchRgx) } messagesOut := make([]any, 0) - for i, msg := range receivedMessages { - messagesOut = append(messagesOut, buildMessageBasicMeta(msg, i)) + for _, msg := range receivedMessages { + messagesOut = append(messagesOut, buildMessageBasicMeta(msg)) } out := make(map[string]any) @@ -124,7 +123,7 @@ func (h *smtpHTTPHandler) handleMessageMeta(w http.ResponseWriter, r *http.Reque return } - out := buildMessageBasicMeta(msg, idx) + out := buildMessageBasicMeta(msg) out["body_size"] = len(msg.Body()) @@ -169,14 +168,13 @@ func (h *smtpHTTPHandler) handleMultipartIndex(w http.ResponseWriter, r *http.Re if headerSearchRgx == nil { multiparts = msg.Multiparts() } else { - // FIXME: This does not preserve the correct indexes! multiparts = msg.SearchPartsByHeader(headerSearchRgx) } multipartsOut := make([]any, 0) - for i, part := range multiparts { - multipartsOut = append(multipartsOut, buildMultipartMeta(part, i)) + for _, part := range multiparts { + multipartsOut = append(multipartsOut, buildMultipartMeta(part)) } out := make(map[string]any) @@ -201,7 +199,7 @@ func (h *smtpHTTPHandler) handleMultipartMeta( return } - handlerutil.RespondWithJSON(w, http.StatusOK, buildMultipartMeta(part, partIdx)) + handlerutil.RespondWithJSON(w, http.StatusOK, buildMultipartMeta(part)) } func (h *smtpHTTPHandler) handleMultipartBody( @@ -256,16 +254,16 @@ func retrievePart(w http.ResponseWriter, msg *ReceivedMessage, partIdx int) *Rec return msg.Multiparts()[partIdx] } -func buildMessageBasicMeta(msg *ReceivedMessage, idx int) map[string]any { +func buildMessageBasicMeta(msg *ReceivedMessage) map[string]any { return map[string]any{ - "idx": idx, + "idx": msg.Index(), "receivedAt": msg.ReceivedAt(), } } -func buildMultipartMeta(part *ReceivedPart, partIdx int) map[string]any { +func buildMultipartMeta(part *ReceivedPart) map[string]any { out := map[string]any{ - "idx": partIdx, + "idx": part.Index(), "body_size": len(part.Body()), } diff --git a/internal/smtp/message.go b/internal/smtp/message.go index 08ce2bc..848e916 100644 --- a/internal/smtp/message.go +++ b/internal/smtp/message.go @@ -16,6 +16,8 @@ import ( // ReceivedMessage contains a single email message as received via SMTP. type ReceivedMessage struct { + index int + smtpFrom string smtpRcptTo []string rawMessageData []byte @@ -34,6 +36,8 @@ type ReceivedMessage struct { // ReceivedPart contains a single part of a multipart message as received // via SMTP. type ReceivedPart struct { + index int + headers textproto.MIMEHeader body []byte } @@ -45,6 +49,7 @@ type ReceivedPart struct { // If a maxMessageSize of 0 is given, this function will default to using // DefaultMaxMessageSize. func NewReceivedMessage( + index int, from string, rcptTo []string, rawMessageData []byte, receivedAt time.Time, maxMessageSize int64, ) (*ReceivedMessage, error) { @@ -63,6 +68,7 @@ func NewReceivedMessage( } msg := &ReceivedMessage{ + index: index, smtpFrom: from, smtpRcptTo: rcptTo, rawMessageData: rawMessageData, @@ -92,7 +98,7 @@ func NewReceivedMessage( r := multipart.NewReader(bytes.NewReader(msg.body), boundary) - for { + for i := 0; ; i++ { rawPart, err := r.NextPart() if err != nil { if errors.Is(err, io.EOF) { @@ -102,7 +108,7 @@ func NewReceivedMessage( } } - part, err := NewReceivedPart(rawPart, maxMessageSize) + part, err := NewReceivedPart(i, rawPart, maxMessageSize) if err != nil { return nil, fmt.Errorf("could not parse message part: %w", err) } @@ -150,7 +156,7 @@ func (m *ReceivedMessage) SearchPartsByHeader(re *regexp.Regexp) []*ReceivedPart // Incoming data is truncated after the given maximum message size. // If a maxMessageSize of 0 is given, this function will default to using // DefaultMaxMessageSize. -func NewReceivedPart(p *multipart.Part, maxMessageSize int64) (*ReceivedPart, error) { +func NewReceivedPart(index int, p *multipart.Part, maxMessageSize int64) (*ReceivedPart, error) { if maxMessageSize == 0 { maxMessageSize = DefaultMaxMessageSize } @@ -161,6 +167,7 @@ func NewReceivedPart(p *multipart.Part, maxMessageSize int64) (*ReceivedPart, er } part := &ReceivedPart{ + index: index, headers: p.Header, body: body, } @@ -184,6 +191,10 @@ func (m *ReceivedMessage) Headers() mail.Header { return m.headers } +func (m *ReceivedMessage) Index() int { + return m.index +} + func (m *ReceivedMessage) IsMultipart() bool { return m.isMultipart } @@ -215,3 +226,7 @@ func (p *ReceivedPart) Body() []byte { func (p *ReceivedPart) Headers() textproto.MIMEHeader { return p.headers } + +func (p *ReceivedPart) Index() int { + return p.index +} diff --git a/internal/smtp/server.go b/internal/smtp/server.go index b041849..0591a1b 100644 --- a/internal/smtp/server.go +++ b/internal/smtp/server.go @@ -153,10 +153,13 @@ func (s *session) Data(r io.Reader) error { s.server.mutex.Lock() defer s.server.mutex.Unlock() + idx := len(s.server.receivedMessages) now := time.Now() logrus.Infof("SMTP: Receiving message from %s to %v at %v", s.from, s.rcptTo, now) - msg, err := NewReceivedMessage(s.from, s.rcptTo, rawData, now, s.server.maxMessageSize) + msg, err := NewReceivedMessage( + idx, s.from, s.rcptTo, rawData, now, s.server.maxMessageSize, + ) if err != nil { errWrapped := fmt.Errorf("error constructing ReceivedMessage in SMTP server: %w", err) logrus.Error("SMTP:", errWrapped) // this is logged in our server