12	"github.com/mjl-/bstore"
 
14	"github.com/mjl-/mox/message"
 
15	"github.com/mjl-/mox/mlog"
 
16	"github.com/mjl-/mox/moxio"
 
17	"github.com/mjl-/mox/store"
 
20// rejectPresent returns whether the message is already present in the rejects mailbox.
 
21func rejectPresent(log mlog.Log, acc *store.Account, rejectsMailbox string, m *store.Message, f *os.File) (present bool, msgID string, hash []byte, rerr error) {
 
22	if p, err := message.Parse(log.Logger, false, store.FileMsgReader(m.MsgPrefix, f)); err != nil {
 
23		log.Infox("parsing reject message for message-id", err)
 
24	} else if header, err := p.Header(); err != nil {
 
25		log.Infox("parsing reject message header for message-id", err)
 
27		msgID, _, err = message.MessageIDCanonical(header.Get("Message-Id"))
 
29			log.Debugx("parsing message-id for reject", err, slog.String("messageid", header.Get("Message-Id")))
 
33	// We must not read MsgPrefix, it will likely change for subsequent deliveries.
 
35	if _, err := io.Copy(h, &moxio.AtReader{R: f}); err != nil {
 
36		log.Infox("copying reject message to hash", err)
 
41	if msgID == "" && len(hash) == 0 {
 
42		return false, "", nil, fmt.Errorf("no message-id or hash for determining reject message presence")
 
47	acc.WithRLock(func() {
 
48		err = acc.DB.Read(context.TODO(), func(tx *bstore.Tx) error {
 
49			mbq := bstore.QueryTx[store.Mailbox](tx)
 
50			mbq.FilterNonzero(store.Mailbox{Name: rejectsMailbox})
 
52			if err == bstore.ErrAbsent {
 
56				return fmt.Errorf("looking for rejects mailbox: %w", err)
 
59			q := bstore.QueryTx[store.Message](tx)
 
60			q.FilterNonzero(store.Message{MailboxID: mb.ID})
 
61			q.FilterEqual("Expunged", false)
 
62			q.FilterFn(func(m store.Message) bool {
 
63				return msgID != "" && m.MessageID == msgID || len(hash) > 0 && bytes.Equal(m.MessageHash, hash)
 
65			exists, err = q.Exists()
 
70		return false, "", nil, fmt.Errorf("querying for presence of reject message: %w", err)
 
72	return exists, msgID, hash, nil