2023-01-30 16:27:06 +03:00
// Package imapserver implements an IMAPv4 server, rev2 (RFC 9051) and rev1 with extensions (RFC 3501 and more).
package imapserver
/ *
Implementation notes
IMAP4rev2 includes functionality that was in extensions for IMAP4rev1 . The
extensions sometimes include features not in IMAP4rev2 . We want IMAP4rev1 - only
implementations to use extensions , so we implement the full feature set of the
extension and announce it as capability . The extensions : LITERAL + , IDLE ,
NAMESPACE , BINARY , UNSELECT , UIDPLUS , ESEARCH , SEARCHRES , SASL - IR , ENABLE ,
LIST - EXTENDED , SPECIAL - USE , MOVE , UTF8 = ONLY .
We take a liberty with UTF8 = ONLY . We are supposed to wait for ENABLE of
UTF8 = ACCEPT or IMAP4rev2 before we respond with quoted strings that contain
non - ASCII UTF - 8. But we will unconditionally accept UTF - 8 at the moment . See
. . / rfc / 6855 : 251
We always respond with utf8 mailbox names . We do parse utf7 ( only in IMAP4rev1 ,
not in IMAP4rev2 ) . . . / rfc / 3501 : 964
- We never execute multiple commands at the same time for a connection . We expect a client to open multiple connections instead . . . / rfc / 9051 : 1110
- Do not write output on a connection with an account lock held . Writing can block , a slow client could block account operations .
- When handling commands that modify the selected mailbox , always check that the mailbox is not opened readonly . And always revalidate the selected mailbox , another session may have deleted the mailbox .
- After making changes to an account / mailbox / message , you must broadcast changes . You must do this with the account lock held . Otherwise , other later changes ( e . g . message deliveries ) may be made and broadcast before changes that were made earlier . Make sure to commit changes in the database first , because the commit may fail .
- Mailbox hierarchies are slash separated , no leading slash . We keep the case , except INBOX is renamed to Inbox , also for submailboxes in INBOX . We don ' t allow existence of a child where its parent does not exist . We have no \ NoInferiors or \ NoSelect . Newly created mailboxes are automatically subscribed .
2023-07-24 22:21:05 +03:00
- For CONDSTORE and QRESYNC support , we set "modseq" for each change / expunge . Once expunged , a modseq doesn ' t change anymore . We don ' t yet remove old expunged records . The records aren ' t too big . Next step may be to let an admin reclaim space manually .
2023-01-30 16:27:06 +03:00
* /
/ *
- todo : do not return binary data for a fetch body . at least not for imap4rev1 . we should be encoding it as base64 ?
- todo : on expunge we currently remove the message even if other sessions still have a reference to the uid . if they try to query the uid , they ' ll get an error . we could be nicer and only actually remove the message when the last reference has gone . we could add a new flag to store . Message marking the message as expunged , not give new session access to such messages , and make store remove them at startup , and clean them when the last session referencing the session goes . however , it will get much more complicated . renaming messages would need special handling . and should we do the same for removed mailboxes ?
- todo : try to recover from syntax errors when the last command line ends with a } , i . e . a literal . we currently abort the entire connection . we may want to read some amount of literal data and continue with a next command .
implement message threading in backend and webmail
we match messages to their parents based on the "references" and "in-reply-to"
headers (requiring the same base subject), and in absense of those headers we
also by only base subject (against messages received max 4 weeks ago).
we store a threadid with messages. all messages in a thread have the same
threadid. messages also have a "thread parent ids", which holds all id's of
parent messages up to the thread root. then there is "thread missing link",
which is set when a referenced immediate parent wasn't found (but possibly
earlier ancestors can still be found and will be in thread parent ids".
threads can be muted: newly delivered messages are automatically marked as
read/seen. threads can be marked as collapsed: if set, the webmail collapses
the thread to a single item in the basic threading view (default is to expand
threads). the muted and collapsed fields are copied from their parent on
message delivery.
the threading is implemented in the webmail. the non-threading mode still works
as before. the new default threading mode "unread" automatically expands only
the threads with at least one unread (not seen) meessage. the basic threading
mode "on" expands all threads except when explicitly collapsed (as saved in the
thread collapsed field). new shortcuts for navigation/interaction threads have
been added, e.g. go to previous/next thread root, toggle collapse/expand of
thread (or double click), toggle mute of thread. some previous shortcuts have
changed, see the help for details.
the message threading are added with an explicit account upgrade step,
automatically started when an account is opened. the upgrade is done in the
background because it will take too long for large mailboxes to block account
operations. the upgrade takes two steps: 1. updating all message records in the
database to add a normalized message-id and thread base subject (with "re:",
"fwd:" and several other schemes stripped). 2. going through all messages in
the database again, reading the "references" and "in-reply-to" headers from
disk, and matching against their parents. this second step is also done at the
end of each import of mbox/maildir mailboxes. new deliveries are matched
immediately against other existing messages, currently no attempt is made to
rematch previously delivered messages (which could be useful for related
messages being delivered out of order).
the threading is not yet exposed over imap.
2023-09-13 09:51:50 +03:00
- todo future : more extensions : STATUS = SIZE , OBJECTID , MULTISEARCH , REPLACE , NOTIFY , CATENATE , MULTIAPPEND , SORT , THREAD , CREATE - SPECIAL - USE .
2023-01-30 16:27:06 +03:00
* /
import (
"bufio"
"bytes"
"context"
2023-02-05 18:29:03 +03:00
"crypto/md5"
2023-02-05 14:30:14 +03:00
"crypto/sha1"
"crypto/sha256"
2023-01-30 16:27:06 +03:00
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
2023-02-05 14:30:14 +03:00
"hash"
2023-01-30 16:27:06 +03:00
"io"
2023-02-08 00:56:03 +03:00
"math"
2023-01-30 16:27:06 +03:00
"net"
"os"
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
"path"
2023-01-30 16:27:06 +03:00
"path/filepath"
"regexp"
"runtime/debug"
"sort"
"strings"
2023-12-05 15:35:58 +03:00
"sync"
2023-01-30 16:27:06 +03:00
"time"
2023-07-24 22:21:05 +03:00
"golang.org/x/exp/maps"
2023-07-24 09:49:19 +03:00
"golang.org/x/exp/slices"
2023-12-05 15:35:58 +03:00
"golang.org/x/exp/slog"
2023-01-30 16:27:06 +03:00
2023-08-10 11:29:06 +03:00
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/bstore"
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/message"
"github.com/mjl-/mox/metrics"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/moxvar"
2023-02-08 00:56:03 +03:00
"github.com/mjl-/mox/ratelimit"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/scram"
"github.com/mjl-/mox/store"
)
var (
metricIMAPConnection = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "mox_imap_connection_total" ,
Help : "Incoming IMAP connections." ,
} ,
[ ] string {
"service" , // imap, imaps
} ,
)
metricIMAPCommands = promauto . NewHistogramVec (
prometheus . HistogramOpts {
Name : "mox_imap_command_duration_seconds" ,
Help : "IMAP command duration and result codes in seconds." ,
Buckets : [ ] float64 { 0.001 , 0.005 , 0.01 , 0.05 , 0.100 , 0.5 , 1 , 5 , 10 , 20 } ,
} ,
[ ] string {
"cmd" ,
"result" , // ok, panic, ioerror, badsyntax, servererror, usererror, error
} ,
)
)
2023-02-08 00:56:03 +03:00
var limiterConnectionrate , limiterConnections * ratelimit . Limiter
func init ( ) {
// Also called by tests, so they don't trigger the rate limiter.
limitersInit ( )
}
func limitersInit ( ) {
mox . LimitersInit ( )
limiterConnectionrate = & ratelimit . Limiter {
WindowLimits : [ ] ratelimit . WindowLimit {
{
Window : time . Minute ,
Limits : [ ... ] int64 { 300 , 900 , 2700 } ,
} ,
} ,
}
limiterConnections = & ratelimit . Limiter {
WindowLimits : [ ] ratelimit . WindowLimit {
{
Window : time . Duration ( math . MaxInt64 ) , // All of time.
Limits : [ ... ] int64 { 30 , 90 , 270 } ,
} ,
} ,
}
}
2023-03-10 12:23:43 +03:00
// Delay after bad/suspicious behaviour. Tests set these to zero.
var badClientDelay = time . Second // Before reads and after 1-byte writes for probably spammers.
var authFailDelay = time . Second // After authentication failure.
2023-02-08 23:45:32 +03:00
2023-01-30 16:27:06 +03:00
// Capabilities (extensions) the server supports. Connections will add a few more, e.g. STARTTLS, LOGINDISABLED, AUTH=PLAIN.
// ENABLE: ../rfc/5161
// LITERAL+: ../rfc/7888
// IDLE: ../rfc/2177
// SASL-IR: ../rfc/4959
// BINARY: ../rfc/3516
// UNSELECT: ../rfc/3691
// UIDPLUS: ../rfc/4315
// ESEARCH: ../rfc/4731
// SEARCHRES: ../rfc/5182
// MOVE: ../rfc/6851
// UTF8=ONLY: ../rfc/6855
// LIST-EXTENDED: ../rfc/5258
// SPECIAL-USE: ../rfc/6154
// LIST-STATUS: ../rfc/5819
// ID: ../rfc/2971
// AUTH=SCRAM-SHA-256: ../rfc/7677 ../rfc/5802
2023-02-05 14:30:14 +03:00
// AUTH=SCRAM-SHA-1: ../rfc/5802
2023-02-05 18:29:03 +03:00
// AUTH=CRAM-MD5: ../rfc/2195
2023-01-30 16:27:06 +03:00
// APPENDLIMIT, we support the max possible size, 1<<63 - 1: ../rfc/7889:129
2023-07-24 22:21:05 +03:00
// CONDSTORE: ../rfc/7162:411
// QRESYNC: ../rfc/7162:1323
const serverCapabilities = "IMAP4rev2 IMAP4rev1 ENABLE LITERAL+ IDLE SASL-IR BINARY UNSELECT UIDPLUS ESEARCH SEARCHRES MOVE UTF8=ONLY LIST-EXTENDED SPECIAL-USE LIST-STATUS AUTH=SCRAM-SHA-256 AUTH=SCRAM-SHA-1 AUTH=CRAM-MD5 ID APPENDLIMIT=9223372036854775807 CONDSTORE QRESYNC"
2023-01-30 16:27:06 +03:00
type conn struct {
cid int64
state state
conn net . Conn
2023-02-03 22:33:19 +03:00
tls bool // Whether TLS has been initialized.
br * bufio . Reader // From remote, with TLS unwrapped in case of TLS.
line chan lineErr // If set, instead of reading from br, a line is read from this channel. For reading a line in IDLE while also waiting for mailbox/account updates.
lastLine string // For detecting if syntax error is fatal, i.e. if this ends with a literal. Without crlf.
bw * bufio . Writer // To remote, with TLS added in case of TLS.
tr * moxio . TraceReader // Kept to change trace level when reading/writing cmd/auth/data.
tw * moxio . TraceWriter
2023-02-08 23:45:32 +03:00
slow bool // If set, reads are done with a 1 second sleep, and writes are done 1 byte at a time, to keep spammers busy.
2023-02-03 22:33:19 +03:00
lastlog time . Time // For printing time since previous log line.
tlsConfig * tls . Config // TLS config to use for handshake.
2023-02-08 00:56:03 +03:00
remoteIP net . IP
2023-01-30 16:27:06 +03:00
noRequireSTARTTLS bool
cmd string // Currently executing, for deciding to applyChanges and logging.
cmdMetric string // Currently executing, for metrics.
cmdStart time . Time
2023-03-10 12:23:43 +03:00
ncmds int // Number of commands processed. Used to abort connection when first incoming command is unknown/invalid.
2023-12-05 15:35:58 +03:00
log mlog . Log
2023-01-30 16:27:06 +03:00
enabled map [ capability ] bool // All upper-case.
// Set by SEARCH with SAVE. Can be used by commands accepting a sequence-set with
// value "$". When used, UIDs must be verified to still exist, because they may
// have been expunged. Cleared by a SELECT or EXAMINE.
// Nil means no searchResult is present. An empty list is a valid searchResult,
// just not matching any messages.
// ../rfc/5182:13 ../rfc/9051:4040
searchResult [ ] store . UID
// Only when authenticated.
2023-02-08 23:45:32 +03:00
authFailed int // Number of failed auth attempts. For slowing down remote with many failures.
username string // Full username as used during login.
account * store . Account
comm * store . Comm // For sending/receiving changes on mailboxes in account, e.g. from messages incoming on smtp, or another imap client.
2023-01-30 16:27:06 +03:00
mailboxID int64 // Only for StateSelected.
readonly bool // If opened mailbox is readonly.
uids [ ] store . UID // UIDs known in this session, sorted. todo future: store more space-efficiently, as ranges.
}
// capability for use with ENABLED and CAPABILITY. We always keep this upper case,
// e.g. IMAP4REV2. These values are treated case-insensitive, but it's easier for
// comparison to just always have the same case.
type capability string
const (
capIMAP4rev2 capability = "IMAP4REV2"
capUTF8Accept capability = "UTF8=ACCEPT"
2023-07-24 22:21:05 +03:00
capCondstore capability = "CONDSTORE"
capQresync capability = "QRESYNC"
2023-01-30 16:27:06 +03:00
)
type lineErr struct {
line string
err error
}
type state byte
const (
stateNotAuthenticated state = iota
stateAuthenticated
stateSelected
)
func stateCommands ( cmds ... string ) map [ string ] struct { } {
r := map [ string ] struct { } { }
for _ , cmd := range cmds {
r [ cmd ] = struct { } { }
}
return r
}
var (
commandsStateAny = stateCommands ( "capability" , "noop" , "logout" , "id" )
commandsStateNotAuthenticated = stateCommands ( "starttls" , "authenticate" , "login" )
commandsStateAuthenticated = stateCommands ( "enable" , "select" , "examine" , "create" , "delete" , "rename" , "subscribe" , "unsubscribe" , "list" , "namespace" , "status" , "append" , "idle" , "lsub" )
commandsStateSelected = stateCommands ( "close" , "unselect" , "expunge" , "search" , "fetch" , "store" , "copy" , "move" , "uid expunge" , "uid search" , "uid fetch" , "uid store" , "uid copy" , "uid move" )
)
var commands = map [ string ] func ( c * conn , tag , cmd string , p * parser ) {
// Any state.
"capability" : ( * conn ) . cmdCapability ,
"noop" : ( * conn ) . cmdNoop ,
"logout" : ( * conn ) . cmdLogout ,
"id" : ( * conn ) . cmdID ,
// Notauthenticated.
"starttls" : ( * conn ) . cmdStarttls ,
"authenticate" : ( * conn ) . cmdAuthenticate ,
"login" : ( * conn ) . cmdLogin ,
// Authenticated and selected.
"enable" : ( * conn ) . cmdEnable ,
"select" : ( * conn ) . cmdSelect ,
"examine" : ( * conn ) . cmdExamine ,
"create" : ( * conn ) . cmdCreate ,
"delete" : ( * conn ) . cmdDelete ,
"rename" : ( * conn ) . cmdRename ,
"subscribe" : ( * conn ) . cmdSubscribe ,
"unsubscribe" : ( * conn ) . cmdUnsubscribe ,
"list" : ( * conn ) . cmdList ,
"lsub" : ( * conn ) . cmdLsub ,
"namespace" : ( * conn ) . cmdNamespace ,
"status" : ( * conn ) . cmdStatus ,
"append" : ( * conn ) . cmdAppend ,
"idle" : ( * conn ) . cmdIdle ,
// Selected.
"check" : ( * conn ) . cmdCheck ,
"close" : ( * conn ) . cmdClose ,
"unselect" : ( * conn ) . cmdUnselect ,
"expunge" : ( * conn ) . cmdExpunge ,
"uid expunge" : ( * conn ) . cmdUIDExpunge ,
"search" : ( * conn ) . cmdSearch ,
"uid search" : ( * conn ) . cmdUIDSearch ,
"fetch" : ( * conn ) . cmdFetch ,
"uid fetch" : ( * conn ) . cmdUIDFetch ,
"store" : ( * conn ) . cmdStore ,
"uid store" : ( * conn ) . cmdUIDStore ,
"copy" : ( * conn ) . cmdCopy ,
"uid copy" : ( * conn ) . cmdUIDCopy ,
"move" : ( * conn ) . cmdMove ,
"uid move" : ( * conn ) . cmdUIDMove ,
}
2023-11-02 17:56:01 +03:00
var errIO = errors . New ( "io error" ) // For read/write errors and errors that should close the connection.
var errProtocol = errors . New ( "protocol error" ) // For protocol errors for which a stack trace should be printed.
2023-01-30 16:27:06 +03:00
var sanityChecks bool
// check err for sanity.
// if not nil and checkSanity true (set during tests), then panic. if not nil during normal operation, just log.
func ( c * conn ) xsanity ( err error , format string , args ... any ) {
if err == nil {
return
}
if sanityChecks {
panic ( fmt . Errorf ( "%s: %s" , fmt . Sprintf ( format , args ... ) , err ) )
}
c . log . Errorx ( fmt . Sprintf ( format , args ... ) , err )
}
type msgseq uint32
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
// Listen initializes all imap listeners for the configuration, and stores them for Serve to start them.
func Listen ( ) {
2023-08-10 11:29:06 +03:00
names := maps . Keys ( mox . Conf . Static . Listeners )
sort . Strings ( names )
for _ , name := range names {
listener := mox . Conf . Static . Listeners [ name ]
2023-01-30 16:27:06 +03:00
var tlsConfig * tls . Config
if listener . TLS != nil {
tlsConfig = listener . TLS . Config
}
if listener . IMAP . Enabled {
port := config . Port ( listener . IMAP . Port , 143 )
for _ , ip := range listener . IPs {
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
listen1 ( "imap" , name , ip , port , tlsConfig , false , listener . IMAP . NoRequireSTARTTLS )
2023-01-30 16:27:06 +03:00
}
}
if listener . IMAPS . Enabled {
port := config . Port ( listener . IMAPS . Port , 993 )
for _ , ip := range listener . IPs {
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
listen1 ( "imaps" , name , ip , port , tlsConfig , true , false )
2023-01-30 16:27:06 +03:00
}
}
}
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
var servers [ ] func ( )
func listen1 ( protocol , listenerName , ip string , port int , tlsConfig * tls . Config , xtls , noRequireSTARTTLS bool ) {
2023-12-05 15:35:58 +03:00
log := mlog . New ( "imapserver" , nil )
2023-01-30 16:27:06 +03:00
addr := net . JoinHostPort ( ip , fmt . Sprintf ( "%d" , port ) )
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
if os . Getuid ( ) == 0 {
2023-12-05 18:06:50 +03:00
log . Print ( "listening for imap" ,
slog . String ( "listener" , listenerName ) ,
slog . String ( "addr" , addr ) ,
slog . String ( "protocol" , protocol ) )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
network := mox . Network ( ip )
ln , err := mox . Listen ( network , addr )
2023-01-30 16:27:06 +03:00
if err != nil {
2023-12-05 15:35:58 +03:00
log . Fatalx ( "imap: listen for imap" , err , slog . String ( "protocol" , protocol ) , slog . String ( "listener" , listenerName ) )
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
}
if xtls {
ln = tls . NewListener ( ln , tlsConfig )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
serve := func ( ) {
for {
conn , err := ln . Accept ( )
if err != nil {
2023-12-05 15:35:58 +03:00
log . Infox ( "imap: accept" , err , slog . String ( "protocol" , protocol ) , slog . String ( "listener" , listenerName ) )
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
continue
}
metricIMAPConnection . WithLabelValues ( protocol ) . Inc ( )
go serve ( listenerName , mox . Cid ( ) , tlsConfig , conn , xtls , noRequireSTARTTLS )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
}
servers = append ( servers , serve )
}
2023-01-30 16:27:06 +03:00
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
// Serve starts serving on all listeners, launching a goroutine per listener.
func Serve ( ) {
for _ , serve := range servers {
go serve ( )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
servers = nil
2023-01-30 16:27:06 +03:00
}
// returns whether this connection accepts utf-8 in strings.
func ( c * conn ) utf8strings ( ) bool {
return c . enabled [ capIMAP4rev2 ] || c . enabled [ capUTF8Accept ]
}
func ( c * conn ) xdbwrite ( fn func ( tx * bstore . Tx ) ) {
2023-05-22 15:40:36 +03:00
err := c . account . DB . Write ( context . TODO ( ) , func ( tx * bstore . Tx ) error {
2023-01-30 16:27:06 +03:00
fn ( tx )
return nil
} )
xcheckf ( err , "transaction" )
}
func ( c * conn ) xdbread ( fn func ( tx * bstore . Tx ) ) {
2023-05-22 15:40:36 +03:00
err := c . account . DB . Read ( context . TODO ( ) , func ( tx * bstore . Tx ) error {
2023-01-30 16:27:06 +03:00
fn ( tx )
return nil
} )
xcheckf ( err , "transaction" )
}
// Closes the currently selected/active mailbox, setting state from selected to authenticated.
// Does not remove messages marked for deletion.
func ( c * conn ) unselect ( ) {
if c . state == stateSelected {
c . state = stateAuthenticated
}
c . mailboxID = 0
c . uids = nil
}
2023-02-08 23:45:32 +03:00
func ( c * conn ) setSlow ( on bool ) {
if on && ! c . slow {
c . log . Debug ( "connection changed to slow" )
} else if ! on && c . slow {
c . log . Debug ( "connection restored to regular pace" )
}
c . slow = on
}
2023-01-30 16:27:06 +03:00
// Write makes a connection an io.Writer. It panics for i/o errors. These errors
// are handled in the connection command loop.
func ( c * conn ) Write ( buf [ ] byte ) ( int , error ) {
2023-02-08 23:45:32 +03:00
chunk := len ( buf )
if c . slow {
chunk = 1
2023-01-30 16:27:06 +03:00
}
2023-02-08 23:45:32 +03:00
var n int
for len ( buf ) > 0 {
2023-02-16 15:22:00 +03:00
err := c . conn . SetWriteDeadline ( time . Now ( ) . Add ( 30 * time . Second ) )
c . log . Check ( err , "setting write deadline" )
2023-02-08 23:45:32 +03:00
nn , err := c . conn . Write ( buf [ : chunk ] )
if err != nil {
panic ( fmt . Errorf ( "write: %s (%w)" , err , errIO ) )
}
n += nn
buf = buf [ chunk : ]
if len ( buf ) > 0 && badClientDelay > 0 {
mox . Sleep ( mox . Context , badClientDelay )
}
2023-01-30 16:27:06 +03:00
}
2023-02-08 23:45:32 +03:00
return n , nil
2023-01-30 16:27:06 +03:00
}
2023-12-05 15:35:58 +03:00
func ( c * conn ) xtrace ( level slog . Level ) func ( ) {
2023-02-03 22:33:19 +03:00
c . xflush ( )
c . tr . SetTrace ( level )
c . tw . SetTrace ( level )
return func ( ) {
c . xflush ( )
c . tr . SetTrace ( mlog . LevelTrace )
c . tw . SetTrace ( mlog . LevelTrace )
}
}
2023-01-30 16:27:06 +03:00
// Cache of line buffers for reading commands.
2023-07-24 22:21:05 +03:00
// QRESYNC recommends 8k max line lengths. ../rfc/7162:2159
2023-01-30 16:27:06 +03:00
var bufpool = moxio . NewBufpool ( 8 , 16 * 1024 )
// read line from connection, not going through line channel.
func ( c * conn ) readline0 ( ) ( string , error ) {
2023-02-08 23:45:32 +03:00
if c . slow && badClientDelay > 0 {
mox . Sleep ( mox . Context , badClientDelay )
}
2023-01-30 16:27:06 +03:00
d := 30 * time . Minute
if c . state == stateNotAuthenticated {
d = 30 * time . Second
}
2023-02-16 15:22:00 +03:00
err := c . conn . SetReadDeadline ( time . Now ( ) . Add ( d ) )
c . log . Check ( err , "setting read deadline" )
2023-01-30 16:27:06 +03:00
2023-12-05 15:35:58 +03:00
line , err := bufpool . Readline ( c . log , c . br )
2023-01-30 16:27:06 +03:00
if err != nil && errors . Is ( err , moxio . ErrLineTooLong ) {
return "" , fmt . Errorf ( "%s (%w)" , err , errProtocol )
} else if err != nil {
return "" , fmt . Errorf ( "%s (%w)" , err , errIO )
}
return line , nil
}
func ( c * conn ) lineChan ( ) chan lineErr {
if c . line == nil {
c . line = make ( chan lineErr , 1 )
go func ( ) {
line , err := c . readline0 ( )
c . line <- lineErr { line , err }
} ( )
}
return c . line
}
// readline from either the c.line channel, or otherwise read from connection.
func ( c * conn ) readline ( readCmd bool ) string {
var line string
var err error
if c . line != nil {
le := <- c . line
c . line = nil
line , err = le . line , le . err
} else {
line , err = c . readline0 ( )
}
if err != nil {
if readCmd && errors . Is ( err , os . ErrDeadlineExceeded ) {
2023-02-16 15:22:00 +03:00
err := c . conn . SetWriteDeadline ( time . Now ( ) . Add ( 10 * time . Second ) )
c . log . Check ( err , "setting write deadline" )
2023-01-30 16:27:06 +03:00
c . writelinef ( "* BYE inactive" )
}
if ! errors . Is ( err , errIO ) && ! errors . Is ( err , errProtocol ) {
err = fmt . Errorf ( "%s (%w)" , err , errIO )
}
panic ( err )
}
c . lastLine = line
// We typically respond immediately (IDLE is an exception).
// The client may not be reading, or may have disappeared.
// Don't wait more than 5 minutes before closing down the connection.
// The write deadline is managed in IDLE as well.
// For unauthenticated connections, we require the client to read faster.
wd := 5 * time . Minute
if c . state == stateNotAuthenticated {
wd = 30 * time . Second
}
2023-02-16 15:22:00 +03:00
err = c . conn . SetWriteDeadline ( time . Now ( ) . Add ( wd ) )
c . log . Check ( err , "setting write deadline" )
2023-01-30 16:27:06 +03:00
return line
}
// write tagged command response, but first write pending changes.
func ( c * conn ) writeresultf ( format string , args ... any ) {
c . bwriteresultf ( format , args ... )
c . xflush ( )
}
2023-07-24 22:21:05 +03:00
// write buffered tagged command response, but first write pending changes.
2023-01-30 16:27:06 +03:00
func ( c * conn ) bwriteresultf ( format string , args ... any ) {
switch c . cmd {
case "fetch" , "store" , "search" :
2023-07-24 22:21:05 +03:00
// ../rfc/9051:5862 ../rfc/7162:2033
2023-01-30 16:27:06 +03:00
default :
if c . comm != nil {
c . applyChanges ( c . comm . Get ( ) , false )
}
}
c . bwritelinef ( format , args ... )
}
func ( c * conn ) writelinef ( format string , args ... any ) {
c . bwritelinef ( format , args ... )
c . xflush ( )
}
// Buffer line for write.
func ( c * conn ) bwritelinef ( format string , args ... any ) {
format += "\r\n"
fmt . Fprintf ( c . bw , format , args ... )
}
func ( c * conn ) xflush ( ) {
err := c . bw . Flush ( )
xcheckf ( err , "flush" ) // Should never happen, the Write caused by the Flush should panic on i/o error.
}
func ( c * conn ) readCommand ( tag * string ) ( cmd string , p * parser ) {
line := c . readline ( true )
p = newParser ( line , c )
p . context ( "tag" )
* tag = p . xtag ( )
p . context ( "command" )
p . xspace ( )
cmd = p . xcommand ( )
return cmd , newParser ( p . remainder ( ) , c )
}
func ( c * conn ) xreadliteral ( size int64 , sync bool ) string {
if sync {
2023-07-24 20:54:55 +03:00
c . writelinef ( "+ " )
2023-01-30 16:27:06 +03:00
}
buf := make ( [ ] byte , size )
if size > 0 {
if err := c . conn . SetReadDeadline ( time . Now ( ) . Add ( 30 * time . Second ) ) ; err != nil {
c . log . Errorx ( "setting read deadline" , err )
}
_ , err := io . ReadFull ( c . br , buf )
if err != nil {
// Cannot use xcheckf due to %w handling of errIO.
panic ( fmt . Errorf ( "reading literal: %s (%w)" , err , errIO ) )
}
}
return string ( buf )
}
2023-07-24 22:21:05 +03:00
func ( c * conn ) xhighestModSeq ( tx * bstore . Tx , mailboxID int64 ) store . ModSeq {
qms := bstore . QueryTx [ store . Message ] ( tx )
qms . FilterNonzero ( store . Message { MailboxID : mailboxID } )
qms . SortDesc ( "ModSeq" )
qms . Limit ( 1 )
m , err := qms . Get ( )
if err == bstore . ErrAbsent {
return store . ModSeq ( 0 )
}
xcheckf ( err , "looking up highest modseq for mailbox" )
return m . ModSeq
}
2023-01-30 16:27:06 +03:00
var cleanClose struct { } // Sentinel value for panic/recover indicating clean close of connection.
func serve ( listenerName string , cid int64 , tlsConfig * tls . Config , nc net . Conn , xtls , noRequireSTARTTLS bool ) {
2023-02-08 00:56:03 +03:00
var remoteIP net . IP
if a , ok := nc . RemoteAddr ( ) . ( * net . TCPAddr ) ; ok {
remoteIP = a . IP
} else {
// For net.Pipe, during tests.
remoteIP = net . ParseIP ( "127.0.0.10" )
}
2023-01-30 16:27:06 +03:00
c := & conn {
cid : cid ,
conn : nc ,
tls : xtls ,
lastlog : time . Now ( ) ,
tlsConfig : tlsConfig ,
2023-02-08 00:56:03 +03:00
remoteIP : remoteIP ,
2023-01-30 16:27:06 +03:00
noRequireSTARTTLS : noRequireSTARTTLS ,
enabled : map [ capability ] bool { } ,
cmd : "(greeting)" ,
cmdStart : time . Now ( ) ,
}
2023-12-05 15:35:58 +03:00
var logmutex sync . Mutex
c . log = mlog . New ( "imapserver" , nil ) . WithFunc ( func ( ) [ ] slog . Attr {
logmutex . Lock ( )
defer logmutex . Unlock ( )
2023-01-30 16:27:06 +03:00
now := time . Now ( )
2023-12-05 15:35:58 +03:00
l := [ ] slog . Attr {
slog . Int64 ( "cid" , c . cid ) ,
slog . Duration ( "delta" , now . Sub ( c . lastlog ) ) ,
2023-01-30 16:27:06 +03:00
}
c . lastlog = now
if c . username != "" {
2023-12-05 15:35:58 +03:00
l = append ( l , slog . String ( "username" , c . username ) )
2023-01-30 16:27:06 +03:00
}
return l
} )
2023-02-03 22:33:19 +03:00
c . tr = moxio . NewTraceReader ( c . log , "C: " , c . conn )
c . tw = moxio . NewTraceWriter ( c . log , "S: " , c )
2023-02-22 12:43:21 +03:00
// todo: tracing should be done on whatever comes out of c.br. the remote connection write a command plus data, and bufio can read it in one read, causing a command parser that sets the tracing level to data to have no effect. we are now typically logging sent messages, when mail clients append to the Sent mailbox.
2023-02-03 22:33:19 +03:00
c . br = bufio . NewReader ( c . tr )
c . bw = bufio . NewWriter ( c . tw )
2023-01-30 16:27:06 +03:00
// Many IMAP connections use IDLE to wait for new incoming messages. We'll enable
// keepalive to get a higher chance of the connection staying alive, or otherwise
// detecting broken connections early.
xconn := c . conn
if xtls {
xconn = c . conn . ( * tls . Conn ) . NetConn ( )
}
if tcpconn , ok := xconn . ( * net . TCPConn ) ; ok {
if err := tcpconn . SetKeepAlivePeriod ( 5 * time . Minute ) ; err != nil {
c . log . Errorx ( "setting keepalive period" , err )
} else if err := tcpconn . SetKeepAlive ( true ) ; err != nil {
c . log . Errorx ( "enabling keepalive" , err )
}
}
2023-12-05 18:06:50 +03:00
c . log . Info ( "new connection" ,
slog . Any ( "remote" , c . conn . RemoteAddr ( ) ) ,
slog . Any ( "local" , c . conn . LocalAddr ( ) ) ,
slog . Bool ( "tls" , xtls ) ,
slog . String ( "listener" , listenerName ) )
2023-01-30 16:27:06 +03:00
defer func ( ) {
c . conn . Close ( )
if c . account != nil {
c . comm . Unregister ( )
err := c . account . Close ( )
c . xsanity ( err , "close account" )
c . account = nil
c . comm = nil
}
x := recover ( )
if x == nil || x == cleanClose {
c . log . Info ( "connection closed" )
2023-04-20 15:16:56 +03:00
} else if err , ok := x . ( error ) ; ok && isClosed ( err ) {
2023-01-30 16:27:06 +03:00
c . log . Infox ( "connection closed" , err )
} else {
2023-12-05 15:35:58 +03:00
c . log . Error ( "unhandled panic" , slog . Any ( "err" , x ) )
2023-01-30 16:27:06 +03:00
debug . PrintStack ( )
2023-09-15 17:47:17 +03:00
metrics . PanicInc ( metrics . Imapserver )
2023-01-30 16:27:06 +03:00
}
} ( )
select {
2023-02-16 11:57:27 +03:00
case <- mox . Shutdown . Done ( ) :
2023-01-30 16:27:06 +03:00
// ../rfc/9051:5381
c . writelinef ( "* BYE mox shutting down" )
2023-03-10 12:23:43 +03:00
return
2023-01-30 16:27:06 +03:00
default :
}
2023-02-08 00:56:03 +03:00
if ! limiterConnectionrate . Add ( c . remoteIP , time . Now ( ) , 1 ) {
c . writelinef ( "* BYE connection rate from your ip or network too high, slow down please" )
return
}
// If remote IP/network resulted in too many authentication failures, refuse to serve.
if ! mox . LimiterFailedAuth . CanAdd ( c . remoteIP , time . Now ( ) , 1 ) {
2023-02-13 15:53:47 +03:00
metrics . AuthenticationRatelimitedInc ( "imap" )
2023-12-05 15:35:58 +03:00
c . log . Debug ( "refusing connection due to many auth failures" , slog . Any ( "remoteip" , c . remoteIP ) )
2023-02-08 00:56:03 +03:00
c . writelinef ( "* BYE too many auth failures" )
return
}
if ! limiterConnections . Add ( c . remoteIP , time . Now ( ) , 1 ) {
2023-12-05 15:35:58 +03:00
c . log . Debug ( "refusing connection due to many open connections" , slog . Any ( "remoteip" , c . remoteIP ) )
2023-02-08 00:56:03 +03:00
c . writelinef ( "* BYE too many open connections from your ip or network" )
return
}
defer limiterConnections . Add ( c . remoteIP , time . Now ( ) , - 1 )
2023-01-30 16:27:06 +03:00
// We register and unregister the original connection, in case it c.conn is
// replaced with a TLS connection later on.
mox . Connections . Register ( nc , "imap" , listenerName )
defer mox . Connections . Unregister ( nc )
c . writelinef ( "* OK [CAPABILITY %s] mox imap" , c . capabilities ( ) )
for {
c . command ( )
c . xflush ( ) // For flushing errors, or possibly commands that did not flush explicitly.
}
}
// isClosed returns whether i/o failed, typically because the connection is closed.
// For connection errors, we often want to generate fewer logs.
func isClosed ( err error ) bool {
return errors . Is ( err , errIO ) || errors . Is ( err , errProtocol ) || moxio . IsClosed ( err )
}
func ( c * conn ) command ( ) {
var tag , cmd , cmdlow string
var p * parser
defer func ( ) {
var result string
defer func ( ) {
metricIMAPCommands . WithLabelValues ( c . cmdMetric , result ) . Observe ( float64 ( time . Since ( c . cmdStart ) ) / float64 ( time . Second ) )
} ( )
2023-12-05 15:35:58 +03:00
logFields := [ ] slog . Attr {
slog . String ( "cmd" , c . cmd ) ,
slog . Duration ( "duration" , time . Since ( c . cmdStart ) ) ,
2023-01-30 16:27:06 +03:00
}
c . cmd = ""
x := recover ( )
if x == nil || x == cleanClose {
c . log . Debug ( "imap command done" , logFields ... )
result = "ok"
2023-05-31 11:31:25 +03:00
if x == cleanClose {
panic ( x )
}
2023-01-30 16:27:06 +03:00
return
}
err , ok := x . ( error )
if ! ok {
2023-12-05 15:35:58 +03:00
c . log . Error ( "imap command panic" , append ( [ ] slog . Attr { slog . Any ( "panic" , x ) } , logFields ... ) ... )
2023-01-30 16:27:06 +03:00
result = "panic"
panic ( x )
}
2023-04-20 15:16:56 +03:00
var sxerr syntaxError
var uerr userError
var serr serverError
2023-01-30 16:27:06 +03:00
if isClosed ( err ) {
c . log . Infox ( "imap command ioerror" , err , logFields ... )
result = "ioerror"
if errors . Is ( err , errProtocol ) {
debug . PrintStack ( )
}
panic ( err )
2023-04-20 15:16:56 +03:00
} else if errors . As ( err , & sxerr ) {
2023-01-30 16:27:06 +03:00
result = "badsyntax"
2023-03-10 12:23:43 +03:00
if c . ncmds == 0 {
// Other side is likely speaking something else than IMAP, send error message and
// stop processing because there is a good chance whatever they sent has multiple
// lines.
c . writelinef ( "* BYE please try again speaking imap" )
panic ( errIO )
}
2023-03-10 13:32:34 +03:00
c . log . Debugx ( "imap command syntax error" , sxerr . err , logFields ... )
2023-12-05 15:35:58 +03:00
c . log . Info ( "imap syntax error" , slog . String ( "lastline" , c . lastLine ) )
2023-01-30 16:27:06 +03:00
fatal := strings . HasSuffix ( c . lastLine , "+}" )
if fatal {
2023-02-16 15:22:00 +03:00
err := c . conn . SetWriteDeadline ( time . Now ( ) . Add ( 5 * time . Second ) )
c . log . Check ( err , "setting write deadline" )
2023-01-30 16:27:06 +03:00
}
2023-03-10 13:32:34 +03:00
if sxerr . line != "" {
c . bwritelinef ( "%s" , sxerr . line )
}
code := ""
if sxerr . code != "" {
code = "[" + sxerr . code + "] "
}
c . bwriteresultf ( "%s BAD %s%s unrecognized syntax/command: %v" , tag , code , cmd , sxerr . errmsg )
2023-01-30 16:27:06 +03:00
if fatal {
c . xflush ( )
panic ( fmt . Errorf ( "aborting connection after syntax error for command with non-sync literal: %w" , errProtocol ) )
}
} else if errors . As ( err , & serr ) {
result = "servererror"
c . log . Errorx ( "imap command server error" , err , logFields ... )
debug . PrintStack ( )
c . bwriteresultf ( "%s NO %s %v" , tag , cmd , err )
} else if errors . As ( err , & uerr ) {
result = "usererror"
c . log . Debugx ( "imap command user error" , err , logFields ... )
if uerr . code != "" {
c . bwriteresultf ( "%s NO [%s] %s %v" , tag , uerr . code , cmd , err )
} else {
c . bwriteresultf ( "%s NO %s %v" , tag , cmd , err )
}
} else {
2023-04-20 15:16:56 +03:00
// Other type of panic, we pass it on, aborting the connection.
result = "panic"
c . log . Errorx ( "imap command panic" , err , logFields ... )
panic ( err )
2023-01-30 16:27:06 +03:00
}
} ( )
tag = "*"
cmd , p = c . readCommand ( & tag )
cmdlow = strings . ToLower ( cmd )
c . cmd = cmdlow
c . cmdStart = time . Now ( )
c . cmdMetric = "(unrecognized)"
select {
2023-02-16 11:57:27 +03:00
case <- mox . Shutdown . Done ( ) :
2023-01-30 16:27:06 +03:00
// ../rfc/9051:5375
c . writelinef ( "* BYE shutting down" )
panic ( errIO )
default :
}
fn := commands [ cmdlow ]
if fn == nil {
xsyntaxErrorf ( "unknown command %q" , cmd )
}
c . cmdMetric = c . cmd
2023-03-10 12:23:43 +03:00
c . ncmds ++
2023-01-30 16:27:06 +03:00
// Check if command is allowed in this state.
if _ , ok1 := commandsStateAny [ cmdlow ] ; ok1 {
} else if _ , ok2 := commandsStateNotAuthenticated [ cmdlow ] ; ok2 && c . state == stateNotAuthenticated {
} else if _ , ok3 := commandsStateAuthenticated [ cmdlow ] ; ok3 && c . state == stateAuthenticated || c . state == stateSelected {
} else if _ , ok4 := commandsStateSelected [ cmdlow ] ; ok4 && c . state == stateSelected {
} else if ok1 || ok2 || ok3 || ok4 {
xuserErrorf ( "not allowed in this connection state" )
} else {
xserverErrorf ( "unrecognized command" )
}
fn ( c , tag , cmd , p )
}
func ( c * conn ) broadcast ( changes [ ] store . Change ) {
if len ( changes ) == 0 {
return
}
2023-12-05 15:35:58 +03:00
c . log . Debug ( "broadcast changes" , slog . Any ( "changes" , changes ) )
2023-01-30 16:27:06 +03:00
c . comm . Broadcast ( changes )
}
// matchStringer matches a string against reference + mailbox patterns.
type matchStringer interface {
MatchString ( s string ) bool
}
type noMatch struct { }
// MatchString for noMatch always returns false.
func ( noMatch ) MatchString ( s string ) bool {
return false
}
// xmailboxPatternMatcher returns a matcher for mailbox names given the reference and patterns.
// Patterns can include "%" and "*", matching any character excluding and including a slash respectively.
func xmailboxPatternMatcher ( ref string , patterns [ ] string ) matchStringer {
if strings . HasPrefix ( ref , "/" ) {
return noMatch { }
}
var subs [ ] string
for _ , pat := range patterns {
if strings . HasPrefix ( pat , "/" ) {
continue
}
s := pat
if ref != "" {
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
s = path . Join ( ref , pat )
2023-01-30 16:27:06 +03:00
}
// Fix casing for all Inbox paths.
first := strings . SplitN ( s , "/" , 2 ) [ 0 ]
if strings . EqualFold ( first , "Inbox" ) {
s = "Inbox" + s [ len ( "Inbox" ) : ]
}
// ../rfc/9051:2361
var rs string
for _ , c := range s {
if c == '%' {
rs += "[^/]*"
} else if c == '*' {
rs += ".*"
} else {
rs += regexp . QuoteMeta ( string ( c ) )
}
}
subs = append ( subs , rs )
}
if len ( subs ) == 0 {
return noMatch { }
}
rs := "^(" + strings . Join ( subs , "|" ) + ")$"
re , err := regexp . Compile ( rs )
xcheckf ( err , "compiling regexp for mailbox patterns" )
return re
}
func ( c * conn ) sequence ( uid store . UID ) msgseq {
return uidSearch ( c . uids , uid )
}
func uidSearch ( uids [ ] store . UID , uid store . UID ) msgseq {
s := 0
e := len ( uids )
for s < e {
i := ( s + e ) / 2
m := uids [ i ]
if uid == m {
return msgseq ( i + 1 )
} else if uid < m {
e = i
} else {
s = i + 1
}
}
return 0
}
func ( c * conn ) xsequence ( uid store . UID ) msgseq {
seq := c . sequence ( uid )
if seq <= 0 {
xserverErrorf ( "unknown uid %d (%w)" , uid , errProtocol )
}
return seq
}
func ( c * conn ) sequenceRemove ( seq msgseq , uid store . UID ) {
i := seq - 1
if c . uids [ i ] != uid {
xserverErrorf ( fmt . Sprintf ( "got uid %d at msgseq %d, expected uid %d" , uid , seq , c . uids [ i ] ) )
}
copy ( c . uids [ i : ] , c . uids [ i + 1 : ] )
c . uids = c . uids [ : len ( c . uids ) - 1 ]
if sanityChecks {
checkUIDs ( c . uids )
}
}
// add uid to the session. care must be taken that pending changes are fetched
// while holding the account wlock, and applied before adding this uid, because
// those pending changes may contain another new uid that has to be added first.
func ( c * conn ) uidAppend ( uid store . UID ) {
if uidSearch ( c . uids , uid ) > 0 {
xserverErrorf ( "uid already present (%w)" , errProtocol )
}
if len ( c . uids ) > 0 && uid < c . uids [ len ( c . uids ) - 1 ] {
xserverErrorf ( "new uid %d is smaller than last uid %d (%w)" , uid , c . uids [ len ( c . uids ) - 1 ] , errProtocol )
}
c . uids = append ( c . uids , uid )
if sanityChecks {
checkUIDs ( c . uids )
}
}
// sanity check that uids are in ascending order.
func checkUIDs ( uids [ ] store . UID ) {
for i , uid := range uids {
if uid == 0 || i > 0 && uid <= uids [ i - 1 ] {
xserverErrorf ( "bad uids %v" , uids )
}
}
}
func ( c * conn ) xnumSetUIDs ( isUID bool , nums numSet ) [ ] store . UID {
_ , uids := c . xnumSetConditionUIDs ( false , true , isUID , nums )
return uids
}
func ( c * conn ) xnumSetCondition ( isUID bool , nums numSet ) [ ] any {
uidargs , _ := c . xnumSetConditionUIDs ( true , false , isUID , nums )
return uidargs
}
func ( c * conn ) xnumSetConditionUIDs ( forDB , returnUIDs bool , isUID bool , nums numSet ) ( [ ] any , [ ] store . UID ) {
if nums . searchResult {
// Update previously stored UIDs. Some may have been deleted.
// Once deleted a UID will never come back, so we'll just remove those uids.
o := 0
for _ , uid := range c . searchResult {
if uidSearch ( c . uids , uid ) > 0 {
c . searchResult [ o ] = uid
o ++
}
}
c . searchResult = c . searchResult [ : o ]
uidargs := make ( [ ] any , len ( c . searchResult ) )
for i , uid := range c . searchResult {
uidargs [ i ] = uid
}
return uidargs , c . searchResult
}
var uidargs [ ] any
var uids [ ] store . UID
add := func ( uid store . UID ) {
if forDB {
uidargs = append ( uidargs , uid )
}
if returnUIDs {
uids = append ( uids , uid )
}
}
if ! isUID {
// Sequence numbers that don't exist, or * on an empty mailbox, should result in a BAD response. ../rfc/9051:7018
for _ , r := range nums . ranges {
var ia , ib int
if r . first . star {
if len ( c . uids ) == 0 {
xsyntaxErrorf ( "invalid seqset * on empty mailbox" )
}
ia = len ( c . uids ) - 1
} else {
ia = int ( r . first . number - 1 )
if ia >= len ( c . uids ) {
xsyntaxErrorf ( "msgseq %d not in mailbox" , r . first . number )
}
}
if r . last == nil {
add ( c . uids [ ia ] )
continue
}
if r . last . star {
if len ( c . uids ) == 0 {
xsyntaxErrorf ( "invalid seqset * on empty mailbox" )
}
ib = len ( c . uids ) - 1
} else {
ib = int ( r . last . number - 1 )
if ib >= len ( c . uids ) {
xsyntaxErrorf ( "msgseq %d not in mailbox" , r . last . number )
}
}
if ia > ib {
ia , ib = ib , ia
}
for _ , uid := range c . uids [ ia : ib + 1 ] {
add ( uid )
}
}
return uidargs , uids
}
// UIDs that do not exist can be ignored.
if len ( c . uids ) == 0 {
return nil , nil
}
for _ , r := range nums . ranges {
last := r . first
if r . last != nil {
last = * r . last
}
uida := store . UID ( r . first . number )
if r . first . star {
uida = c . uids [ len ( c . uids ) - 1 ]
}
uidb := store . UID ( last . number )
if last . star {
uidb = c . uids [ len ( c . uids ) - 1 ]
}
if uida > uidb {
uida , uidb = uidb , uida
}
// Binary search for uida.
s := 0
e := len ( c . uids )
for s < e {
m := ( s + e ) / 2
if uida < c . uids [ m ] {
e = m
} else if uida > c . uids [ m ] {
s = m + 1
} else {
break
}
}
for _ , uid := range c . uids [ s : ] {
if uid >= uida && uid <= uidb {
add ( uid )
} else if uid > uidb {
break
}
}
}
return uidargs , uids
}
func ( c * conn ) ok ( tag , cmd string ) {
c . bwriteresultf ( "%s OK %s done" , tag , cmd )
c . xflush ( )
}
// xcheckmailboxname checks if name is valid, returning an INBOX-normalized name.
// I.e. it changes various casings of INBOX and INBOX/* to Inbox and Inbox/*.
// Name is invalid if it contains leading/trailing/double slashes, or when it isn't
// unicode-normalized, or when empty or has special characters.
func xcheckmailboxname ( name string , allowInbox bool ) string {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
name , isinbox , err := store . CheckMailboxName ( name , allowInbox )
if isinbox {
xuserErrorf ( "special mailboxname Inbox not allowed" )
} else if err != nil {
xusercodeErrorf ( "CANNOT" , err . Error ( ) )
2023-01-30 16:27:06 +03:00
}
return name
}
// Lookup mailbox by name.
// If the mailbox does not exist, panic is called with a user error.
// Must be called with account rlock held.
func ( c * conn ) xmailbox ( tx * bstore . Tx , name string , missingErrCode string ) store . Mailbox {
2023-04-20 15:16:56 +03:00
mb , err := c . account . MailboxFind ( tx , name )
xcheckf ( err , "finding mailbox" )
2023-01-30 16:27:06 +03:00
if mb == nil {
// missingErrCode can be empty, or e.g. TRYCREATE or ALREADYEXISTS.
xusercodeErrorf ( missingErrCode , "%w" , store . ErrUnknownMailbox )
}
return * mb
}
// Lookup mailbox by ID.
// If the mailbox does not exist, panic is called with a user error.
// Must be called with account rlock held.
func ( c * conn ) xmailboxID ( tx * bstore . Tx , id int64 ) store . Mailbox {
mb := store . Mailbox { ID : id }
err := tx . Get ( & mb )
if err == bstore . ErrAbsent {
xuserErrorf ( "%w" , store . ErrUnknownMailbox )
}
return mb
}
// Apply changes to our session state.
// If initial is false, updates like EXISTS and EXPUNGE are written to the client.
// If initial is true, we only apply the changes.
// Should not be called while holding locks, as changes are written to client connections, which can block.
// Does not flush output.
func ( c * conn ) applyChanges ( changes [ ] store . Change , initial bool ) {
if len ( changes ) == 0 {
return
}
2023-02-16 15:22:00 +03:00
err := c . conn . SetWriteDeadline ( time . Now ( ) . Add ( 5 * time . Minute ) )
c . log . Check ( err , "setting write deadline" )
2023-01-30 16:27:06 +03:00
2023-12-05 15:35:58 +03:00
c . log . Debug ( "applying changes" , slog . Any ( "changes" , changes ) )
2023-01-30 16:27:06 +03:00
// Only keep changes for the selected mailbox, and changes that are always relevant.
var n [ ] store . Change
for _ , change := range changes {
var mbID int64
switch ch := change . ( type ) {
case store . ChangeAddUID :
mbID = ch . MailboxID
case store . ChangeRemoveUIDs :
mbID = ch . MailboxID
case store . ChangeFlags :
mbID = ch . MailboxID
case store . ChangeRemoveMailbox , store . ChangeAddMailbox , store . ChangeRenameMailbox , store . ChangeAddSubscription :
n = append ( n , change )
continue
implement message threading in backend and webmail
we match messages to their parents based on the "references" and "in-reply-to"
headers (requiring the same base subject), and in absense of those headers we
also by only base subject (against messages received max 4 weeks ago).
we store a threadid with messages. all messages in a thread have the same
threadid. messages also have a "thread parent ids", which holds all id's of
parent messages up to the thread root. then there is "thread missing link",
which is set when a referenced immediate parent wasn't found (but possibly
earlier ancestors can still be found and will be in thread parent ids".
threads can be muted: newly delivered messages are automatically marked as
read/seen. threads can be marked as collapsed: if set, the webmail collapses
the thread to a single item in the basic threading view (default is to expand
threads). the muted and collapsed fields are copied from their parent on
message delivery.
the threading is implemented in the webmail. the non-threading mode still works
as before. the new default threading mode "unread" automatically expands only
the threads with at least one unread (not seen) meessage. the basic threading
mode "on" expands all threads except when explicitly collapsed (as saved in the
thread collapsed field). new shortcuts for navigation/interaction threads have
been added, e.g. go to previous/next thread root, toggle collapse/expand of
thread (or double click), toggle mute of thread. some previous shortcuts have
changed, see the help for details.
the message threading are added with an explicit account upgrade step,
automatically started when an account is opened. the upgrade is done in the
background because it will take too long for large mailboxes to block account
operations. the upgrade takes two steps: 1. updating all message records in the
database to add a normalized message-id and thread base subject (with "re:",
"fwd:" and several other schemes stripped). 2. going through all messages in
the database again, reading the "references" and "in-reply-to" headers from
disk, and matching against their parents. this second step is also done at the
end of each import of mbox/maildir mailboxes. new deliveries are matched
immediately against other existing messages, currently no attempt is made to
rematch previously delivered messages (which could be useful for related
messages being delivered out of order).
the threading is not yet exposed over imap.
2023-09-13 09:51:50 +03:00
case store . ChangeMailboxCounts , store . ChangeMailboxSpecialUse , store . ChangeMailboxKeywords , store . ChangeThread :
2023-01-30 16:27:06 +03:00
default :
panic ( fmt . Errorf ( "missing case for %#v" , change ) )
}
if c . state == stateSelected && mbID == c . mailboxID {
n = append ( n , change )
}
}
changes = n
2023-07-24 22:21:05 +03:00
qresync := c . enabled [ capQresync ]
condstore := c . enabled [ capCondstore ]
2023-01-30 16:27:06 +03:00
i := 0
for i < len ( changes ) {
// First process all new uids. So we only send a single EXISTS.
var adds [ ] store . ChangeAddUID
for ; i < len ( changes ) ; i ++ {
ch , ok := changes [ i ] . ( store . ChangeAddUID )
if ! ok {
break
}
seq := c . sequence ( ch . UID )
if seq > 0 && initial {
continue
}
c . uidAppend ( ch . UID )
adds = append ( adds , ch )
}
if len ( adds ) > 0 {
if initial {
continue
}
// Write the exists, and the UID and flags as well. Hopefully the client waits for
// long enough after the EXISTS to see these messages, and doesn't request them
// again with a FETCH.
c . bwritelinef ( "* %d EXISTS" , len ( c . uids ) )
for _ , add := range adds {
seq := c . xsequence ( add . UID )
2023-07-24 22:21:05 +03:00
var modseqStr string
if condstore {
modseqStr = fmt . Sprintf ( " MODSEQ (%d)" , add . ModSeq . Client ( ) )
}
c . bwritelinef ( "* %d FETCH (UID %d FLAGS %s%s)" , seq , add . UID , flaglist ( add . Flags , add . Keywords ) . pack ( c ) , modseqStr )
2023-01-30 16:27:06 +03:00
}
continue
}
change := changes [ i ]
i ++
switch ch := change . ( type ) {
case store . ChangeRemoveUIDs :
2023-07-24 22:21:05 +03:00
var vanishedUIDs numSet
2023-01-30 16:27:06 +03:00
for _ , uid := range ch . UIDs {
var seq msgseq
if initial {
seq = c . sequence ( uid )
if seq <= 0 {
continue
}
} else {
seq = c . xsequence ( uid )
}
c . sequenceRemove ( seq , uid )
if ! initial {
2023-07-24 22:21:05 +03:00
if qresync {
vanishedUIDs . append ( uint32 ( uid ) )
} else {
c . bwritelinef ( "* %d EXPUNGE" , seq )
}
}
}
if qresync {
// VANISHED without EARLIER. ../rfc/7162:2004
for _ , s := range vanishedUIDs . Strings ( 4 * 1024 - 32 ) {
c . bwritelinef ( "* VANISHED %s" , s )
2023-01-30 16:27:06 +03:00
}
}
case store . ChangeFlags :
// The uid can be unknown if we just expunged it while another session marked it as deleted just before.
seq := c . sequence ( ch . UID )
if seq <= 0 {
continue
}
if ! initial {
2023-07-24 22:21:05 +03:00
var modseqStr string
if condstore {
modseqStr = fmt . Sprintf ( " MODSEQ (%d)" , ch . ModSeq . Client ( ) )
}
c . bwritelinef ( "* %d FETCH (UID %d FLAGS %s%s)" , seq , ch . UID , flaglist ( ch . Flags , ch . Keywords ) . pack ( c ) , modseqStr )
2023-01-30 16:27:06 +03:00
}
case store . ChangeRemoveMailbox :
2023-02-17 20:35:11 +03:00
// Only announce \NonExistent to modern clients, otherwise they may ignore the
// unrecognized \NonExistent and interpret this as a newly created mailbox, while
// the goal was to remove it...
if c . enabled [ capIMAP4rev2 ] {
c . bwritelinef ( ` * LIST (\NonExistent) "/" %s ` , astring ( ch . Name ) . pack ( c ) )
}
2023-01-30 16:27:06 +03:00
case store . ChangeAddMailbox :
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . bwritelinef ( ` * LIST (%s) "/" %s ` , strings . Join ( ch . Flags , " " ) , astring ( ch . Mailbox . Name ) . pack ( c ) )
2023-01-30 16:27:06 +03:00
case store . ChangeRenameMailbox :
c . bwritelinef ( ` * LIST (%s) "/" %s ("OLDNAME" (%s)) ` , strings . Join ( ch . Flags , " " ) , astring ( ch . NewName ) . pack ( c ) , string0 ( ch . OldName ) . pack ( c ) )
case store . ChangeAddSubscription :
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . bwritelinef ( ` * LIST (%s) "/" %s ` , strings . Join ( append ( [ ] string { ` \Subscribed ` } , ch . Flags ... ) , " " ) , astring ( ch . Name ) . pack ( c ) )
2023-01-30 16:27:06 +03:00
default :
panic ( fmt . Sprintf ( "internal error, missing case for %#v" , change ) )
}
}
}
// Capability returns the capabilities this server implements and currently has
// available given the connection state.
//
// State: any
func ( c * conn ) cmdCapability ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1208 ../rfc/3501:1300
// Request syntax: ../rfc/9051:6464 ../rfc/3501:4669
p . xempty ( )
caps := c . capabilities ( )
// Response syntax: ../rfc/9051:6427 ../rfc/3501:4655
c . bwritelinef ( "* CAPABILITY %s" , caps )
c . ok ( tag , cmd )
}
// capabilities returns non-empty string with available capabilities based on connection state.
// For use in cmdCapability and untagged OK responses on connection start, login and authenticate.
func ( c * conn ) capabilities ( ) string {
caps := serverCapabilities
// ../rfc/9051:1238
2023-02-27 16:10:43 +03:00
// We only allow starting without TLS when explicitly configured, in violation of RFC.
if ! c . tls && c . tlsConfig != nil {
2023-01-30 16:27:06 +03:00
caps += " STARTTLS"
}
if c . tls || c . noRequireSTARTTLS {
caps += " AUTH=PLAIN"
} else {
caps += " LOGINDISABLED"
}
return caps
}
// No op, but useful for retrieving pending changes as untagged responses, e.g. of
// message delivery.
//
// State: any
func ( c * conn ) cmdNoop ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1261 ../rfc/3501:1363
// Request syntax: ../rfc/9051:6464 ../rfc/3501:4669
p . xempty ( )
c . ok ( tag , cmd )
}
// Logout, after which server closes the connection.
//
// State: any
func ( c * conn ) cmdLogout ( tag , cmd string , p * parser ) {
// Commands: ../rfc/3501:1407 ../rfc/9051:1290
// Request syntax: ../rfc/9051:6464 ../rfc/3501:4669
p . xempty ( )
c . unselect ( )
c . state = stateNotAuthenticated
// Response syntax: ../rfc/9051:6886 ../rfc/3501:4935
c . bwritelinef ( "* BYE thanks" )
c . ok ( tag , cmd )
panic ( cleanClose )
}
// Clients can use ID to tell the server which software they are using. Servers can
// respond with their version. For statistics/logging/debugging purposes.
//
// State: any
func ( c * conn ) cmdID ( tag , cmd string , p * parser ) {
// Command: ../rfc/2971:129
// Request syntax: ../rfc/2971:241
p . xspace ( )
var params map [ string ] string
if p . take ( "(" ) {
params = map [ string ] string { }
for ! p . take ( ")" ) {
if len ( params ) > 0 {
p . xspace ( )
}
k := p . xstring ( )
p . xspace ( )
v := p . xnilString ( )
if _ , ok := params [ k ] ; ok {
xsyntaxErrorf ( "duplicate key %q" , k )
}
params [ k ] = v
}
} else {
p . xnil ( )
}
p . xempty ( )
// We just log the client id.
2023-12-05 15:35:58 +03:00
c . log . Info ( "client id" , slog . Any ( "params" , params ) )
2023-01-30 16:27:06 +03:00
// Response syntax: ../rfc/2971:243
// We send our name and version. ../rfc/2971:193
c . bwritelinef ( ` * ID ("name" "mox" "version" %s) ` , string0 ( moxvar . Version ) . pack ( c ) )
c . ok ( tag , cmd )
}
// STARTTLS enables TLS on the connection, after a plain text start.
// Only allowed if TLS isn't already enabled, either through connecting to a
// TLS-enabled TCP port, or a previous STARTTLS command.
// After STARTTLS, plain text authentication typically becomes available.
//
// Status: Not authenticated.
func ( c * conn ) cmdStarttls ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1340 ../rfc/3501:1468
// Request syntax: ../rfc/9051:6473 ../rfc/3501:4676
p . xempty ( )
if c . tls {
xsyntaxErrorf ( "tls already active" ) // ../rfc/9051:1353
}
conn := c . conn
if n := c . br . Buffered ( ) ; n > 0 {
buf := make ( [ ] byte , n )
_ , err := io . ReadFull ( c . br , buf )
xcheckf ( err , "reading buffered data for tls handshake" )
conn = & prefixConn { buf , conn }
}
2023-11-13 12:26:31 +03:00
// We add the cid to facilitate debugging in case of TLS connection failure.
c . ok ( tag , cmd + " (" + mox . ReceivedID ( c . cid ) + ")" )
2023-01-30 16:27:06 +03:00
cidctx := context . WithValue ( mox . Context , mlog . CidKey , c . cid )
ctx , cancel := context . WithTimeout ( cidctx , time . Minute )
defer cancel ( )
tlsConn := tls . Server ( conn , c . tlsConfig )
c . log . Debug ( "starting tls server handshake" )
if err := tlsConn . HandshakeContext ( ctx ) ; err != nil {
panic ( fmt . Errorf ( "starttls handshake: %s (%w)" , err , errIO ) )
}
cancel ( )
2023-12-05 23:13:57 +03:00
tlsversion , ciphersuite := moxio . TLSInfo ( tlsConn )
2023-12-05 15:35:58 +03:00
c . log . Debug ( "tls server handshake done" , slog . String ( "tls" , tlsversion ) , slog . String ( "ciphersuite" , ciphersuite ) )
2023-01-30 16:27:06 +03:00
c . conn = tlsConn
2023-02-03 22:33:19 +03:00
c . tr = moxio . NewTraceReader ( c . log , "C: " , c . conn )
c . tw = moxio . NewTraceWriter ( c . log , "S: " , c )
c . br = bufio . NewReader ( c . tr )
c . bw = bufio . NewWriter ( c . tw )
2023-01-30 16:27:06 +03:00
c . tls = true
}
// Authenticate using SASL. Supports multiple back and forths between client and
// server to finish authentication, unlike LOGIN which is just a single
// username/password.
//
// Status: Not authenticated.
func ( c * conn ) cmdAuthenticate ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1403 ../rfc/3501:1519
// Examples: ../rfc/9051:1520 ../rfc/3501:1631
2023-02-08 23:45:32 +03:00
// For many failed auth attempts, slow down verification attempts.
2023-03-10 12:23:43 +03:00
if c . authFailed > 3 && authFailDelay > 0 {
mox . Sleep ( mox . Context , time . Duration ( c . authFailed - 3 ) * authFailDelay )
2023-02-08 23:45:32 +03:00
}
c . authFailed ++ // Compensated on success.
defer func ( ) {
// On the 3rd failed authentication, start responding slowly. Successful auth will
// cause fast responses again.
if c . authFailed >= 3 {
c . setSlow ( true )
}
} ( )
2023-01-30 16:27:06 +03:00
var authVariant string
authResult := "error"
defer func ( ) {
metrics . AuthenticationInc ( "imap" , authVariant , authResult )
2023-02-08 00:56:03 +03:00
switch authResult {
case "ok" :
mox . LimiterFailedAuth . Reset ( c . remoteIP , time . Now ( ) )
default :
mox . LimiterFailedAuth . Add ( c . remoteIP , time . Now ( ) , 1 )
}
2023-01-30 16:27:06 +03:00
} ( )
// Request syntax: ../rfc/9051:6341 ../rfc/3501:4561
p . xspace ( )
authType := p . xatom ( )
xreadInitial := func ( ) [ ] byte {
var line string
if p . empty ( ) {
c . writelinef ( "+ " )
line = c . readline ( false )
} else {
// ../rfc/9051:1407 ../rfc/4959:84
p . xspace ( )
line = p . remainder ( )
if line == "=" {
// ../rfc/9051:1450
line = "" // Base64 decode will result in empty buffer.
}
}
// ../rfc/9051:1442 ../rfc/3501:1553
if line == "*" {
authResult = "aborted"
xsyntaxErrorf ( "authenticate aborted by client" )
}
buf , err := base64 . StdEncoding . DecodeString ( line )
if err != nil {
xsyntaxErrorf ( "parsing base64: %v" , err )
}
return buf
}
xreadContinuation := func ( ) [ ] byte {
line := c . readline ( false )
if line == "*" {
authResult = "aborted"
xsyntaxErrorf ( "authenticate aborted by client" )
}
buf , err := base64 . StdEncoding . DecodeString ( line )
if err != nil {
xsyntaxErrorf ( "parsing base64: %v" , err )
}
return buf
}
switch strings . ToUpper ( authType ) {
case "PLAIN" :
authVariant = "plain"
if ! c . noRequireSTARTTLS && ! c . tls {
// ../rfc/9051:5194
xusercodeErrorf ( "PRIVACYREQUIRED" , "tls required for login" )
}
2023-02-03 22:33:19 +03:00
// Plain text passwords, mark as traceauth.
defer c . xtrace ( mlog . LevelTraceauth ) ( )
2023-01-30 16:27:06 +03:00
buf := xreadInitial ( )
2023-02-03 22:33:19 +03:00
c . xtrace ( mlog . LevelTrace ) // Restore.
2023-01-30 16:27:06 +03:00
plain := bytes . Split ( buf , [ ] byte { 0 } )
if len ( plain ) != 3 {
xsyntaxErrorf ( "bad plain auth data, expected 3 nul-separated tokens, got %d tokens" , len ( plain ) )
}
authz := string ( plain [ 0 ] )
authc := string ( plain [ 1 ] )
password := string ( plain [ 2 ] )
2023-02-08 23:45:32 +03:00
if authz != "" && authz != authc {
xusercodeErrorf ( "AUTHORIZATIONFAILED" , "cannot assume role" )
}
2023-12-05 15:35:58 +03:00
acc , err := store . OpenEmailAuth ( c . log , authc , password )
2023-01-30 16:27:06 +03:00
if err != nil {
if errors . Is ( err , store . ErrUnknownCredentials ) {
authResult = "badcreds"
2023-12-05 15:35:58 +03:00
c . log . Info ( "authentication failed" , slog . String ( "username" , authc ) )
2023-01-30 16:27:06 +03:00
xusercodeErrorf ( "AUTHENTICATIONFAILED" , "bad credentials" )
}
xusercodeErrorf ( "" , "error" )
}
c . account = acc
c . username = authc
2023-02-05 18:29:03 +03:00
case "CRAM-MD5" :
authVariant = strings . ToLower ( authType )
// ../rfc/9051:1462
p . xempty ( )
// ../rfc/2195:82
chal := fmt . Sprintf ( "<%d.%d@%s>" , uint64 ( mox . CryptoRandInt ( ) ) , time . Now ( ) . UnixNano ( ) , mox . Conf . Static . HostnameDomain . ASCII )
c . writelinef ( "+ %s" , base64 . StdEncoding . EncodeToString ( [ ] byte ( chal ) ) )
resp := xreadContinuation ( )
t := strings . Split ( string ( resp ) , " " )
if len ( t ) != 2 || len ( t [ 1 ] ) != 2 * md5 . Size {
xsyntaxErrorf ( "malformed cram-md5 response" )
}
addr := t [ 0 ]
2023-12-05 15:35:58 +03:00
c . log . Debug ( "cram-md5 auth" , slog . String ( "address" , addr ) )
acc , _ , err := store . OpenEmail ( c . log , addr )
2023-02-05 18:29:03 +03:00
if err != nil {
if errors . Is ( err , store . ErrUnknownCredentials ) {
2023-12-05 15:35:58 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , addr ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xusercodeErrorf ( "AUTHENTICATIONFAILED" , "bad credentials" )
}
xserverErrorf ( "looking up address: %v" , err )
}
defer func ( ) {
if acc != nil {
err := acc . Close ( )
c . xsanity ( err , "close account" )
}
} ( )
var ipadhash , opadhash hash . Hash
acc . WithRLock ( func ( ) {
2023-05-22 15:40:36 +03:00
err := acc . DB . Read ( context . TODO ( ) , func ( tx * bstore . Tx ) error {
2023-02-05 18:29:03 +03:00
password , err := bstore . QueryTx [ store . Password ] ( tx ) . Get ( )
if err == bstore . ErrAbsent {
2023-12-05 15:35:58 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , addr ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xusercodeErrorf ( "AUTHENTICATIONFAILED" , "bad credentials" )
}
if err != nil {
return err
}
ipadhash = password . CRAMMD5 . Ipad
opadhash = password . CRAMMD5 . Opad
return nil
} )
xcheckf ( err , "tx read" )
} )
if ipadhash == nil || opadhash == nil {
2023-12-05 15:35:58 +03:00
c . log . Info ( "cram-md5 auth attempt without derived secrets set, save password again to store secrets" , slog . String ( "username" , addr ) )
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , addr ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xusercodeErrorf ( "AUTHENTICATIONFAILED" , "bad credentials" )
}
// ../rfc/2195:138 ../rfc/2104:142
ipadhash . Write ( [ ] byte ( chal ) )
opadhash . Write ( ipadhash . Sum ( nil ) )
digest := fmt . Sprintf ( "%x" , opadhash . Sum ( nil ) )
if digest != t [ 1 ] {
2023-12-05 15:35:58 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , addr ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xusercodeErrorf ( "AUTHENTICATIONFAILED" , "bad credentials" )
}
c . account = acc
acc = nil // Cancel cleanup.
c . username = addr
2023-02-05 14:30:14 +03:00
case "SCRAM-SHA-1" , "SCRAM-SHA-256" :
2023-01-30 16:27:06 +03:00
// todo: improve handling of errors during scram. e.g. invalid parameters. should we abort the imap command, or continue until the end and respond with a scram-level error?
2023-01-31 02:22:26 +03:00
// todo: use single implementation between ../imapserver/server.go and ../smtpserver/server.go
2023-01-30 16:27:06 +03:00
2023-02-05 14:30:14 +03:00
authVariant = strings . ToLower ( authType )
var h func ( ) hash . Hash
if authVariant == "scram-sha-1" {
h = sha1 . New
} else {
h = sha256 . New
}
2023-01-30 16:27:06 +03:00
2023-02-03 22:33:19 +03:00
// No plaintext credentials, we can log these normally.
2023-01-30 16:27:06 +03:00
c0 := xreadInitial ( )
2023-02-05 14:30:14 +03:00
ss , err := scram . NewServer ( h , c0 )
2023-01-30 16:27:06 +03:00
if err != nil {
2023-03-10 13:32:34 +03:00
xsyntaxErrorf ( "starting scram: %s" , err )
2023-01-30 16:27:06 +03:00
}
2023-12-05 15:35:58 +03:00
c . log . Debug ( "scram auth" , slog . String ( "authentication" , ss . Authentication ) )
acc , _ , err := store . OpenEmail ( c . log , ss . Authentication )
2023-01-30 16:27:06 +03:00
if err != nil {
// todo: we could continue scram with a generated salt, deterministically generated
// from the username. that way we don't have to store anything but attackers cannot
// learn if an account exists. same for absent scram saltedpassword below.
xuserErrorf ( "scram not possible" )
}
defer func ( ) {
if acc != nil {
err := acc . Close ( )
c . xsanity ( err , "close account" )
}
} ( )
if ss . Authorization != "" && ss . Authorization != ss . Authentication {
xuserErrorf ( "authentication with authorization for different user not supported" )
}
2023-02-05 14:30:14 +03:00
var xscram store . SCRAM
2023-01-30 16:27:06 +03:00
acc . WithRLock ( func ( ) {
2023-05-22 15:40:36 +03:00
err := acc . DB . Read ( context . TODO ( ) , func ( tx * bstore . Tx ) error {
2023-02-05 14:30:14 +03:00
password , err := bstore . QueryTx [ store . Password ] ( tx ) . Get ( )
if authVariant == "scram-sha-1" {
xscram = password . SCRAMSHA1
} else {
xscram = password . SCRAMSHA256
}
if err == bstore . ErrAbsent || err == nil && ( len ( xscram . Salt ) == 0 || xscram . Iterations == 0 || len ( xscram . SaltedPassword ) == 0 ) {
2023-12-05 15:35:58 +03:00
c . log . Info ( "scram auth attempt without derived secrets set, save password again to store secrets" , slog . String ( "address" , ss . Authentication ) )
2023-01-30 16:27:06 +03:00
xuserErrorf ( "scram not possible" )
}
xcheckf ( err , "fetching credentials" )
return err
} )
xcheckf ( err , "read tx" )
} )
2023-02-05 14:30:14 +03:00
s1 , err := ss . ServerFirst ( xscram . Iterations , xscram . Salt )
2023-01-30 16:27:06 +03:00
xcheckf ( err , "scram first server step" )
c . writelinef ( "+ %s" , base64 . StdEncoding . EncodeToString ( [ ] byte ( s1 ) ) )
c2 := xreadContinuation ( )
2023-02-05 14:30:14 +03:00
s3 , err := ss . Finish ( c2 , xscram . SaltedPassword )
2023-01-30 16:27:06 +03:00
if len ( s3 ) > 0 {
c . writelinef ( "+ %s" , base64 . StdEncoding . EncodeToString ( [ ] byte ( s3 ) ) )
}
if err != nil {
2023-01-31 02:22:26 +03:00
c . readline ( false ) // Should be "*" for cancellation.
2023-01-30 16:27:06 +03:00
if errors . Is ( err , scram . ErrInvalidProof ) {
authResult = "badcreds"
2023-12-05 15:35:58 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , ss . Authentication ) , slog . Any ( "remote" , c . remoteIP ) )
2023-01-30 16:27:06 +03:00
xusercodeErrorf ( "AUTHENTICATIONFAILED" , "bad credentials" )
}
xuserErrorf ( "server final: %w" , err )
}
// Client must still respond, but there is nothing to say. See ../rfc/9051:6221
// The message should be empty. todo: should we require it is empty?
xreadContinuation ( )
c . account = acc
acc = nil // Cancel cleanup.
c . username = ss . Authentication
default :
xuserErrorf ( "method not supported" )
}
2023-02-08 23:45:32 +03:00
c . setSlow ( false )
authResult = "ok"
c . authFailed = 0
2023-01-30 16:27:06 +03:00
c . comm = store . RegisterComm ( c . account )
c . state = stateAuthenticated
c . writeresultf ( "%s OK [CAPABILITY %s] authenticate done" , tag , c . capabilities ( ) )
}
// Login logs in with username and password.
//
// Status: Not authenticated.
func ( c * conn ) cmdLogin ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1597 ../rfc/3501:1663
authResult := "error"
defer func ( ) {
metrics . AuthenticationInc ( "imap" , "login" , authResult )
} ( )
2023-02-03 22:33:19 +03:00
// todo: get this line logged with traceauth. the plaintext password is included on the command line, which we've already read (before dispatching to this function).
2023-01-30 16:27:06 +03:00
// Request syntax: ../rfc/9051:6667 ../rfc/3501:4804
p . xspace ( )
userid := p . xastring ( )
p . xspace ( )
password := p . xastring ( )
p . xempty ( )
if ! c . noRequireSTARTTLS && ! c . tls {
// ../rfc/9051:5194
xusercodeErrorf ( "PRIVACYREQUIRED" , "tls required for login" )
}
2023-02-08 23:45:32 +03:00
// For many failed auth attempts, slow down verification attempts.
2023-03-10 12:23:43 +03:00
if c . authFailed > 3 && authFailDelay > 0 {
mox . Sleep ( mox . Context , time . Duration ( c . authFailed - 3 ) * authFailDelay )
2023-02-08 23:45:32 +03:00
}
c . authFailed ++ // Compensated on success.
defer func ( ) {
// On the 3rd failed authentication, start responding slowly. Successful auth will
// cause fast responses again.
if c . authFailed >= 3 {
c . setSlow ( true )
}
} ( )
2023-12-05 15:35:58 +03:00
acc , err := store . OpenEmailAuth ( c . log , userid , password )
2023-01-30 16:27:06 +03:00
if err != nil {
authResult = "badcreds"
var code string
if errors . Is ( err , store . ErrUnknownCredentials ) {
code = "AUTHENTICATIONFAILED"
2023-12-05 15:35:58 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , userid ) , slog . Any ( "remote" , c . remoteIP ) )
2023-01-30 16:27:06 +03:00
}
xusercodeErrorf ( code , "login failed" )
}
c . account = acc
c . username = userid
2023-02-08 23:45:32 +03:00
c . authFailed = 0
c . setSlow ( false )
2023-01-30 16:27:06 +03:00
c . comm = store . RegisterComm ( acc )
c . state = stateAuthenticated
authResult = "ok"
c . writeresultf ( "%s OK [CAPABILITY %s] login done" , tag , c . capabilities ( ) )
}
// Enable explicitly opts in to an extension. A server can typically send new kinds
// of responses to a client. Most extensions do not require an ENABLE because a
// client implicitly opts in to new response syntax by making a requests that uses
// new optional extension request syntax.
//
// State: Authenticated and selected.
func ( c * conn ) cmdEnable ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1652 ../rfc/5161:80
// Examples: ../rfc/9051:1728 ../rfc/5161:147
// Request syntax: ../rfc/9051:6518 ../rfc/5161:207
p . xspace ( )
caps := [ ] string { p . xatom ( ) }
for ! p . empty ( ) {
p . xspace ( )
caps = append ( caps , p . xatom ( ) )
}
// Clients should only send capabilities that need enabling.
// We should only echo that we recognize as needing enabling.
var enabled string
2023-07-24 22:21:05 +03:00
var qresync bool
2023-01-30 16:27:06 +03:00
for _ , s := range caps {
cap := capability ( strings . ToUpper ( s ) )
switch cap {
2023-07-24 22:21:05 +03:00
case capIMAP4rev2 ,
capUTF8Accept ,
capCondstore : // ../rfc/7162:384
c . enabled [ cap ] = true
enabled += " " + s
case capQresync :
2023-01-30 16:27:06 +03:00
c . enabled [ cap ] = true
enabled += " " + s
2023-07-24 22:21:05 +03:00
qresync = true
2023-01-30 16:27:06 +03:00
}
}
2023-07-24 22:21:05 +03:00
// QRESYNC enabled CONDSTORE too ../rfc/7162:1391
if qresync && ! c . enabled [ capCondstore ] {
c . xensureCondstore ( nil )
enabled += " CONDSTORE"
}
2023-01-30 16:27:06 +03:00
// Response syntax: ../rfc/9051:6520 ../rfc/5161:211
c . bwritelinef ( "* ENABLED%s" , enabled )
c . ok ( tag , cmd )
}
2023-07-24 22:21:05 +03:00
// The CONDSTORE extension can be enabled in many different ways. ../rfc/7162:368
// If a mailbox is selected, an untagged OK with HIGHESTMODSEQ is written to the
// client. If tx is non-nil, it is used to read the HIGHESTMODSEQ from the
// database. Otherwise a new read-only transaction is created.
func ( c * conn ) xensureCondstore ( tx * bstore . Tx ) {
if ! c . enabled [ capCondstore ] {
c . enabled [ capCondstore ] = true
// todo spec: can we send an untagged enabled response?
// ../rfc/7162:603
if c . mailboxID <= 0 {
return
}
var modseq store . ModSeq
if tx != nil {
modseq = c . xhighestModSeq ( tx , c . mailboxID )
} else {
c . xdbread ( func ( tx * bstore . Tx ) {
modseq = c . xhighestModSeq ( tx , c . mailboxID )
} )
}
c . bwritelinef ( "* OK [HIGHESTMODSEQ %d] after condstore-enabling command" , modseq . Client ( ) )
}
}
2023-01-30 16:27:06 +03:00
// State: Authenticated and selected.
func ( c * conn ) cmdSelect ( tag , cmd string , p * parser ) {
c . cmdSelectExamine ( true , tag , cmd , p )
}
// State: Authenticated and selected.
func ( c * conn ) cmdExamine ( tag , cmd string , p * parser ) {
c . cmdSelectExamine ( false , tag , cmd , p )
}
// Select and examine are almost the same commands. Select just opens a mailbox for
// read/write and examine opens a mailbox readonly.
//
// State: Authenticated and selected.
func ( c * conn ) cmdSelectExamine ( isselect bool , tag , cmd string , p * parser ) {
2023-07-24 22:21:05 +03:00
// Select command: ../rfc/9051:1754 ../rfc/3501:1743 ../rfc/7162:1146 ../rfc/7162:1432
2023-01-30 16:27:06 +03:00
// Examine command: ../rfc/9051:1868 ../rfc/3501:1855
2023-07-24 22:21:05 +03:00
// Select examples: ../rfc/9051:1831 ../rfc/3501:1826 ../rfc/7162:1159 ../rfc/7162:1479
2023-01-30 16:27:06 +03:00
2023-07-24 22:21:05 +03:00
// Select request syntax: ../rfc/9051:7005 ../rfc/3501:4996 ../rfc/4466:652 ../rfc/7162:2559 ../rfc/7162:2598
2023-01-30 16:27:06 +03:00
// Examine request syntax: ../rfc/9051:6551 ../rfc/3501:4746
p . xspace ( )
name := p . xmailbox ( )
2023-07-24 22:21:05 +03:00
var qruidvalidity uint32
var qrmodseq int64 // QRESYNC required parameters.
var qrknownUIDs , qrknownSeqSet , qrknownUIDSet * numSet // QRESYNC optional parameters.
if p . space ( ) {
seen := map [ string ] bool { }
p . xtake ( "(" )
for len ( seen ) == 0 || ! p . take ( ")" ) {
w := p . xtakelist ( "CONDSTORE" , "QRESYNC" )
if seen [ w ] {
xsyntaxErrorf ( "duplicate select parameter %s" , w )
}
seen [ w ] = true
switch w {
case "CONDSTORE" :
// ../rfc/7162:363
c . xensureCondstore ( nil ) // ../rfc/7162:373
case "QRESYNC" :
// ../rfc/7162:2598
// Note: unlike with CONDSTORE, there are no QRESYNC-related commands/parameters
// that enable capabilities.
if ! c . enabled [ capQresync ] {
// ../rfc/7162:1446
xsyntaxErrorf ( "QRESYNC must first be enabled" )
}
p . xspace ( )
p . xtake ( "(" )
qruidvalidity = p . xnznumber ( ) // ../rfc/7162:2606
p . xspace ( )
qrmodseq = p . xnznumber64 ( )
if p . take ( " " ) {
seqMatchData := p . take ( "(" )
if ! seqMatchData {
ss := p . xnumSet0 ( false , false ) // ../rfc/7162:2608
qrknownUIDs = & ss
seqMatchData = p . take ( " (" )
}
if seqMatchData {
ss0 := p . xnumSet0 ( false , false )
qrknownSeqSet = & ss0
p . xspace ( )
ss1 := p . xnumSet0 ( false , false )
qrknownUIDSet = & ss1
p . xtake ( ")" )
}
}
p . xtake ( ")" )
default :
panic ( "missing case for select param " + w )
}
}
}
2023-01-30 16:27:06 +03:00
p . xempty ( )
// Deselect before attempting the new select. This means we will deselect when an
// error occurs during select.
// ../rfc/9051:1809
if c . state == stateSelected {
2023-07-24 22:21:05 +03:00
// ../rfc/9051:1812 ../rfc/7162:2111
2023-01-30 16:27:06 +03:00
c . bwritelinef ( "* OK [CLOSED] x" )
c . unselect ( )
}
name = xcheckmailboxname ( name , true )
2023-07-24 22:21:05 +03:00
var highestModSeq store . ModSeq
var highDeletedModSeq store . ModSeq
2023-01-30 16:27:06 +03:00
var firstUnseen msgseq = 0
var mb store . Mailbox
c . account . WithRLock ( func ( ) {
c . xdbread ( func ( tx * bstore . Tx ) {
mb = c . xmailbox ( tx , name , "" )
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : mb . ID } )
2023-07-24 22:21:05 +03:00
q . FilterEqual ( "Expunged" , false )
2023-01-30 16:27:06 +03:00
q . SortAsc ( "UID" )
c . uids = [ ] store . UID { }
var seq msgseq = 1
err := q . ForEach ( func ( m store . Message ) error {
c . uids = append ( c . uids , m . UID )
if firstUnseen == 0 && ! m . Seen {
firstUnseen = seq
}
seq ++
return nil
} )
if sanityChecks {
checkUIDs ( c . uids )
}
xcheckf ( err , "fetching uids" )
2023-07-24 22:21:05 +03:00
// Condstore extension, find the highest modseq.
if c . enabled [ capCondstore ] {
highestModSeq = c . xhighestModSeq ( tx , mb . ID )
}
// For QRESYNC, we need to know the highest modset of deleted expunged records to
// maintain synchronization.
if c . enabled [ capQresync ] {
highDeletedModSeq , err = c . account . HighestDeletedModSeq ( tx )
xcheckf ( err , "getting highest deleted modseq" )
}
2023-01-30 16:27:06 +03:00
} )
} )
c . applyChanges ( c . comm . Get ( ) , true )
2023-06-24 01:24:43 +03:00
var flags string
if len ( mb . Keywords ) > 0 {
flags = " " + strings . Join ( mb . Keywords , " " )
}
c . bwritelinef ( ` * FLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent%s) ` , flags )
c . bwritelinef ( ` * OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent \*)] x ` )
2023-01-30 16:27:06 +03:00
if ! c . enabled [ capIMAP4rev2 ] {
c . bwritelinef ( ` * 0 RECENT ` )
}
c . bwritelinef ( ` * %d EXISTS ` , len ( c . uids ) )
if ! c . enabled [ capIMAP4rev2 ] && firstUnseen > 0 {
// ../rfc/9051:8051 ../rfc/3501:1774
c . bwritelinef ( ` * OK [UNSEEN %d] x ` , firstUnseen )
}
c . bwritelinef ( ` * OK [UIDVALIDITY %d] x ` , mb . UIDValidity )
c . bwritelinef ( ` * OK [UIDNEXT %d] x ` , mb . UIDNext )
c . bwritelinef ( ` * LIST () "/" %s ` , astring ( mb . Name ) . pack ( c ) )
2023-07-24 22:21:05 +03:00
if c . enabled [ capCondstore ] {
// ../rfc/7162:417
// ../rfc/7162-eid5055 ../rfc/7162:484 ../rfc/7162:1167
c . bwritelinef ( ` * OK [HIGHESTMODSEQ %d] x ` , highestModSeq . Client ( ) )
}
// If QRESYNC uidvalidity matches, we send any changes. ../rfc/7162:1509
if qruidvalidity == mb . UIDValidity {
// We send the vanished UIDs at the end, so we can easily combine the modseq
// changes and vanished UIDs that result from that, with the vanished UIDs from the
// case where we don't store enough history.
vanishedUIDs := map [ store . UID ] struct { } { }
var preVanished store . UID
var oldClientUID store . UID
// If samples of known msgseq and uid pairs are given (they must be in order), we
// use them to determine the earliest UID for which we send VANISHED responses.
// ../rfc/7162:1579
if qrknownSeqSet != nil {
if ! qrknownSeqSet . isBasicIncreasing ( ) {
xuserErrorf ( "QRESYNC known message sequence set must be numeric and strictly increasing" )
}
if ! qrknownUIDSet . isBasicIncreasing ( ) {
xuserErrorf ( "QRESYNC known uid set must be numeric and strictly increasing" )
}
seqiter := qrknownSeqSet . newIter ( )
uiditer := qrknownUIDSet . newIter ( )
for {
msgseq , ok0 := seqiter . Next ( )
uid , ok1 := uiditer . Next ( )
if ! ok0 && ! ok1 {
break
} else if ! ok0 || ! ok1 {
xsyntaxErrorf ( "invalid combination of known sequence set and uid set, must be of equal length" )
}
i := int ( msgseq - 1 )
if i < 0 || i >= len ( c . uids ) || c . uids [ i ] != store . UID ( uid ) {
if uidSearch ( c . uids , store . UID ( uid ) ) <= 0 {
// We will check this old client UID for consistency below.
oldClientUID = store . UID ( uid )
}
break
}
preVanished = store . UID ( uid + 1 )
}
}
// We gather vanished UIDs and report them at the end. This seems OK because we
// already sent HIGHESTMODSEQ, and a client should know not to commit that value
// until after it has seen the tagged OK of this command. The RFC has a remark
// about ordering of some untagged responses, it's not immediately clear what it
// means, but given the examples appears to allude to servers that decide to not
// send expunge/vanished before the tagged OK.
// ../rfc/7162:1340
// We are reading without account lock. Similar to when we process FETCH/SEARCH
// requests. We don't have to reverify existence of the mailbox, so we don't
// rlock, even briefly.
c . xdbread ( func ( tx * bstore . Tx ) {
if oldClientUID > 0 {
// The client sent a UID that is now removed. This is typically fine. But we check
// that it is consistent with the modseq the client sent. If the UID already didn't
// exist at that modseq, the client may be missing some information.
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : mb . ID , UID : oldClientUID } )
m , err := q . Get ( )
if err == nil {
// If client claims to be up to date up to and including qrmodseq, and the message
// was deleted at or before that time, we send changes from just before that
// modseq, and we send vanished for all UIDs.
if m . Expunged && qrmodseq >= m . ModSeq . Client ( ) {
qrmodseq = m . ModSeq . Client ( ) - 1
preVanished = 0
qrknownUIDs = nil
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . bwritelinef ( "* OK [ALERT] Synchronization inconsistency in client detected. Client tried to sync with a UID that was removed at or after the MODSEQ it sent in the request. Sending all historic message removals for selected mailbox. Full synchronization recommended." )
2023-07-24 22:21:05 +03:00
}
} else if err != bstore . ErrAbsent {
xcheckf ( err , "checking old client uid" )
}
}
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : mb . ID } )
// Note: we don't filter by Expunged.
q . FilterGreater ( "ModSeq" , store . ModSeqFromClient ( qrmodseq ) )
q . FilterLessEqual ( "ModSeq" , highestModSeq )
q . SortAsc ( "ModSeq" )
err := q . ForEach ( func ( m store . Message ) error {
if m . Expunged && m . UID < preVanished {
return nil
}
// If known UIDs was specified, we only report about those UIDs. ../rfc/7162:1523
if qrknownUIDs != nil && ! qrknownUIDs . contains ( uint32 ( m . UID ) ) {
return nil
}
if m . Expunged {
vanishedUIDs [ m . UID ] = struct { } { }
return nil
}
msgseq := c . sequence ( m . UID )
if msgseq > 0 {
c . bwritelinef ( "* %d FETCH (UID %d FLAGS %s MODSEQ (%d))" , msgseq , m . UID , flaglist ( m . Flags , m . Keywords ) . pack ( c ) , m . ModSeq . Client ( ) )
}
return nil
} )
xcheckf ( err , "listing changed messages" )
} )
// Add UIDs from client's known UID set to vanished list if we don't have enough history.
if qrmodseq < highDeletedModSeq . Client ( ) {
// If no known uid set was in the request, we substitute 1:max or the empty set.
// ../rfc/7162:1524
if qrknownUIDs == nil {
if len ( c . uids ) > 0 {
qrknownUIDs = & numSet { ranges : [ ] numRange { { first : setNumber { number : 1 } , last : & setNumber { number : uint32 ( c . uids [ len ( c . uids ) - 1 ] ) } } } }
} else {
qrknownUIDs = & numSet { }
}
}
iter := qrknownUIDs . newIter ( )
for {
v , ok := iter . Next ( )
if ! ok {
break
}
if c . sequence ( store . UID ( v ) ) <= 0 {
vanishedUIDs [ store . UID ( v ) ] = struct { } { }
}
}
}
// Now that we have all vanished UIDs, send them over compactly.
if len ( vanishedUIDs ) > 0 {
l := maps . Keys ( vanishedUIDs )
sort . Slice ( l , func ( i , j int ) bool {
return l [ i ] < l [ j ]
} )
// ../rfc/7162:1985
for _ , s := range compactUIDSet ( l ) . Strings ( 4 * 1024 - 32 ) {
c . bwritelinef ( "* VANISHED (EARLIER) %s" , s )
}
}
}
2023-01-30 16:27:06 +03:00
if isselect {
c . bwriteresultf ( "%s OK [READ-WRITE] x" , tag )
c . readonly = false
} else {
c . bwriteresultf ( "%s OK [READ-ONLY] x" , tag )
c . readonly = true
}
c . mailboxID = mb . ID
c . state = stateSelected
c . searchResult = nil
c . xflush ( )
}
// Create makes a new mailbox, and its parents too if absent.
//
// State: Authenticated and selected.
func ( c * conn ) cmdCreate ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1900 ../rfc/3501:1888
// Examples: ../rfc/9051:1951 ../rfc/6154:411 ../rfc/4466:212 ../rfc/3501:1933
// Request syntax: ../rfc/9051:6484 ../rfc/6154:468 ../rfc/4466:500 ../rfc/3501:4687
p . xspace ( )
name := p . xmailbox ( )
// todo: support CREATE-SPECIAL-USE ../rfc/6154:296
p . xempty ( )
origName := name
name = strings . TrimRight ( name , "/" ) // ../rfc/9051:1930
name = xcheckmailboxname ( name , false )
var changes [ ] store . Change
var created [ ] string // Created mailbox names.
c . account . WithWLock ( func ( ) {
c . xdbwrite ( func ( tx * bstore . Tx ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var exists bool
var err error
changes , created , exists , err = c . account . MailboxCreate ( tx , name )
if exists {
// ../rfc/9051:1914
xuserErrorf ( "mailbox already exists" )
2023-01-30 16:27:06 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xcheckf ( err , "creating mailbox" )
2023-01-30 16:27:06 +03:00
} )
c . broadcast ( changes )
} )
for _ , n := range created {
var more string
if n == name && name != origName && ! ( name == "Inbox" || strings . HasPrefix ( name , "Inbox/" ) ) {
more = fmt . Sprintf ( ` ("OLDNAME" (%s)) ` , string0 ( origName ) . pack ( c ) )
}
c . bwritelinef ( ` * LIST (\Subscribed) "/" %s%s ` , astring ( n ) . pack ( c ) , more )
}
c . ok ( tag , cmd )
}
// Delete removes a mailbox and all its messages.
// Inbox cannot be removed.
//
// State: Authenticated and selected.
func ( c * conn ) cmdDelete ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:1972 ../rfc/3501:1946
// Examples: ../rfc/9051:2025 ../rfc/3501:1992
// Request syntax: ../rfc/9051:6505 ../rfc/3501:4716
p . xspace ( )
name := p . xmailbox ( )
p . xempty ( )
name = xcheckmailboxname ( name , false )
// Messages to remove after having broadcasted the removal of messages.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var removeMessageIDs [ ] int64
2023-01-30 16:27:06 +03:00
c . account . WithWLock ( func ( ) {
var mb store . Mailbox
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var changes [ ] store . Change
2023-01-30 16:27:06 +03:00
c . xdbwrite ( func ( tx * bstore . Tx ) {
mb = c . xmailbox ( tx , name , "NONEXISTENT" )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var hasChildren bool
var err error
changes , removeMessageIDs , hasChildren , err = c . account . MailboxDelete ( context . TODO ( ) , c . log , tx , mb )
if hasChildren {
2023-01-30 16:27:06 +03:00
xusercodeErrorf ( "HASCHILDREN" , "mailbox has a child, only leaf mailboxes can be deleted" )
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xcheckf ( err , "deleting mailbox" )
2023-01-30 16:27:06 +03:00
} )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . broadcast ( changes )
2023-01-30 16:27:06 +03:00
} )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
for _ , mID := range removeMessageIDs {
p := c . account . MessagePath ( mID )
2023-02-16 15:22:00 +03:00
err := os . Remove ( p )
2023-12-05 15:35:58 +03:00
c . log . Check ( err , "removing message file for mailbox delete" , slog . String ( "path" , p ) )
2023-01-30 16:27:06 +03:00
}
c . ok ( tag , cmd )
}
// Rename changes the name of a mailbox.
// Renaming INBOX is special, it moves the inbox messages to a new mailbox, leaving inbox empty.
// Renaming a mailbox with submailboxes also renames all submailboxes.
// Subscriptions stay with the old name, though newly created missing parent
// mailboxes for the destination name are automatically subscribed.
//
// State: Authenticated and selected.
func ( c * conn ) cmdRename ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:2062 ../rfc/3501:2040
// Examples: ../rfc/9051:2132 ../rfc/3501:2092
// Request syntax: ../rfc/9051:6863 ../rfc/3501:4908
p . xspace ( )
src := p . xmailbox ( )
p . xspace ( )
dst := p . xmailbox ( )
p . xempty ( )
src = xcheckmailboxname ( src , true )
dst = xcheckmailboxname ( dst , false )
c . account . WithWLock ( func ( ) {
var changes [ ] store . Change
c . xdbwrite ( func ( tx * bstore . Tx ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
srcMB := c . xmailbox ( tx , src , "NONEXISTENT" )
2023-01-30 16:27:06 +03:00
2023-06-30 18:19:29 +03:00
// Inbox is very special. Unlike other mailboxes, its children are not moved. And
// unlike a regular move, its messages are moved to a newly created mailbox. We do
// indeed create a new destination mailbox and actually move the messages.
2023-01-30 16:27:06 +03:00
// ../rfc/9051:2101
if src == "Inbox" {
2023-04-20 15:16:56 +03:00
exists , err := c . account . MailboxExists ( tx , dst )
xcheckf ( err , "checking if destination mailbox exists" )
if exists {
2023-01-30 16:27:06 +03:00
xusercodeErrorf ( "ALREADYEXISTS" , "destination mailbox %q already exists" , dst )
}
if dst == src {
xuserErrorf ( "cannot move inbox to itself" )
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
uidval , err := c . account . NextUIDValidity ( tx )
xcheckf ( err , "next uid validity" )
2023-01-30 16:27:06 +03:00
dstMB := store . Mailbox {
Name : dst ,
UIDValidity : uidval ,
UIDNext : 1 ,
2023-07-24 09:49:19 +03:00
Keywords : srcMB . Keywords ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
HaveCounts : true ,
2023-01-30 16:27:06 +03:00
}
2023-04-20 15:16:56 +03:00
err = tx . Insert ( & dstMB )
2023-01-30 16:27:06 +03:00
xcheckf ( err , "create new destination mailbox" )
2023-07-24 22:21:05 +03:00
modseq , err := c . account . NextModSeq ( tx )
xcheckf ( err , "assigning next modseq" )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = make ( [ ] store . Change , 2 ) // Placeholders filled in below.
2023-06-30 18:19:29 +03:00
// Move existing messages, with their ID's and on-disk files intact, to the new
2023-07-24 22:21:05 +03:00
// mailbox. We keep the expunged messages, the destination mailbox doesn't care
// about them.
2023-06-30 18:19:29 +03:00
var oldUIDs [ ] store . UID
2023-01-30 16:27:06 +03:00
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : srcMB . ID } )
2023-07-24 22:21:05 +03:00
q . FilterEqual ( "Expunged" , false )
2023-06-30 18:19:29 +03:00
q . SortAsc ( "UID" )
err = q . ForEach ( func ( m store . Message ) error {
2023-07-24 22:21:05 +03:00
om := m
om . ID = 0
om . ModSeq = modseq
om . PrepareExpunge ( )
oldUIDs = append ( oldUIDs , om . UID )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mc := m . MailboxCounts ( )
srcMB . Sub ( mc )
dstMB . Add ( mc )
2023-06-30 18:19:29 +03:00
m . MailboxID = dstMB . ID
m . UID = dstMB . UIDNext
dstMB . UIDNext ++
2023-07-24 22:21:05 +03:00
m . CreateSeq = modseq
m . ModSeq = modseq
2023-06-30 18:19:29 +03:00
if err := tx . Update ( & m ) ; err != nil {
return fmt . Errorf ( "updating message to move to new mailbox: %w" , err )
}
2023-07-24 22:21:05 +03:00
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = append ( changes , m . ChangeAddUID ( ) )
2023-07-24 22:21:05 +03:00
if err := tx . Insert ( & om ) ; err != nil {
return fmt . Errorf ( "adding empty expunge message record to inbox: %w" , err )
}
2023-06-30 18:19:29 +03:00
return nil
} )
2023-01-30 16:27:06 +03:00
xcheckf ( err , "moving messages from inbox to destination mailbox" )
2023-06-30 18:19:29 +03:00
err = tx . Update ( & dstMB )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xcheckf ( err , "updating uidnext and counts in destination mailbox" )
err = tx . Update ( & srcMB )
xcheckf ( err , "updating counts for inbox" )
2023-06-30 18:19:29 +03:00
2023-01-30 16:27:06 +03:00
var dstFlags [ ] string
if tx . Get ( & store . Subscription { Name : dstMB . Name } ) == nil {
dstFlags = [ ] string { ` \Subscribed ` }
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes [ 0 ] = store . ChangeRemoveUIDs { MailboxID : srcMB . ID , UIDs : oldUIDs , ModSeq : modseq }
changes [ 1 ] = store . ChangeAddMailbox { Mailbox : dstMB , Flags : dstFlags }
// changes[2:...] are ChangeAddUIDs
changes = append ( changes , srcMB . ChangeCounts ( ) , dstMB . ChangeCounts ( ) )
2023-01-30 16:27:06 +03:00
return
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var notExists , alreadyExists bool
var err error
changes , _ , notExists , alreadyExists , err = c . account . MailboxRename ( tx , srcMB , dst )
if notExists {
2023-01-30 16:27:06 +03:00
// ../rfc/9051:5140
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xusercodeErrorf ( "NONEXISTENT" , "%s" , err )
} else if alreadyExists {
xusercodeErrorf ( "ALREADYEXISTS" , "%s" , err )
2023-01-30 16:27:06 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xcheckf ( err , "renaming mailbox" )
2023-01-30 16:27:06 +03:00
} )
c . broadcast ( changes )
} )
c . ok ( tag , cmd )
}
// Subscribe marks a mailbox path as subscribed. The mailbox does not have to
// exist. Subscribed may mean an email client will show the mailbox in its UI
// and/or periodically fetch new messages for the mailbox.
//
// State: Authenticated and selected.
func ( c * conn ) cmdSubscribe ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:2172 ../rfc/3501:2135
// Examples: ../rfc/9051:2198 ../rfc/3501:2162
// Request syntax: ../rfc/9051:7083 ../rfc/3501:5059
p . xspace ( )
name := p . xmailbox ( )
p . xempty ( )
name = xcheckmailboxname ( name , true )
c . account . WithWLock ( func ( ) {
var changes [ ] store . Change
c . xdbwrite ( func ( tx * bstore . Tx ) {
2023-04-20 15:16:56 +03:00
var err error
changes , err = c . account . SubscriptionEnsure ( tx , name )
xcheckf ( err , "ensuring subscription" )
2023-01-30 16:27:06 +03:00
} )
c . broadcast ( changes )
} )
c . ok ( tag , cmd )
}
// Unsubscribe marks a mailbox as not subscribed. The mailbox doesn't have to exist.
//
// State: Authenticated and selected.
func ( c * conn ) cmdUnsubscribe ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:2203 ../rfc/3501:2166
// Examples: ../rfc/9051:2219 ../rfc/3501:2181
// Request syntax: ../rfc/9051:7143 ../rfc/3501:5077
p . xspace ( )
name := p . xmailbox ( )
p . xempty ( )
name = xcheckmailboxname ( name , true )
c . account . WithWLock ( func ( ) {
c . xdbwrite ( func ( tx * bstore . Tx ) {
// It's OK if not currently subscribed, ../rfc/9051:2215
err := tx . Delete ( & store . Subscription { Name : name } )
if err == bstore . ErrAbsent {
2023-04-20 15:16:56 +03:00
exists , err := c . account . MailboxExists ( tx , name )
xcheckf ( err , "checking if mailbox exists" )
if ! exists {
2023-01-30 16:27:06 +03:00
xuserErrorf ( "mailbox does not exist" )
}
return
}
xcheckf ( err , "removing subscription" )
} )
// todo: can we send untagged message about a mailbox no longer being subscribed?
} )
c . ok ( tag , cmd )
}
// LSUB command for listing subscribed mailboxes.
// Removed in IMAP4rev2, only in IMAP4rev1.
//
// State: Authenticated and selected.
func ( c * conn ) cmdLsub ( tag , cmd string , p * parser ) {
// Command: ../rfc/3501:2374
// Examples: ../rfc/3501:2415
// Request syntax: ../rfc/3501:4806
p . xspace ( )
ref := p . xmailbox ( )
p . xspace ( )
pattern := p . xlistMailbox ( )
p . xempty ( )
re := xmailboxPatternMatcher ( ref , [ ] string { pattern } )
var lines [ ] string
c . xdbread ( func ( tx * bstore . Tx ) {
q := bstore . QueryTx [ store . Subscription ] ( tx )
q . SortAsc ( "Name" )
subscriptions , err := q . List ( )
xcheckf ( err , "querying subscriptions" )
have := map [ string ] bool { }
subscribedKids := map [ string ] bool { }
ispercent := strings . HasSuffix ( pattern , "%" )
for _ , sub := range subscriptions {
name := sub . Name
if ispercent {
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
for p := path . Dir ( name ) ; p != "." ; p = path . Dir ( p ) {
2023-01-30 16:27:06 +03:00
subscribedKids [ p ] = true
}
}
if ! re . MatchString ( name ) {
continue
}
have [ name ] = true
line := fmt . Sprintf ( ` * LSUB () "/" %s ` , astring ( name ) . pack ( c ) )
lines = append ( lines , line )
}
// ../rfc/3501:2394
if ! ispercent {
return
}
qmb := bstore . QueryTx [ store . Mailbox ] ( tx )
qmb . SortAsc ( "Name" )
err = qmb . ForEach ( func ( mb store . Mailbox ) error {
if have [ mb . Name ] || ! subscribedKids [ mb . Name ] || ! re . MatchString ( mb . Name ) {
return nil
}
line := fmt . Sprintf ( ` * LSUB (\NoSelect) "/" %s ` , astring ( mb . Name ) . pack ( c ) )
lines = append ( lines , line )
return nil
} )
xcheckf ( err , "querying mailboxes" )
} )
// Response syntax: ../rfc/3501:4833 ../rfc/3501:4837
for _ , line := range lines {
c . bwritelinef ( "%s" , line )
}
c . ok ( tag , cmd )
}
// The namespace command returns the mailbox path separator. We only implement
// the personal mailbox hierarchy, no shared/other.
//
// In IMAP4rev2, it was an extension before.
//
// State: Authenticated and selected.
func ( c * conn ) cmdNamespace ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:3098 ../rfc/2342:137
// Examples: ../rfc/9051:3117 ../rfc/2342:155
// Request syntax: ../rfc/9051:6767 ../rfc/2342:410
p . xempty ( )
// Response syntax: ../rfc/9051:6778 ../rfc/2342:415
c . bwritelinef ( ` * NAMESPACE (("" "/")) NIL NIL ` )
c . ok ( tag , cmd )
}
// The status command returns information about a mailbox, such as the number of
// messages, "uid validity", etc. Nowadays, the extended LIST command can return
// the same information about many mailboxes for one command.
//
// State: Authenticated and selected.
func ( c * conn ) cmdStatus ( tag , cmd string , p * parser ) {
2023-07-24 22:21:05 +03:00
// Command: ../rfc/9051:3328 ../rfc/3501:2424 ../rfc/7162:1127
// Examples: ../rfc/9051:3400 ../rfc/3501:2501 ../rfc/7162:1139
2023-01-30 16:27:06 +03:00
// Request syntax: ../rfc/9051:7053 ../rfc/3501:5036
p . xspace ( )
name := p . xmailbox ( )
p . xspace ( )
p . xtake ( "(" )
attrs := [ ] string { p . xstatusAtt ( ) }
for ! p . take ( ")" ) {
p . xspace ( )
attrs = append ( attrs , p . xstatusAtt ( ) )
}
p . xempty ( )
name = xcheckmailboxname ( name , true )
var mb store . Mailbox
var responseLine string
c . account . WithRLock ( func ( ) {
c . xdbread ( func ( tx * bstore . Tx ) {
mb = c . xmailbox ( tx , name , "" )
responseLine = c . xstatusLine ( tx , mb , attrs )
} )
} )
c . bwritelinef ( "%s" , responseLine )
c . ok ( tag , cmd )
}
// Response syntax: ../rfc/9051:6681 ../rfc/9051:7070 ../rfc/9051:7059 ../rfc/3501:4834
func ( c * conn ) xstatusLine ( tx * bstore . Tx , mb store . Mailbox , attrs [ ] string ) string {
status := [ ] string { }
for _ , a := range attrs {
A := strings . ToUpper ( a )
switch A {
case "MESSAGES" :
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
status = append ( status , A , fmt . Sprintf ( "%d" , mb . Total + mb . Deleted ) )
2023-01-30 16:27:06 +03:00
case "UIDNEXT" :
status = append ( status , A , fmt . Sprintf ( "%d" , mb . UIDNext ) )
case "UIDVALIDITY" :
status = append ( status , A , fmt . Sprintf ( "%d" , mb . UIDValidity ) )
case "UNSEEN" :
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
status = append ( status , A , fmt . Sprintf ( "%d" , mb . Unseen ) )
2023-01-30 16:27:06 +03:00
case "DELETED" :
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
status = append ( status , A , fmt . Sprintf ( "%d" , mb . Deleted ) )
2023-01-30 16:27:06 +03:00
case "SIZE" :
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
status = append ( status , A , fmt . Sprintf ( "%d" , mb . Size ) )
2023-01-30 16:27:06 +03:00
case "RECENT" :
status = append ( status , A , "0" )
case "APPENDLIMIT" :
// ../rfc/7889:255
status = append ( status , A , "NIL" )
2023-07-24 22:21:05 +03:00
case "HIGHESTMODSEQ" :
// ../rfc/7162:366
status = append ( status , A , fmt . Sprintf ( "%d" , c . xhighestModSeq ( tx , mb . ID ) . Client ( ) ) )
2023-01-30 16:27:06 +03:00
default :
xsyntaxErrorf ( "unknown attribute %q" , a )
}
}
return fmt . Sprintf ( "* STATUS %s (%s)" , astring ( mb . Name ) . pack ( c ) , strings . Join ( status , " " ) )
}
2023-06-24 01:24:43 +03:00
func flaglist ( fl store . Flags , keywords [ ] string ) listspace {
2023-01-30 16:27:06 +03:00
l := listspace { }
flag := func ( v bool , s string ) {
if v {
l = append ( l , bare ( s ) )
}
}
flag ( fl . Seen , ` \Seen ` )
flag ( fl . Answered , ` \Answered ` )
flag ( fl . Flagged , ` \Flagged ` )
flag ( fl . Deleted , ` \Deleted ` )
flag ( fl . Draft , ` \Draft ` )
flag ( fl . Forwarded , ` $Forwarded ` )
flag ( fl . Junk , ` $Junk ` )
flag ( fl . Notjunk , ` $NotJunk ` )
flag ( fl . Phishing , ` $Phishing ` )
flag ( fl . MDNSent , ` $MDNSent ` )
2023-06-24 01:24:43 +03:00
for _ , k := range keywords {
l = append ( l , bare ( k ) )
}
2023-01-30 16:27:06 +03:00
return l
}
// Append adds a message to a mailbox.
//
// State: Authenticated and selected.
func ( c * conn ) cmdAppend ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:3406 ../rfc/6855:204 ../rfc/3501:2527
// Examples: ../rfc/9051:3482 ../rfc/3501:2589
// Request syntax: ../rfc/9051:6325 ../rfc/6855:219 ../rfc/3501:4547
p . xspace ( )
name := p . xmailbox ( )
p . xspace ( )
var storeFlags store . Flags
2023-06-24 01:24:43 +03:00
var keywords [ ] string
2023-01-30 16:27:06 +03:00
if p . hasPrefix ( "(" ) {
// Error must be a syntax error, to properly abort the connection due to literal.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var err error
storeFlags , keywords , err = store . ParseFlagsKeywords ( p . xflagList ( ) )
if err != nil {
xsyntaxErrorf ( "parsing flags: %v" , err )
}
2023-01-30 16:27:06 +03:00
p . xspace ( )
}
var tm time . Time
if p . hasPrefix ( ` " ` ) {
tm = p . xdateTime ( )
p . xspace ( )
} else {
tm = time . Now ( )
}
// todo: only with utf8 should we we accept message headers with utf-8. we currently always accept them.
// todo: this is only relevant if we also support the CATENATE extension?
// ../rfc/6855:204
utf8 := p . take ( "UTF8 (" )
size , sync := p . xliteralSize ( 0 , utf8 )
name = xcheckmailboxname ( name , true )
c . xdbread ( func ( tx * bstore . Tx ) {
c . xmailbox ( tx , name , "TRYCREATE" )
} )
if sync {
2023-07-24 20:54:55 +03:00
c . writelinef ( "+ " )
2023-01-30 16:27:06 +03:00
}
// Read the message into a temporary file.
2023-12-05 15:35:58 +03:00
msgFile , err := store . CreateMessageTemp ( c . log , "imap-append" )
2023-01-30 16:27:06 +03:00
xcheckf ( err , "creating temp file for message" )
defer func ( ) {
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
p := msgFile . Name ( )
err := msgFile . Close ( )
c . xsanity ( err , "closing APPEND temporary file" )
err = os . Remove ( p )
c . xsanity ( err , "removing APPEND temporary file" )
2023-01-30 16:27:06 +03:00
} ( )
2023-02-03 22:33:19 +03:00
defer c . xtrace ( mlog . LevelTracedata ) ( )
2023-08-11 15:07:49 +03:00
mw := message . NewWriter ( msgFile )
2023-01-30 16:27:06 +03:00
msize , err := io . Copy ( mw , io . LimitReader ( c . br , size ) )
2023-02-03 22:33:19 +03:00
c . xtrace ( mlog . LevelTrace ) // Restore.
2023-01-30 16:27:06 +03:00
if err != nil {
// Cannot use xcheckf due to %w handling of errIO.
panic ( fmt . Errorf ( "reading literal message: %s (%w)" , err , errIO ) )
}
if msize != size {
xserverErrorf ( "read %d bytes for message, expected %d (%w)" , msize , size , errIO )
}
if utf8 {
line := c . readline ( false )
np := newParser ( line , c )
np . xtake ( ")" )
np . xempty ( )
} else {
line := c . readline ( false )
np := newParser ( line , c )
np . xempty ( )
}
p . xempty ( )
if ! sync {
name = xcheckmailboxname ( name , true )
}
var mb store . Mailbox
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var m store . Message
2023-01-30 16:27:06 +03:00
var pendingChanges [ ] store . Change
c . account . WithWLock ( func ( ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var changes [ ] store . Change
2023-01-30 16:27:06 +03:00
c . xdbwrite ( func ( tx * bstore . Tx ) {
mb = c . xmailbox ( tx , name , "TRYCREATE" )
2023-06-24 01:24:43 +03:00
// Ensure keywords are stored in mailbox.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mbKwChanged bool
mb . Keywords , mbKwChanged = store . MergeKeywords ( mb . Keywords , keywords )
if mbKwChanged {
changes = append ( changes , mb . ChangeKeywords ( ) )
2023-06-24 01:24:43 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
m = store . Message {
2023-01-30 16:27:06 +03:00
MailboxID : mb . ID ,
MailboxOrigID : mb . ID ,
Received : tm ,
Flags : storeFlags ,
2023-06-24 01:24:43 +03:00
Keywords : keywords ,
2023-12-11 17:13:57 +03:00
Size : mw . Size ,
2023-01-30 16:27:06 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mb . Add ( m . MailboxCounts ( ) )
// Update mailbox before delivering, which updates uidnext which we mustn't overwrite.
err = tx . Update ( & mb )
xcheckf ( err , "updating mailbox counts" )
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
err := c . account . DeliverMessage ( c . log , tx , & m , msgFile , true , false , false )
2023-04-20 15:16:56 +03:00
xcheckf ( err , "delivering message" )
2023-01-30 16:27:06 +03:00
} )
// Fetch pending changes, possibly with new UIDs, so we can apply them before adding our own new UID.
if c . comm != nil {
pendingChanges = c . comm . Get ( )
}
// Broadcast the change to other connections.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = append ( changes , m . ChangeAddUID ( ) , mb . ChangeCounts ( ) )
c . broadcast ( changes )
2023-01-30 16:27:06 +03:00
} )
if c . mailboxID == mb . ID {
c . applyChanges ( pendingChanges , false )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . uidAppend ( m . UID )
2023-07-24 22:21:05 +03:00
// todo spec: with condstore/qresync, is there a mechanism to the client know the modseq for the appended uid? in theory an untagged fetch with the modseq after the OK APPENDUID could make sense, but this probably isn't allowed.
2023-01-30 16:27:06 +03:00
c . bwritelinef ( "* %d EXISTS" , len ( c . uids ) )
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . writeresultf ( "%s OK [APPENDUID %d %d] appended" , tag , mb . UIDValidity , m . UID )
2023-01-30 16:27:06 +03:00
}
// Idle makes a client wait until the server sends untagged updates, e.g. about
// message delivery or mailbox create/rename/delete/subscription, etc. It allows a
// client to get updates in real-time, not needing the use for NOOP.
//
// State: Authenticated and selected.
func ( c * conn ) cmdIdle ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:3542 ../rfc/2177:49
// Example: ../rfc/9051:3589 ../rfc/2177:119
// Request syntax: ../rfc/9051:6594 ../rfc/2177:163
p . xempty ( )
c . writelinef ( "+ waiting" )
var line string
wait :
for {
select {
case le := <- c . lineChan ( ) :
c . line = nil
xcheckf ( le . err , "get line" )
line = le . line
break wait
2023-07-23 16:28:37 +03:00
case <- c . comm . Pending :
c . applyChanges ( c . comm . Get ( ) , false )
2023-01-30 16:27:06 +03:00
c . xflush ( )
2023-02-16 11:57:27 +03:00
case <- mox . Shutdown . Done ( ) :
2023-01-30 16:27:06 +03:00
// ../rfc/9051:5375
c . writelinef ( "* BYE shutting down" )
panic ( errIO )
}
}
// Reset the write deadline. In case of little activity, with a command timeout of
// 30 minutes, we have likely passed it.
2023-02-16 15:22:00 +03:00
err := c . conn . SetWriteDeadline ( time . Now ( ) . Add ( 5 * time . Minute ) )
c . log . Check ( err , "setting write deadline" )
2023-01-30 16:27:06 +03:00
if strings . ToUpper ( line ) != "DONE" {
// We just close the connection because our protocols are out of sync.
panic ( fmt . Errorf ( "%w: in IDLE, expected DONE" , errIO ) )
}
c . ok ( tag , cmd )
}
// Check is an old deprecated command that is supposed to execute some mailbox consistency checks.
//
// State: Selected
func ( c * conn ) cmdCheck ( tag , cmd string , p * parser ) {
// Command: ../rfc/3501:2618
// Request syntax: ../rfc/3501:4679
p . xempty ( )
c . account . WithRLock ( func ( ) {
c . xdbread ( func ( tx * bstore . Tx ) {
c . xmailboxID ( tx , c . mailboxID ) // Validate.
} )
} )
c . ok ( tag , cmd )
}
// Close undoes select/examine, closing the currently opened mailbox and deleting
// messages that were marked for deletion with the \Deleted flag.
//
// State: Selected
func ( c * conn ) cmdClose ( tag , cmd string , p * parser ) {
2023-07-24 22:21:05 +03:00
// Command: ../rfc/9051:3636 ../rfc/3501:2652 ../rfc/7162:1836
2023-01-30 16:27:06 +03:00
// Request syntax: ../rfc/9051:6476 ../rfc/3501:4679
p . xempty ( )
if c . readonly {
c . unselect ( )
c . ok ( tag , cmd )
return
}
2023-07-24 22:21:05 +03:00
remove , _ := c . xexpunge ( nil , true )
2023-01-30 16:27:06 +03:00
defer func ( ) {
for _ , m := range remove {
p := c . account . MessagePath ( m . ID )
err := os . Remove ( p )
c . xsanity ( err , "removing message file for expunge for close" )
}
} ( )
c . unselect ( )
c . ok ( tag , cmd )
}
// expunge messages marked for deletion in currently selected/active mailbox.
// if uidSet is not nil, only messages matching the set are deleted.
2023-07-24 22:21:05 +03:00
//
// messages that have been marked expunged from the database are returned, but the
// corresponding files still have to be removed.
//
// the highest modseq in the mailbox is returned, typically associated with the
// removal of the messages, but if no messages were expunged the current latest max
// modseq for the mailbox is returned.
func ( c * conn ) xexpunge ( uidSet * numSet , missingMailboxOK bool ) ( remove [ ] store . Message , highestModSeq store . ModSeq ) {
var modseq store . ModSeq
2023-01-30 16:27:06 +03:00
c . account . WithWLock ( func ( ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mb store . Mailbox
2023-01-30 16:27:06 +03:00
c . xdbwrite ( func ( tx * bstore . Tx ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mb = store . Mailbox { ID : c . mailboxID }
2023-01-30 16:27:06 +03:00
err := tx . Get ( & mb )
if err == bstore . ErrAbsent {
if missingMailboxOK {
return
}
xuserErrorf ( "%w" , store . ErrUnknownMailbox )
}
qm := bstore . QueryTx [ store . Message ] ( tx )
qm . FilterNonzero ( store . Message { MailboxID : c . mailboxID } )
qm . FilterEqual ( "Deleted" , true )
2023-07-24 22:21:05 +03:00
qm . FilterEqual ( "Expunged" , false )
2023-01-30 16:27:06 +03:00
qm . FilterFn ( func ( m store . Message ) bool {
// Only remove if this session knows about the message and if present in optional uidSet.
return uidSearch ( c . uids , m . UID ) > 0 && ( uidSet == nil || uidSet . containsUID ( m . UID , c . uids , c . searchResult ) )
} )
qm . SortAsc ( "UID" )
remove , err = qm . List ( )
xcheckf ( err , "listing messages to delete" )
if len ( remove ) == 0 {
2023-07-24 22:21:05 +03:00
highestModSeq = c . xhighestModSeq ( tx , c . mailboxID )
2023-01-30 16:27:06 +03:00
return
}
2023-07-24 22:21:05 +03:00
// Assign new modseq.
modseq , err = c . account . NextModSeq ( tx )
xcheckf ( err , "assigning next modseq" )
highestModSeq = modseq
2023-01-30 16:27:06 +03:00
removeIDs := make ( [ ] int64 , len ( remove ) )
anyIDs := make ( [ ] any , len ( remove ) )
for i , m := range remove {
removeIDs [ i ] = m . ID
anyIDs [ i ] = m . ID
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mb . Sub ( m . MailboxCounts ( ) )
2023-08-23 17:13:39 +03:00
// Update "remove", because RetrainMessage below will save the message.
remove [ i ] . Expunged = true
remove [ i ] . ModSeq = modseq
2023-01-30 16:27:06 +03:00
}
qmr := bstore . QueryTx [ store . Recipient ] ( tx )
qmr . FilterEqual ( "MessageID" , anyIDs ... )
_ , err = qmr . Delete ( )
xcheckf ( err , "removing message recipients" )
qm = bstore . QueryTx [ store . Message ] ( tx )
qm . FilterIDs ( removeIDs )
2023-08-23 17:13:39 +03:00
n , err := qm . UpdateNonzero ( store . Message { Expunged : true , ModSeq : modseq } )
if err == nil && n != len ( removeIDs ) {
err = fmt . Errorf ( "only %d messages set to expunged, expected %d" , n , len ( removeIDs ) )
}
2023-07-24 22:21:05 +03:00
xcheckf ( err , "marking messages marked for deleted as expunged" )
2023-01-30 16:27:06 +03:00
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
err = tx . Update ( & mb )
xcheckf ( err , "updating mailbox counts" )
2023-07-24 22:21:05 +03:00
// Mark expunged messages as not needing training, then retrain them, so if they
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
// were trained, they get untrained.
for i := range remove {
remove [ i ] . Junk = false
remove [ i ] . Notjunk = false
2023-01-30 16:27:06 +03:00
}
2023-05-22 15:40:36 +03:00
err = c . account . RetrainMessages ( context . TODO ( ) , c . log , tx , remove , true )
2023-07-24 22:21:05 +03:00
xcheckf ( err , "untraining expunged messages" )
2023-01-30 16:27:06 +03:00
} )
// Broadcast changes to other connections. We may not have actually removed any
// messages, so take care not to send an empty update.
if len ( remove ) > 0 {
ouids := make ( [ ] store . UID , len ( remove ) )
for i , m := range remove {
ouids [ i ] = m . UID
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes := [ ] store . Change {
store . ChangeRemoveUIDs { MailboxID : c . mailboxID , UIDs : ouids , ModSeq : modseq } ,
mb . ChangeCounts ( ) ,
}
2023-01-30 16:27:06 +03:00
c . broadcast ( changes )
}
} )
2023-07-24 22:21:05 +03:00
return remove , highestModSeq
2023-01-30 16:27:06 +03:00
}
// Unselect is similar to close in that it closes the currently active mailbox, but
// it does not remove messages marked for deletion.
//
// State: Selected
func ( c * conn ) cmdUnselect ( tag , cmd string , p * parser ) {
// Command: ../rfc/9051:3667 ../rfc/3691:89
// Request syntax: ../rfc/9051:6476 ../rfc/3691:135
p . xempty ( )
c . unselect ( )
c . ok ( tag , cmd )
}
// Expunge deletes messages marked with \Deleted in the currently selected mailbox.
// Clients are wiser to use UID EXPUNGE because it allows a UID sequence set to
// explicitly opt in to removing specific messages.
//
// State: Selected
func ( c * conn ) cmdExpunge ( tag , cmd string , p * parser ) {
2023-07-24 22:21:05 +03:00
// Command: ../rfc/9051:3687 ../rfc/3501:2695 ../rfc/7162:1770
2023-01-30 16:27:06 +03:00
// Request syntax: ../rfc/9051:6476 ../rfc/3501:4679
p . xempty ( )
if c . readonly {
xuserErrorf ( "mailbox open in read-only mode" )
}
c . cmdxExpunge ( tag , cmd , nil )
}
// UID expunge deletes messages marked with \Deleted in the currently selected
// mailbox if they match a UID sequence set.
//
// State: Selected
func ( c * conn ) cmdUIDExpunge ( tag , cmd string , p * parser ) {
2023-07-24 22:21:05 +03:00
// Command: ../rfc/9051:4775 ../rfc/4315:75 ../rfc/7162:1873
2023-01-30 16:27:06 +03:00
// Request syntax: ../rfc/9051:7125 ../rfc/9051:7129 ../rfc/4315:298
p . xspace ( )
uidSet := p . xnumSet ( )
p . xempty ( )
if c . readonly {
xuserErrorf ( "mailbox open in read-only mode" )
}
c . cmdxExpunge ( tag , cmd , & uidSet )
}
// Permanently delete messages for the currently selected/active mailbox. If uidset
// is not nil, only those UIDs are removed.
// State: Selected
func ( c * conn ) cmdxExpunge ( tag , cmd string , uidSet * numSet ) {
// Command: ../rfc/9051:3687 ../rfc/3501:2695
2023-07-24 22:21:05 +03:00
remove , highestModSeq := c . xexpunge ( uidSet , false )
2023-01-30 16:27:06 +03:00
defer func ( ) {
for _ , m := range remove {
p := c . account . MessagePath ( m . ID )
err := os . Remove ( p )
c . xsanity ( err , "removing message file for expunge" )
}
} ( )
// Response syntax: ../rfc/9051:6742 ../rfc/3501:4864
2023-07-24 22:21:05 +03:00
var vanishedUIDs numSet
qresync := c . enabled [ capQresync ]
2023-01-30 16:27:06 +03:00
for _ , m := range remove {
seq := c . xsequence ( m . UID )
c . sequenceRemove ( seq , m . UID )
2023-07-24 22:21:05 +03:00
if qresync {
vanishedUIDs . append ( uint32 ( m . UID ) )
} else {
c . bwritelinef ( "* %d EXPUNGE" , seq )
}
}
if ! vanishedUIDs . empty ( ) {
// VANISHED without EARLIER. ../rfc/7162:2004
for _ , s := range vanishedUIDs . Strings ( 4 * 1024 - 32 ) {
c . bwritelinef ( "* VANISHED %s" , s )
}
2023-01-30 16:27:06 +03:00
}
2023-07-24 22:21:05 +03:00
if c . enabled [ capCondstore ] {
c . writeresultf ( "%s OK [HIGHESTMODSEQ %d] expunged" , tag , highestModSeq . Client ( ) )
} else {
c . ok ( tag , cmd )
}
2023-01-30 16:27:06 +03:00
}
// State: Selected
func ( c * conn ) cmdSearch ( tag , cmd string , p * parser ) {
c . cmdxSearch ( false , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdUIDSearch ( tag , cmd string , p * parser ) {
c . cmdxSearch ( true , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdFetch ( tag , cmd string , p * parser ) {
c . cmdxFetch ( false , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdUIDFetch ( tag , cmd string , p * parser ) {
c . cmdxFetch ( true , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdStore ( tag , cmd string , p * parser ) {
c . cmdxStore ( false , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdUIDStore ( tag , cmd string , p * parser ) {
c . cmdxStore ( true , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdCopy ( tag , cmd string , p * parser ) {
c . cmdxCopy ( false , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdUIDCopy ( tag , cmd string , p * parser ) {
c . cmdxCopy ( true , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdMove ( tag , cmd string , p * parser ) {
c . cmdxMove ( false , tag , cmd , p )
}
// State: Selected
func ( c * conn ) cmdUIDMove ( tag , cmd string , p * parser ) {
c . cmdxMove ( true , tag , cmd , p )
}
func ( c * conn ) gatherCopyMoveUIDs ( isUID bool , nums numSet ) ( [ ] store . UID , [ ] any ) {
// Gather uids, then sort so we can return a consistently simple and hard to
// misinterpret COPYUID/MOVEUID response. It seems safer to have UIDs in ascending
// order, because requested uid set of 12:10 is equal to 10:12, so if we would just
// echo whatever the client sends us without reordering, the client can reorder our
// response and interpret it differently than we intended.
// ../rfc/9051:5072
uids := c . xnumSetUIDs ( isUID , nums )
sort . Slice ( uids , func ( i , j int ) bool {
return uids [ i ] < uids [ j ]
} )
uidargs := make ( [ ] any , len ( uids ) )
for i , uid := range uids {
uidargs [ i ] = uid
}
return uids , uidargs
}
// Copy copies messages from the currently selected/active mailbox to another named
// mailbox.
//
// State: Selected
func ( c * conn ) cmdxCopy ( isUID bool , tag , cmd string , p * parser ) {
// Command: ../rfc/9051:4602 ../rfc/3501:3288
// Request syntax: ../rfc/9051:6482 ../rfc/3501:4685
p . xspace ( )
nums := p . xnumSet ( )
p . xspace ( )
name := p . xmailbox ( )
p . xempty ( )
name = xcheckmailboxname ( name , true )
uids , uidargs := c . gatherCopyMoveUIDs ( isUID , nums )
// Files that were created during the copy. Remove them if the operation fails.
var createdIDs [ ] int64
defer func ( ) {
x := recover ( )
if x == nil {
return
}
for _ , id := range createdIDs {
p := c . account . MessagePath ( id )
err := os . Remove ( p )
c . xsanity ( err , "cleaning up created file" )
}
panic ( x )
} ( )
var mbDst store . Mailbox
var origUIDs , newUIDs [ ] store . UID
var flags [ ] store . Flags
2023-06-24 01:24:43 +03:00
var keywords [ ] [ ] string
2023-07-24 22:21:05 +03:00
var modseq store . ModSeq // For messages in new mailbox, assigned when first message is copied.
2023-01-30 16:27:06 +03:00
c . account . WithWLock ( func ( ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mbKwChanged bool
2023-01-30 16:27:06 +03:00
c . xdbwrite ( func ( tx * bstore . Tx ) {
mbSrc := c . xmailboxID ( tx , c . mailboxID ) // Validate.
mbDst = c . xmailbox ( tx , name , "TRYCREATE" )
if mbDst . ID == mbSrc . ID {
xuserErrorf ( "cannot copy to currently selected mailbox" )
}
if len ( uidargs ) == 0 {
xuserErrorf ( "no matching messages to copy" )
}
2023-07-24 22:21:05 +03:00
var err error
modseq , err = c . account . NextModSeq ( tx )
xcheckf ( err , "assigning next modseq" )
2023-01-30 16:27:06 +03:00
// Reserve the uids in the destination mailbox.
uidFirst := mbDst . UIDNext
mbDst . UIDNext += store . UID ( len ( uidargs ) )
// Fetch messages from database.
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : c . mailboxID } )
q . FilterEqual ( "UID" , uidargs ... )
2023-07-24 22:21:05 +03:00
q . FilterEqual ( "Expunged" , false )
2023-01-30 16:27:06 +03:00
xmsgs , err := q . List ( )
xcheckf ( err , "fetching messages" )
if len ( xmsgs ) != len ( uidargs ) {
xserverErrorf ( "uid and message mismatch" )
}
msgs := map [ store . UID ] store . Message { }
for _ , m := range xmsgs {
msgs [ m . UID ] = m
}
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
nmsgs := make ( [ ] store . Message , len ( xmsgs ) )
conf , _ := c . account . Conf ( )
2023-01-30 16:27:06 +03:00
2023-07-24 09:49:19 +03:00
mbKeywords := map [ string ] struct { } { }
2023-01-30 16:27:06 +03:00
// Insert new messages into database.
var origMsgIDs , newMsgIDs [ ] int64
for i , uid := range uids {
m , ok := msgs [ uid ]
if ! ok {
xuserErrorf ( "messages changed, could not fetch requested uid" )
}
origID := m . ID
origMsgIDs = append ( origMsgIDs , origID )
m . ID = 0
m . UID = uidFirst + store . UID ( i )
2023-07-24 22:21:05 +03:00
m . CreateSeq = modseq
m . ModSeq = modseq
2023-01-30 16:27:06 +03:00
m . MailboxID = mbDst . ID
2023-08-09 17:47:29 +03:00
if m . IsReject && m . MailboxDestinedID != 0 {
2023-03-03 15:19:27 +03:00
// Incorrectly delivered to Rejects mailbox. Adjust MailboxOrigID so this message
// is used for reputation calculation during future deliveries.
m . MailboxOrigID = m . MailboxDestinedID
2023-08-09 17:47:29 +03:00
m . IsReject = false
2023-03-03 15:19:27 +03:00
}
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
m . TrainedJunk = nil
2023-09-21 16:19:11 +03:00
m . JunkFlagsForMailbox ( mbDst , conf )
2023-01-30 16:27:06 +03:00
err := tx . Insert ( & m )
xcheckf ( err , "inserting message" )
msgs [ uid ] = m
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
nmsgs [ i ] = m
2023-01-30 16:27:06 +03:00
origUIDs = append ( origUIDs , uid )
newUIDs = append ( newUIDs , m . UID )
newMsgIDs = append ( newMsgIDs , m . ID )
flags = append ( flags , m . Flags )
2023-06-24 01:24:43 +03:00
keywords = append ( keywords , m . Keywords )
2023-07-24 09:49:19 +03:00
for _ , kw := range m . Keywords {
mbKeywords [ kw ] = struct { } { }
}
2023-01-30 16:27:06 +03:00
qmr := bstore . QueryTx [ store . Recipient ] ( tx )
qmr . FilterNonzero ( store . Recipient { MessageID : origID } )
mrs , err := qmr . List ( )
xcheckf ( err , "listing message recipients" )
for _ , mr := range mrs {
mr . ID = 0
mr . MessageID = m . ID
err := tx . Insert ( & mr )
xcheckf ( err , "inserting message recipient" )
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mbDst . Add ( m . MailboxCounts ( ) )
2023-07-24 09:49:19 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mbDst . Keywords , mbKwChanged = store . MergeKeywords ( mbDst . Keywords , maps . Keys ( mbKeywords ) )
2023-07-24 09:49:19 +03:00
err = tx . Update ( & mbDst )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xcheckf ( err , "updating destination mailbox for uids, keywords and counts" )
2023-07-24 09:49:19 +03:00
2023-01-30 16:27:06 +03:00
// Copy message files to new message ID's.
2023-07-23 13:15:29 +03:00
syncDirs := map [ string ] struct { } { }
2023-01-30 16:27:06 +03:00
for i := range origMsgIDs {
src := c . account . MessagePath ( origMsgIDs [ i ] )
dst := c . account . MessagePath ( newMsgIDs [ i ] )
2023-07-23 13:15:29 +03:00
dstdir := filepath . Dir ( dst )
if _ , ok := syncDirs [ dstdir ] ; ! ok {
os . MkdirAll ( dstdir , 0770 )
syncDirs [ dstdir ] = struct { } { }
}
err := moxio . LinkOrCopy ( c . log , dst , src , nil , true )
2023-01-30 16:27:06 +03:00
xcheckf ( err , "link or copy file %q to %q" , src , dst )
createdIDs = append ( createdIDs , newMsgIDs [ i ] )
}
2023-07-23 13:15:29 +03:00
for dir := range syncDirs {
2023-12-05 15:35:58 +03:00
err := moxio . SyncDir ( c . log , dir )
2023-07-23 13:15:29 +03:00
xcheckf ( err , "sync directory" )
}
2023-05-22 15:40:36 +03:00
err = c . account . RetrainMessages ( context . TODO ( ) , c . log , tx , nmsgs , false )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
xcheckf ( err , "train copied messages" )
2023-01-30 16:27:06 +03:00
} )
// Broadcast changes to other connections.
if len ( newUIDs ) > 0 {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes := make ( [ ] store . Change , 0 , len ( newUIDs ) + 2 )
2023-01-30 16:27:06 +03:00
for i , uid := range newUIDs {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = append ( changes , store . ChangeAddUID { MailboxID : mbDst . ID , UID : uid , ModSeq : modseq , Flags : flags [ i ] , Keywords : keywords [ i ] } )
}
changes = append ( changes , mbDst . ChangeCounts ( ) )
if mbKwChanged {
changes = append ( changes , mbDst . ChangeKeywords ( ) )
2023-01-30 16:27:06 +03:00
}
c . broadcast ( changes )
}
} )
// All good, prevent defer above from cleaning up copied files.
createdIDs = nil
// ../rfc/9051:6881 ../rfc/4315:183
c . writeresultf ( "%s OK [COPYUID %d %s %s] copied" , tag , mbDst . UIDValidity , compactUIDSet ( origUIDs ) . String ( ) , compactUIDSet ( newUIDs ) . String ( ) )
}
// Move moves messages from the currently selected/active mailbox to a named mailbox.
//
// State: Selected
func ( c * conn ) cmdxMove ( isUID bool , tag , cmd string , p * parser ) {
2023-07-24 22:21:05 +03:00
// Command: ../rfc/9051:4650 ../rfc/6851:119 ../rfc/6851:265
2023-01-30 16:27:06 +03:00
2023-07-24 22:21:05 +03:00
// Request syntax: ../rfc/6851:320
2023-01-30 16:27:06 +03:00
p . xspace ( )
nums := p . xnumSet ( )
p . xspace ( )
name := p . xmailbox ( )
p . xempty ( )
name = xcheckmailboxname ( name , true )
if c . readonly {
xuserErrorf ( "mailbox open in read-only mode" )
}
uids , uidargs := c . gatherCopyMoveUIDs ( isUID , nums )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mbSrc , mbDst store . Mailbox
2023-01-30 16:27:06 +03:00
var changes [ ] store . Change
var newUIDs [ ] store . UID
2023-07-24 22:21:05 +03:00
var modseq store . ModSeq
2023-01-30 16:27:06 +03:00
c . account . WithWLock ( func ( ) {
c . xdbwrite ( func ( tx * bstore . Tx ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mbSrc = c . xmailboxID ( tx , c . mailboxID ) // Validate.
2023-01-30 16:27:06 +03:00
mbDst = c . xmailbox ( tx , name , "TRYCREATE" )
if mbDst . ID == c . mailboxID {
xuserErrorf ( "cannot move to currently selected mailbox" )
}
if len ( uidargs ) == 0 {
xuserErrorf ( "no matching messages to move" )
}
// Reserve the uids in the destination mailbox.
uidFirst := mbDst . UIDNext
uidnext := uidFirst
mbDst . UIDNext += store . UID ( len ( uids ) )
2023-07-24 22:21:05 +03:00
// Assign a new modseq, for the new records and for the expunged records.
var err error
modseq , err = c . account . NextModSeq ( tx )
xcheckf ( err , "assigning next modseq" )
// Update existing record with new UID and MailboxID in database for messages. We
// add a new but expunged record again in the original/source mailbox, for qresync.
// Keeping the original ID for the live message means we don't have to move the
// on-disk message contents file.
2023-01-30 16:27:06 +03:00
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : c . mailboxID } )
q . FilterEqual ( "UID" , uidargs ... )
2023-07-24 22:21:05 +03:00
q . FilterEqual ( "Expunged" , false )
2023-01-30 16:27:06 +03:00
q . SortAsc ( "UID" )
msgs , err := q . List ( )
xcheckf ( err , "listing messages to move" )
if len ( msgs ) != len ( uidargs ) {
xserverErrorf ( "uid and message mismatch" )
}
2023-07-24 09:49:19 +03:00
keywords := map [ string ] struct { } { }
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
conf , _ := c . account . Conf ( )
2023-01-30 16:27:06 +03:00
for i := range msgs {
m := & msgs [ i ]
if m . UID != uids [ i ] {
xserverErrorf ( "internal error: got uid %d, expected %d, for index %d" , m . UID , uids [ i ] , i )
}
2023-07-24 22:21:05 +03:00
2023-09-22 16:43:25 +03:00
mbSrc . Sub ( m . MailboxCounts ( ) )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
2023-07-24 22:21:05 +03:00
// Copy of message record that we'll insert when UID is freed up.
om := * m
om . PrepareExpunge ( )
om . ID = 0 // Assign new ID.
om . ModSeq = modseq
2023-01-30 16:27:06 +03:00
m . MailboxID = mbDst . ID
2023-08-09 17:47:29 +03:00
if m . IsReject && m . MailboxDestinedID != 0 {
2023-03-03 15:19:27 +03:00
// Incorrectly delivered to Rejects mailbox. Adjust MailboxOrigID so this message
// is used for reputation calculation during future deliveries.
m . MailboxOrigID = m . MailboxDestinedID
2023-08-09 17:47:29 +03:00
m . IsReject = false
2023-09-22 16:43:25 +03:00
m . Seen = false
2023-03-03 15:19:27 +03:00
}
2023-09-22 16:43:25 +03:00
mbDst . Add ( m . MailboxCounts ( ) )
2023-01-30 16:27:06 +03:00
m . UID = uidnext
2023-07-24 22:21:05 +03:00
m . ModSeq = modseq
2023-09-21 16:19:11 +03:00
m . JunkFlagsForMailbox ( mbDst , conf )
2023-01-30 16:27:06 +03:00
uidnext ++
err := tx . Update ( m )
xcheckf ( err , "updating moved message in database" )
2023-07-24 09:49:19 +03:00
2023-07-24 22:21:05 +03:00
// Now that UID is unused, we can insert the old record again.
err = tx . Insert ( & om )
xcheckf ( err , "inserting record for expunge after moving message" )
2023-07-24 09:49:19 +03:00
for _ , kw := range m . Keywords {
keywords [ kw ] = struct { } { }
}
}
// Ensure destination mailbox has keywords of the moved messages.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mbKwChanged bool
mbDst . Keywords , mbKwChanged = store . MergeKeywords ( mbDst . Keywords , maps . Keys ( keywords ) )
if mbKwChanged {
changes = append ( changes , mbDst . ChangeKeywords ( ) )
2023-01-30 16:27:06 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
err = tx . Update ( & mbSrc )
xcheckf ( err , "updating source mailbox counts" )
2023-07-24 09:49:19 +03:00
err = tx . Update ( & mbDst )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xcheckf ( err , "updating destination mailbox for uids, keywords and counts" )
2023-07-24 09:49:19 +03:00
2023-05-22 15:40:36 +03:00
err = c . account . RetrainMessages ( context . TODO ( ) , c . log , tx , msgs , false )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
xcheckf ( err , "retraining messages after move" )
2023-01-30 16:27:06 +03:00
// Prepare broadcast changes to other connections.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = make ( [ ] store . Change , 0 , 1 + len ( msgs ) + 2 )
2023-07-24 22:21:05 +03:00
changes = append ( changes , store . ChangeRemoveUIDs { MailboxID : c . mailboxID , UIDs : uids , ModSeq : modseq } )
2023-01-30 16:27:06 +03:00
for _ , m := range msgs {
newUIDs = append ( newUIDs , m . UID )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = append ( changes , m . ChangeAddUID ( ) )
2023-01-30 16:27:06 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = append ( changes , mbSrc . ChangeCounts ( ) , mbDst . ChangeCounts ( ) )
2023-01-30 16:27:06 +03:00
} )
c . broadcast ( changes )
} )
// ../rfc/9051:4708 ../rfc/6851:254
// ../rfc/9051:4713
c . bwritelinef ( "* OK [COPYUID %d %s %s] moved" , mbDst . UIDValidity , compactUIDSet ( uids ) . String ( ) , compactUIDSet ( newUIDs ) . String ( ) )
2023-07-24 22:21:05 +03:00
qresync := c . enabled [ capQresync ]
var vanishedUIDs numSet
2023-01-30 16:27:06 +03:00
for i := 0 ; i < len ( uids ) ; i ++ {
seq := c . xsequence ( uids [ i ] )
c . sequenceRemove ( seq , uids [ i ] )
2023-07-24 22:21:05 +03:00
if qresync {
vanishedUIDs . append ( uint32 ( uids [ i ] ) )
} else {
c . bwritelinef ( "* %d EXPUNGE" , seq )
}
}
if ! vanishedUIDs . empty ( ) {
// VANISHED without EARLIER. ../rfc/7162:2004
for _ , s := range vanishedUIDs . Strings ( 4 * 1024 - 32 ) {
c . bwritelinef ( "* VANISHED %s" , s )
}
2023-01-30 16:27:06 +03:00
}
2023-07-24 22:21:05 +03:00
if c . enabled [ capQresync ] {
// ../rfc/9051:6744 ../rfc/7162:1334
c . writeresultf ( "%s OK [HIGHESTMODSEQ %d] move" , tag , modseq . Client ( ) )
} else {
c . ok ( tag , cmd )
}
2023-01-30 16:27:06 +03:00
}
// Store sets a full set of flags, or adds/removes specific flags.
//
// State: Selected
func ( c * conn ) cmdxStore ( isUID bool , tag , cmd string , p * parser ) {
// Command: ../rfc/9051:4543 ../rfc/3501:3214
2023-07-24 22:21:05 +03:00
// Request syntax: ../rfc/9051:7076 ../rfc/3501:5052 ../rfc/4466:691 ../rfc/7162:2471
2023-01-30 16:27:06 +03:00
p . xspace ( )
nums := p . xnumSet ( )
p . xspace ( )
2023-07-24 22:21:05 +03:00
var unchangedSince * int64
if p . take ( "(" ) {
// ../rfc/7162:2471
p . xtake ( "UNCHANGEDSINCE" )
p . xspace ( )
v := p . xnumber64 ( )
unchangedSince = & v
p . xtake ( ")" )
p . xspace ( )
// UNCHANGEDSINCE is a CONDSTORE-enabling parameter ../rfc/7162:382
c . xensureCondstore ( nil )
}
2023-01-30 16:27:06 +03:00
var plus , minus bool
if p . take ( "+" ) {
plus = true
} else if p . take ( "-" ) {
minus = true
}
p . xtake ( "FLAGS" )
silent := p . take ( ".SILENT" )
p . xspace ( )
var flagstrs [ ] string
if p . hasPrefix ( "(" ) {
flagstrs = p . xflagList ( )
} else {
flagstrs = append ( flagstrs , p . xflag ( ) )
for p . space ( ) {
flagstrs = append ( flagstrs , p . xflag ( ) )
}
}
p . xempty ( )
if c . readonly {
xuserErrorf ( "mailbox open in read-only mode" )
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
flags , keywords , err := store . ParseFlagsKeywords ( flagstrs )
if err != nil {
xuserErrorf ( "parsing flags: %v" , err )
}
2023-06-24 01:24:43 +03:00
var mask store . Flags
2023-01-30 16:27:06 +03:00
if plus {
2023-06-24 01:24:43 +03:00
mask , flags = flags , store . FlagsAll
2023-01-30 16:27:06 +03:00
} else if minus {
2023-06-24 01:24:43 +03:00
mask , flags = flags , store . Flags { }
2023-01-30 16:27:06 +03:00
} else {
mask = store . FlagsAll
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mb , origmb store . Mailbox
2023-01-30 16:27:06 +03:00
var updated [ ] store . Message
2023-07-24 22:21:05 +03:00
var changed [ ] store . Message // ModSeq more recent than unchangedSince, will be in MODIFIED response code, and we will send untagged fetch responses so client is up to date.
var modseq store . ModSeq // Assigned when needed.
modified := map [ int64 ] bool { }
2023-01-30 16:27:06 +03:00
c . account . WithWLock ( func ( ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var mbKwChanged bool
var changes [ ] store . Change
2023-01-30 16:27:06 +03:00
c . xdbwrite ( func ( tx * bstore . Tx ) {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mb = c . xmailboxID ( tx , c . mailboxID ) // Validate.
origmb = mb
2023-01-30 16:27:06 +03:00
uidargs := c . xnumSetCondition ( isUID , nums )
if len ( uidargs ) == 0 {
return
}
2023-06-24 01:24:43 +03:00
// Ensure keywords are in mailbox.
if ! minus {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mb . Keywords , mbKwChanged = store . MergeKeywords ( mb . Keywords , keywords )
if mbKwChanged {
2023-06-24 01:24:43 +03:00
err := tx . Update ( & mb )
xcheckf ( err , "updating mailbox with keywords" )
}
}
2023-01-30 16:27:06 +03:00
q := bstore . QueryTx [ store . Message ] ( tx )
q . FilterNonzero ( store . Message { MailboxID : c . mailboxID } )
q . FilterEqual ( "UID" , uidargs ... )
2023-07-24 22:21:05 +03:00
q . FilterEqual ( "Expunged" , false )
2023-06-24 01:24:43 +03:00
err := q . ForEach ( func ( m store . Message ) error {
2023-07-24 22:21:05 +03:00
// Client may specify a message multiple times, but we only process it once. ../rfc/7162:823
if modified [ m . ID ] {
return nil
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mc := m . MailboxCounts ( )
2023-07-24 22:21:05 +03:00
origFlags := m . Flags
2023-06-24 01:24:43 +03:00
m . Flags = m . Flags . Set ( mask , flags )
2023-07-24 22:21:05 +03:00
oldKeywords := append ( [ ] string { } , m . Keywords ... )
2023-06-24 01:24:43 +03:00
if minus {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
m . Keywords , _ = store . RemoveKeywords ( m . Keywords , keywords )
2023-06-24 01:24:43 +03:00
} else if plus {
m . Keywords , _ = store . MergeKeywords ( m . Keywords , keywords )
} else {
m . Keywords = keywords
}
2023-07-24 22:21:05 +03:00
keywordsChanged := func ( ) bool {
sort . Strings ( oldKeywords )
n := append ( [ ] string { } , m . Keywords ... )
sort . Strings ( n )
return ! slices . Equal ( oldKeywords , n )
}
// If the message has a more recent modseq than the check requires, we won't modify
// it and report in the final command response.
// ../rfc/7162:555
//
// unchangedSince 0 always fails the check, we don't turn it into 1 like with our
// internal modseqs. RFC implies that is not required for non-system flags, but we
// don't have per-flag modseq and this seems reasonable. ../rfc/7162:640
if unchangedSince != nil && m . ModSeq . Client ( ) > * unchangedSince {
changed = append ( changed , m )
return nil
}
// Note: we don't perform the optimization described in ../rfc/7162:1258
// It requires that we keep track of the flags we think the client knows (but only
// on this connection). We don't track that. It also isn't clear why this is
// allowed because it is skipping the condstore conditional check, and the new
// combination of flags could be unintended.
// We do not assign a new modseq if nothing actually changed. ../rfc/7162:1246 ../rfc/7162:312
if origFlags == m . Flags && ! keywordsChanged ( ) {
// Note: since we didn't update the modseq, we are not adding m.ID to "modified",
// it would skip the modseq check above. We still add m to list of updated, so we
// send an untagged fetch response. But we don't broadcast it.
updated = append ( updated , m )
return nil
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
mb . Sub ( mc )
mb . Add ( m . MailboxCounts ( ) )
2023-07-24 22:21:05 +03:00
// Assign new modseq for first actual change.
if modseq == 0 {
var err error
modseq , err = c . account . NextModSeq ( tx )
xcheckf ( err , "next modseq" )
}
m . ModSeq = modseq
modified [ m . ID ] = true
2023-06-24 01:24:43 +03:00
updated = append ( updated , m )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
changes = append ( changes , m . ChangeFlags ( origFlags ) )
2023-06-24 01:24:43 +03:00
return tx . Update ( & m )
} )
xcheckf ( err , "storing flags in messages" )
2023-01-30 16:27:06 +03:00
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
if mb . MailboxCounts != origmb . MailboxCounts {
err := tx . Update ( & mb )
xcheckf ( err , "updating mailbox counts" )
changes = append ( changes , mb . ChangeCounts ( ) )
}
if mbKwChanged {
changes = append ( changes , mb . ChangeKeywords ( ) )
}
2023-06-24 01:24:43 +03:00
err = c . account . RetrainMessages ( context . TODO ( ) , c . log , tx , updated , false )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
xcheckf ( err , "training messages" )
} )
2023-01-30 16:27:06 +03:00
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
c . broadcast ( changes )
2023-01-30 16:27:06 +03:00
} )
2023-07-24 22:21:05 +03:00
// In the RFC, the section about STORE/UID STORE says we must return MODSEQ when
// UNCHANGEDSINCE was specified. It does not specify it in case UNCHANGEDSINCE
// isn't specified. For that case it does say MODSEQ is needed in unsolicited
// untagged fetch responses. Implying that solicited untagged fetch responses
// should not include MODSEQ (why else mention unsolicited explicitly?). But, in
// the introduction to CONDSTORE it does explicitly specify MODSEQ should be
// included in untagged fetch responses at all times with CONDSTORE-enabled
// connections. It would have been better if the command behaviour was specified in
// the command section, not the introduction to the extension.
// ../rfc/7162:388 ../rfc/7162:852
// ../rfc/7162:549
if ! silent || c . enabled [ capCondstore ] {
for _ , m := range updated {
var flags string
if ! silent {
flags = fmt . Sprintf ( " FLAGS %s" , flaglist ( m . Flags , m . Keywords ) . pack ( c ) )
}
var modseqStr string
if c . enabled [ capCondstore ] {
modseqStr = fmt . Sprintf ( " MODSEQ (%d)" , m . ModSeq . Client ( ) )
}
// ../rfc/9051:6749 ../rfc/3501:4869 ../rfc/7162:2490
c . bwritelinef ( "* %d FETCH (UID %d%s%s)" , c . xsequence ( m . UID ) , m . UID , flags , modseqStr )
}
}
// We don't explicitly send flags for failed updated with silent set. The regular
// notification will get the flags to the client.
// ../rfc/7162:630 ../rfc/3501:3233
if len ( changed ) == 0 {
c . ok ( tag , cmd )
return
}
// Write unsolicited untagged fetch responses for messages that didn't pass the
// unchangedsince check. ../rfc/7162:679
// Also gather UIDs or sequences for the MODIFIED response below. ../rfc/7162:571
var mnums [ ] store . UID
for _ , m := range changed {
c . bwritelinef ( "* %d FETCH (UID %d FLAGS %s MODSEQ (%d))" , c . xsequence ( m . UID ) , m . UID , flaglist ( m . Flags , m . Keywords ) . pack ( c ) , m . ModSeq . Client ( ) )
if isUID {
mnums = append ( mnums , m . UID )
} else {
mnums = append ( mnums , store . UID ( c . xsequence ( m . UID ) ) )
2023-01-30 16:27:06 +03:00
}
}
2023-07-24 22:21:05 +03:00
sort . Slice ( mnums , func ( i , j int ) bool {
return mnums [ i ] < mnums [ j ]
} )
set := compactUIDSet ( mnums )
// ../rfc/7162:2506
c . writeresultf ( "%s OK [MODIFIED %s] conditional store did not modify all" , tag , set . String ( ) )
2023-01-30 16:27:06 +03:00
}