~rjarry/aerc

models: pad message uids with zeroes

IMAP message UIDs are 32 bits unsigned integers. All other backends use
opaque strings without any specific ordering. The UIDs are used for
ordering only for IMAP.

Pad the integer uids with zeroes when converting them to strings to
ensure ASCII sorting will work as previously.

Fixes: 73dc39c6ee08 ("treewide: replace uint32 uids with opaque strings")
Reported-by: Fedor Pchelkin <boddah8794@gmail.com>
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Jens Grassel <jens@wegtam.com>
calendar: skip editor when replying to invitations

Most of the time, accepting an invitation does not require editing the
automatically generated email before sending it. Allow the user to
optionally skip the editor entirely and go directly to the review screen
when dealing with invitations.

References: https://todo.sr.ht/~rjarry/aerc/247
Signed-off-by: inwit <inwit@sindominio.net>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
dirtree: fix dirlist-collapse=0 regression

Ensure not to collapse any folder when dirlist-collapse=0.

Fixes: 73dc39c6ee08 ("treewide: replace uint32 uids with opaque strings")
Reported-by: Tim Culverhouse <tim@timculverhouse.com>
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
templates/quote: only prefix quoted lines with '>'

Prefix all quoted lines with depth > 1 with '>'. Quoted lines of depth 1
are quoted with "> ". This follows conventions of mailing lists which
collapse quote depths without spaces. For example:

    >>> Quote depth 3
    > Quote depth 1
    >> Quote depth 2

Changelog-changed: Template function `quote` only prefixes with a
 space if at quote depth 1.
Requested-by: Isaac Freund <mail@isaacfreund.com>
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
lint: update golangci-lint to 1.61.0

golangci-lint 1.56 does not work with go 1.23. It causes obscure errors:

[linters_context/goanalysis] buildir: panic during analysis:
Cannot range over: func(yield func(K, V) bool), goroutine 4743 [running]: runtime/debug.Stack()
	/usr/lib/go/src/runtime/debug/stack.go:26 +0x5e
github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyzeSafe.func1()
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:104 +0x5a
panic({0x164b260?, 0xc00669b4a0?})
	/usr/lib/go/src/runtime/panic.go:785 +0x132
honnef.co/go/tools/go/ir.(*builder).rangeStmt(0xc000051910, 0xc00a29cf00, 0xc009bf55c0, 0x0, {0x1af1960, 0xc009bf55c0})
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2214 +0x894
honnef.co/go/tools/go/ir.(*builder).stmt(0xc000051910, 0xc00a29cf00, {0x1af6970?, 0xc009bf55c0?})
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2427 +0x20a
honnef.co/go/tools/go/ir.(*builder).stmtList(...)
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:847
honnef.co/go/tools/go/ir.(*builder).stmt(0xc000051910, 0xc00a29cf00, {0x1af6880?, 0xc004f52ed0?})
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2385 +0x1415
honnef.co/go/tools/go/ir.(*builder).buildFunction(0xc000051910, 0xc00a29cf00)
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2497 +0x417
honnef.co/go/tools/go/ir.(*builder).buildFuncDecl(0xc000051910, 0xc00622eea0, 0xc004f52f00)
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2534 +0x189
honnef.co/go/tools/go/ir.(*Package).build(0xc00622eea0)
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2638 +0xb46
sync.(*Once).doSlow(0xc009b81260?, 0xc009bf5bc0?)
	/usr/lib/go/src/sync/once.go:76 +0xb4
sync.(*Once).Do(...)
	/usr/lib/go/src/sync/once.go:67
honnef.co/go/tools/go/ir.(*Package).Build(...)
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2556
honnef.co/go/tools/internal/passes/buildir.run(0xc000cf61a0)
	/home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/internal/passes/buildir/buildir.go:86 +0x18b
github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyze(0xc002d77d70)
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:190 +0x9cd
github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyzeSafe.func2()
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:112 +0x17
github.com/golangci/golangci-lint/pkg/timeutils.(*Stopwatch).TrackStage(0xc0007a5c70, {0x1859190, 0x7}, 0xc001c28f48)
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/timeutils/stopwatch.go:111 +0x44
github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyzeSafe(0xc00212f680?)
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:111 +0x6e
github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*loadingPackage).analyze.func2(0xc002d77d70)
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_loadingpackage.go:80 +0xa5
created by github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*loadingPackage).analyze in goroutine 3468
	/home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_loadingpackage.go:75 +0x1e9

Update golangci-lint to 1.61.0 that works with go 1.23. It has new
checkers that report errors that we need to fix:

lib/crypto/gpg/gpgbin/gpgbin.go:226:22: printf: non-constant format string in call to fmt.Errorf (govet)
			return fmt.Errorf(strings.TrimPrefix(line, "[GNUPG:] "))
			                  ^
worker/imap/observer.go:142:21: printf: non-constant format string in call to fmt.Errorf (govet)
		Error: fmt.Errorf(errMsg),
		                  ^
app/dirlist.go:409:5: S1009: should omit nil check; len() for []string is defined as zero (gosimple)
	if dirlist.dirs == nil || len(dirlist.dirs) == 0 {
	   ^
app/dirtree.go:181:5: S1009: should omit nil check; len() for []*git.sr.ht/~rjarry/aerc/worker/types.Thread is defined as zero (gosimple)
	if dt.list == nil || len(dt.list) == 0 || dt.countVisible(dt.list) < y+dt.Scroll() {
	   ^
app/authinfo.go:30:34: printf: non-constant format string in call to (*git.sr.ht/~rjarry/aerc/lib/ui.Context).Printf (govet)
		ctx.Printf(0, 0, defaultStyle, text)
		                               ^
app/authinfo.go:34:27: printf: non-constant format string in call to (*git.sr.ht/~rjarry/aerc/lib/ui.Context).Printf (govet)
		ctx.Printf(0, 0, style, text)
		                        ^
app/authinfo.go:62:34: printf: non-constant format string in call to (*git.sr.ht/~rjarry/aerc/lib/ui.Context).Printf (govet)
				x += ctx.Printf(x, 0, style, text)
				                             ^

Pretty much all of these errors are us passing non-const format strings
to various methods. In C land, this is a large security issue. I would
assume the same stands in Go. Thank you golangci-lint!

Link: https://builds.sr.ht/~rjarry/job/1332376#task-validate-500
Signed-off-by: Tristan Partin <tristan@partin.io>
Acked-by: Robin Jarry <robin@jarry.cc>
commands: add an echo command

Currently if you want to explore what templates resolve to, it's a bit
of pain. Add an echo command that doesn't do anything, other than prints
the string that is its argument with the templates resolved.

Implements: https://todo.sr.ht/~rjarry/aerc/277
Changelog-added: New `:echo` command that prints its arguments with
 templates resolved.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
Reviewed-by: Tristan Partin <tristan@partin.io>
Acked-by: Robin Jarry <robin@jarry.cc>
mailmap: fix email typo for tim

This breaks my git-people script.

Link: https://git.sr.ht/~rjarry/dotfiles/tree/main/item/bin/git-people
Signed-off-by: Robin Jarry <robin@jarry.cc>
treewide: replace uint32 uids with opaque strings

Add a new models.UID type (an alias to string). Replace all occurrences
of uint32 being used as message UID or thread UID with models.UID.

Update all workers to only expose models.UID values and deal with the
conversion internally. Only IMAP needs to convert these to uint32. All
other backends already use plain strings as message identifiers, in
which case no conversion is even needed.

The directory tree implementation needed to be heavily refactored in
order to accommodate thread UID not being usable as a list index.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Inwit <inwit@sindominio.net>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
reload: reload everything if no flags are provided

Currently, the :reload command silently does nothing if no flags are
provided. Eliminate this confusion by reloading all reloadable configs
in the absence of flags.

Signed-off-by: Jason Cox <me@jasoncarloscox.com>
Reviewed-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>

imap: fix SeqMap.Pop runtime error

Fix a runtime error in the SeqMap.Pop function causing a
index-out-of-range panic:

Version: 0.18.2.r22.gfff69046 (go1.22.6 amd64 linux 2024-08-10)
Error: runtime error: index out of range [487] with length 487

goroutine 24430 [running]:
runtime/debug.Stack()
	runtime/debug/stack.go:24 +0x5e
git.sr.ht/~rjarry/aerc/lib/log.PanicHandler()
	git.sr.ht/~rjarry/aerc/lib/log/panic-logger.go:49 +0x66a
panic({0x5dbf5a688020?, 0xc002ab0d80?})
	runtime/panic.go:770 +0x132
git.sr.ht/~rjarry/aerc/worker/imap.(*SeqMap).Pop(0xc0003d4940, 0x1e8)
	git.sr.ht/~rjarry/aerc/worker/imap/seqmap.go:64 +0x17c
git.sr.ht/~rjarry/aerc/worker/imap.(*IMAPWorker).handleImapUpdate(0xc0003d4820, {0x5dbf5a6dbd00, 0xc002068708})
	git.sr.ht/~rjarry/aerc/worker/imap/worker.go:296 +0x26c
git.sr.ht/~rjarry/aerc/worker/imap.(*IMAPWorker).drainUpdates.func1()
	git.sr.ht/~rjarry/aerc/worker/imap/flags.go:29 +0x10c
created by git.sr.ht/~rjarry/aerc/worker/imap.(*IMAPWorker).drainUpdates in goroutine 52
	git.sr.ht/~rjarry/aerc/worker/imap/flags.go:21 +0x78

SeqMap.Pop uses two locks in the same function: first lock grabs the
size of the slice, second lock removes the given index in the slice.
Combine the two locks to prevent a change of the slice size before the
index can be removed.

Reported-by: Moritz Poldrack <moritz@poldrack.dev>
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>

patch: add auto-switch option

Add an auto-switch option that changes the project of the patch manager
based on the subject line of a message if it contains a '[PATCH
<project>]' segment.

A subject line with '[PATCH aerc v2]' would switch to the 'aerc' project
if that project is available in the patch manager.

The auto switching can be activated per account by adding
'pama-auto-switch = true' to your account config.

Implements: https://todo.sr.ht/~rjarry/aerc/226
Changelog-added: Auto-switch projects based on the message subject
 for the :patch command.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>

gpg: fix signed message encoding

Fix the content encoding for GPG-signed messages.

To remove the Mime-Version header field for the signed message part, the
raw message is parsed with go-message. go-message.Read(), however,
decodes the message body as well (i.e. from quoted-printable to UTF8
depending on the Content-Transfer-Encoding header). This means that the
msg.Body field now contains the decoded message (it is no longer encoded
as quoted-printable). We never encode the message back to the proper
Content-Transfer-Encoding.

To fix this, use net/mail.ReadMessage() to parse the headers and to not
decode the message body.

To verify the issue, send a signed message with the following text:
"19+1=20!"

The message will be properly signed, but the text is wrong; it shows
"19+1 !"; instead it should read "19+1=3D20!".

Fixes: 5e443bce ("gpg: fix mime-version header position")
References: https://todo.sr.ht/~rjarry/aerc/79
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Jens Grassel <jens@wegtam.com>
Acked-by: Robin Jarry <robin@jarry.cc>

forward: better preserve attached file names

Use a dedicated filename function for getting a name of the attached
file instad of analyzing its mime type. Some attachments have file name
in `Content-Disposition` rather than `Content-Type`, the new method
handles both cases.

Signed-off-by: Vitaly Ovchinnikov <v@ovch.ru>
Acked-by: Robin Jarry <robin@jarry.cc>

jmap: fetch created messages and set recent flag

When a push notification arrives, automatically fetch any newly created
messages. Set the Recent flag on these messages to trigger a
notification in the UI.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>

notmuch: fix watcher path

The watcher path does not necessarily contain ".notmuch". From
notmuch-config(1):
Notmuch will store its database here,
(in sub-directory named .notmuch if database.mail_root is unset).

So we can simply check if the ".notmuch" folder exists and fallback to
the default path if it does not.

Signed-off-by: Thorben Günther <admin@xenrox.net>
Acked-by: Robin Jarry <robin@jarry.cc>

ircbot: sanitize email subjects

Email subjects may contain line breaks if they are too long to fit in 72
columns. The supybot library does not support sending messages with line
breaks. It raises an error. Replace all consecutive "white space"
characters (including \r and \n) with regular space characters.

Signed-off-by: Robin Jarry <robin@jarry.cc>
commands: add reload

Add the reload command that performs a reload of config files. The
reload command supports reloading the binds.conf and aerc.conf config
files.

Reloading will reload account views (including the directory list),
message viewers, and composers.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Robin Jarry <robin@jarry.cc>

store: extract configure logic

Extract a function to configure the message store from its constructor
to reconfigure the store when the data changes.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Robin Jarry <robin@jarry.cc>

account: update split views

Update split views on reload. Use the safe acct.SelectedMessage()
instead of acct.msglist.Selected() which can panic if msglist.store is
nil.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Robin Jarry <robin@jarry.cc>

account: extract configure logic

Extract a function to configure the account view from the constructor;
rebuild the grid whenever the config data changed on a hot-reload.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Robin Jarry <robin@jarry.cc>

Next