Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go-imap v2 Backend interface work #374

Closed
wants to merge 54 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b491b6f
Write NIL for empty ENVELOPE fields
foxcpp Aug 2, 2020
c1eb47c
backendutil: Improve Match function
foxcpp Aug 2, 2020
a8110ee
remove "should not be called directly" comments and replaced them wit…
patrick246 Aug 10, 2020
54d331f
imap: lower some fields + content disposition keys
sqwishy Aug 22, 2020
9313ce6
utf7: fix package doc comment
proletarius101 Sep 10, 2020
88f167c
return empty reader instead of nil when BODY is found but server retu…
Sep 14, 2020
5a03a09
add support for empty groups in address lists
Sep 22, 2020
baf4272
utf7: reset ascii boolean at EOF while destination is short
horejsek Sep 24, 2020
f9d60f8
client: Drop syscall.ECONNRESET hack from Client.readOnce
foxcpp Oct 22, 2020
8011154
Add section about "built-in" extensions to README
foxcpp Mar 4, 2020
3e25bca
Merge support for SPECIAL-USE extension
foxcpp Mar 4, 2020
61057f7
Add constant with builtin extensions for tests
foxcpp Jul 27, 2020
7b7dd37
Merge support for CHILDREN extension
foxcpp Mar 4, 2020
3b0941c
Add the IMPORTANT extension
emersion Nov 1, 2020
422193a
readme: remove pointless `go get` command
emersion Nov 1, 2020
de4b254
Message chan not closed
Nov 3, 2020
c11754c
Fix small typo in example's doc comment
atc0005 Nov 18, 2020
f359a13
readme: switch GoDoc links to godocs.io
emersion Jan 15, 2021
dda53fd
utf7: reset ascii boolean after decode while destination is short.
daichitakahashi Feb 12, 2021
b814bef
Fix #421: Correct docstring on client.Append
Mar 27, 2021
eb574be
ci: drop codecov
emersion Apr 30, 2021
3f3821d
readme: remove codecov badge
emersion Apr 30, 2021
cfc6775
Add issue template
emersion Apr 30, 2021
4cb5e43
github: add issue template front-matter
emersion Apr 30, 2021
5518512
github: add about field to issue template
emersion Apr 30, 2021
6159a01
Update dependencies
Necoro May 11, 2021
795b28a
Ignore unilateral updates from the server in FETCH
Necoro May 18, 2021
0e5bf8b
github: switch to Libera Chat for questions
emersion May 20, 2021
8411770
Merge support for the UNSELECT extension
foxcpp Mar 4, 2020
c7dce92
Merge support for APPENDLIMIT extension
foxcpp Mar 4, 2020
08b95f8
Merge client support for ENABLE extension
emersion Sep 7, 2021
75ea6d9
Add missing ENABLE command and response
emersion Sep 7, 2021
231c001
Merge support for MOVE extension
emersion Sep 7, 2021
ac3f8e1
Merge support for IDLE extension
emersion Sep 7, 2021
c465e67
Ignore IDLE in Server.Enable
emersion Sep 7, 2021
e23e10a
Add missing IDLE command and response
emersion Sep 7, 2021
cde6bfe
readme: mark IDLE as built-in
emersion Sep 7, 2021
da05cbc
readme: sort built-in ext list
emersion Sep 7, 2021
f6112fa
Update dependent packages: go-message@0.15.0, x/text@0.3.7.
iredmail Oct 6, 2021
84d3022
github: redirect to Libera webchat for questions
emersion Oct 6, 2021
16ffd07
Add link to notmuch backend
stbenjam Oct 18, 2021
09ddf18
server: Add Mailbox.Close, make ListMailboxes return MailboxInfo
foxcpp May 11, 2020
b8e85d6
v2: Move backend methods for commands not related to current mailbox …
foxcpp Jul 26, 2020
9ed2179
v2: Merge GetMailbox and initial Status call
foxcpp Jul 26, 2020
9e39c85
v2: Delegate update handling to backend
foxcpp Jul 26, 2020
ea1ede8
v2: Pass read-only mailbox status to the backend
foxcpp Jul 26, 2020
e798252
v2: Remove Mailbox.Check, make Mailbox.Poll mandatory
foxcpp Jul 27, 2020
50db3b0
server: Call Poll after User.CreateMessage
foxcpp Jul 27, 2020
6af0c3d
server: Add missing Close calls
foxcpp Jul 30, 2020
e79b0ef
server: Add TRYCREATE response for COPY
foxcpp Aug 2, 2020
4e70bda
pass currently selected mailbox to CreateMessage, to allow for correc…
patrick246 Oct 1, 2020
1e767d4
v2: Introduce Mailbox.Idle
foxcpp Jan 5, 2022
3ba9e4f
Add IDLE to capabilities list
foxcpp Jan 19, 2022
df940c3
Merge branch 'master' into backend-v2
foxcpp Jun 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions .build.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
image: alpine/edge
packages:
- go
# Required by codecov
- bash
- findutils
sources:
- https://github.com/emersion/go-imap
artifacts:
- coverage.html
tasks:
- build: |
cd go-imap
go build -v ./...
go build -race -v ./...
- test: |
cd go-imap
go test -coverprofile=coverage.txt -covermode=atomic ./...
- upload-coverage: |
- coverage: |
cd go-imap
export CODECOV_TOKEN=8c0f7014-fcfa-4ed9-8972-542eb5958fb3
curl -s https://codecov.io/bash | bash
go tool cover -html=coverage.txt -o ~/coverage.html
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: "https://web.libera.chat/gamja/#emersion"
about: "Please ask questions in #emersion on Libera Chat"
12 changes: 12 additions & 0 deletions .github/ISSUE_TEMPLATE/issue_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: Bug report or feature request
about: Report a bug or request a new feature
---

<!--

Please read the following before submitting a new issue:

Do NOT create GitHub issues if you have a question about go-imap or about the IMAP protocol in general. Ask questions on IRC in #emersion on Libera Chat.

-->
38 changes: 23 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# go-imap

[![GoDoc](https://godoc.org/github.com/emersion/go-imap?status.svg)](https://godoc.org/github.com/emersion/go-imap)
[![godocs.io](https://godocs.io/github.com/emersion/go-imap?status.svg)](https://godocs.io/github.com/emersion/go-imap)
[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-imap/commits.svg)](https://builds.sr.ht/~emersion/go-imap/commits?)
[![Codecov](https://codecov.io/gh/emersion/go-imap/branch/master/graph/badge.svg)](https://codecov.io/gh/emersion/go-imap)

An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
can be used to build a client and/or a server.

```shell
go get github.com/emersion/go-imap/...
```

## Usage

### Client [![GoDoc](https://godoc.org/github.com/emersion/go-imap/client?status.svg)](https://godoc.org/github.com/emersion/go-imap/client)
### Client [![godocs.io](https://godocs.io/github.com/emersion/go-imap/client?status.svg)](https://godocs.io/github.com/emersion/go-imap/client)

```go
package main
Expand Down Expand Up @@ -71,7 +66,7 @@ func main() {
from := uint32(1)
to := mbox.Messages
if mbox.Messages > 3 {
// We're using unsigned integers here, only substract if the result is > 0
// We're using unsigned integers here, only subtract if the result is > 0
from = mbox.Messages - 3
}
seqset := new(imap.SeqSet)
Expand All @@ -96,7 +91,7 @@ func main() {
}
```

### Server [![GoDoc](https://godoc.org/github.com/emersion/go-imap/server?status.svg)](https://godoc.org/github.com/emersion/go-imap/server)
### Server [![godocs.io](https://godocs.io/github.com/emersion/go-imap/server?status.svg)](https://godocs.io/github.com/emersion/go-imap/server)

```go
package main
Expand Down Expand Up @@ -128,6 +123,24 @@ func main() {

You can now use `telnet localhost 1143` to manually connect to the server.

## Extensions

Support for several IMAP extensions is included in go-imap itself. This
includes:

* [APPENDLIMIT](https://tools.ietf.org/html/rfc7889)
* [CHILDREN](https://tools.ietf.org/html/rfc3348)
* [ENABLE](https://tools.ietf.org/html/rfc5161)
* [IDLE](https://tools.ietf.org/html/rfc2177)
* [IMPORTANT](https://tools.ietf.org/html/rfc8457)
* [LITERAL+](https://tools.ietf.org/html/rfc7888)
* [MOVE](https://tools.ietf.org/html/rfc6851)
* [SASL-IR](https://tools.ietf.org/html/rfc4959)
* [SPECIAL-USE](https://tools.ietf.org/html/rfc6154)
* [UNSELECT](https://tools.ietf.org/html/rfc3691)

Support for other extensions is provided via separate packages. See below.

## Extending go-imap

### Extensions
Expand All @@ -136,18 +149,12 @@ Commands defined in IMAP extensions are available in other packages. See [the
wiki](https://github.com/emersion/go-imap/wiki/Using-extensions#using-client-extensions)
to learn how to use them.

* [APPENDLIMIT](https://github.com/emersion/go-imap-appendlimit)
* [COMPRESS](https://github.com/emersion/go-imap-compress)
* [ENABLE](https://github.com/emersion/go-imap-enable)
* [ID](https://github.com/ProtonMail/go-imap-id)
* [IDLE](https://github.com/emersion/go-imap-idle)
* [METADATA](https://github.com/emersion/go-imap-metadata)
* [MOVE](https://github.com/emersion/go-imap-move)
* [NAMESPACE](https://github.com/foxcpp/go-imap-namespace)
* [QUOTA](https://github.com/emersion/go-imap-quota)
* [SORT and THREAD](https://github.com/emersion/go-imap-sortthread)
* [SPECIAL-USE](https://github.com/emersion/go-imap-specialuse)
* [UNSELECT](https://github.com/emersion/go-imap-unselect)
* [UIDPLUS](https://github.com/emersion/go-imap-uidplus)

### Server backends
Expand All @@ -156,6 +163,7 @@ to learn how to use them.
* [Multi](https://github.com/emersion/go-imap-multi)
* [PGP](https://github.com/emersion/go-imap-pgp)
* [Proxy](https://github.com/emersion/go-imap-proxy)
* [Notmuch](https://github.com/stbenjam/go-imap-notmuch) - Experimental gateway for [Notmuch](https://notmuchmail.org/)

### Related projects

Expand Down
29 changes: 29 additions & 0 deletions backend/appendlimit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package backend

import (
"errors"
)

// An error that should be returned by User.CreateMessage when the message size
// is too big.
var ErrTooBig = errors.New("Message size exceeding limit")

// A backend that supports retrieving per-user message size limits.
type AppendLimitBackend interface {
Backend

// Get the fixed maximum message size in octets that the backend will accept
// when creating a new message. If there is no limit, return nil.
CreateMessageLimit() *uint32
}

// A user that supports retrieving per-user message size limits.
type AppendLimitUser interface {
User

// Get the fixed maximum message size in octets that the backend will accept
// when creating a new message. If there is no limit, return nil.
//
// This overrides the global backend limit.
CreateMessageLimit() *uint32
}
39 changes: 32 additions & 7 deletions backend/backendutil/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
"github.com/emersion/go-message/textproto"
)

func matchString(s, substr string) bool {
Expand Down Expand Up @@ -41,16 +42,28 @@ type lengther interface {
Len() int
}

type countWriter struct {
N int
}

func (w *countWriter) Write(b []byte) (int, error) {
w.N += len(b)
return len(b), nil
}

func bodyLen(e *message.Entity) (int, error) {
headerSize := countWriter{}
textproto.WriteHeader(&headerSize, e.Header.Header)

if l, ok := e.Body.(lengther); ok {
return l.Len(), nil
return l.Len() + headerSize.N, nil
}

b, err := bufferBody(e)
if err != nil {
return 0, err
}
return b.Len(), nil
return b.Len() + headerSize.N, nil
}

// Match returns true if a message and its metadata matches the provided
Expand All @@ -66,12 +79,12 @@ func Match(e *message.Entity, seqNum, uid uint32, date time.Time, flags []string
if err != nil {
return false, err
}
t = t.Round(24 * time.Hour)
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)

if !c.SentBefore.IsZero() && !t.Before(c.SentBefore) {
return false, nil
}
if !c.SentSince.IsZero() && !t.After(c.SentSince) {
if !c.SentSince.IsZero() && t.Before(c.SentSince) {
return false, nil
}
}
Expand Down Expand Up @@ -104,8 +117,17 @@ func Match(e *message.Entity, seqNum, uid uint32, date time.Time, flags []string
}
}
for _, text := range c.Text {
// TODO: also match header fields
if ok, err := matchBody(e, text); err != nil || !ok {
headerMatch := false
for f := e.Header.Fields(); f.Next(); {
decoded, err := f.Text()
if err != nil {
continue
}
if strings.Contains(f.Key()+": "+decoded, text) {
headerMatch = true
}
}
if ok, err := matchBody(e, text); err != nil || !ok && !headerMatch {
return false, err
}
}
Expand Down Expand Up @@ -194,7 +216,10 @@ func matchSeqNumAndUid(seqNum uint32, uid uint32, c *imap.SearchCriteria) bool {
}

func matchDate(date time.Time, c *imap.SearchCriteria) bool {
date = date.Round(24 * time.Hour)
// We discard time zone information by setting it to UTC.
// RFC 3501 explicitly requires zone unaware date comparison.
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.UTC)

if !c.Since.IsZero() && !date.After(c.Since) {
return false
}
Expand Down
8 changes: 4 additions & 4 deletions backend/backendutil/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,9 @@ func TestMatchIssue298Regression(t *testing.T) {
t.Fatal("Expected no error while reading entity, got:", err)
}

// Search for body size > 1 ("LARGER 1"), which should match messages #2 and #3
// Search for body size > 15 ("LARGER 15"), which should match messages #2 and #3
criteria := &imap.SearchCriteria{
Larger: 1,
Larger: 15,
}
ok1, err := Match(e1, 1, 101, time.Now(), nil, criteria)
if err != nil {
Expand All @@ -404,9 +404,9 @@ func TestMatchIssue298Regression(t *testing.T) {
t.Errorf("Expected message #3 to match search criteria")
}

// Search for body size < 3 ("SMALLER 3"), which should match messages #1 and #2
// Search for body size < 17 ("SMALLER 17"), which should match messages #1 and #2
criteria = &imap.SearchCriteria{
Smaller: 3,
Smaller: 17,
}
ok1, err = Match(e1, 1, 101, time.Now(), nil, criteria)
if err != nil {
Expand Down
43 changes: 15 additions & 28 deletions backend/mailbox.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package backend

import (
"time"

"github.com/emersion/go-imap"
)

Expand All @@ -12,26 +10,17 @@ type Mailbox interface {
// Name returns this mailbox name.
Name() string

// Closes the mailbox.
Close() error

// Info returns this mailbox info.
Info() (*imap.MailboxInfo, error)

// Status returns this mailbox status. The fields Name, Flags, PermanentFlags
// and UnseenSeqNum in the returned MailboxStatus must be always populated.
// This function does not affect the state of any messages in the mailbox. See
// RFC 3501 section 6.3.10 for a list of items that can be requested.
Status(items []imap.StatusItem) (*imap.MailboxStatus, error)

// SetSubscribed adds or removes the mailbox to the server's set of "active"
// or "subscribed" mailboxes.
SetSubscribed(subscribed bool) error

// Check requests a checkpoint of the currently selected mailbox. A checkpoint
// refers to any implementation-dependent housekeeping associated with the
// mailbox (e.g., resolving the server's in-memory state of the mailbox with
// the state on its disk). A checkpoint MAY take a non-instantaneous amount of
// real time to complete. If a server implementation has no such housekeeping
// considerations, CHECK is equivalent to NOOP.
Check() error
// Poll requests any pending mailbox updates to be sent.
//
// Argument indicates whether EXPUNGE updates are permitted to be
// sent.
Poll(expunge bool) error

// ListMessages returns a list of messages. seqset must be interpreted as UIDs
// if uid is set to true and as message sequence numbers otherwise. See RFC
Expand All @@ -44,19 +33,11 @@ type Mailbox interface {
// uid is set to true, or sequence numbers otherwise.
SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error)

// CreateMessage appends a new message to this mailbox. The \Recent flag will
// be added no matter flags is empty or not. If date is nil, the current time
// will be used.
//
// If the Backend implements Updater, it must notify the client immediately
// via a mailbox update.
CreateMessage(flags []string, date time.Time, body imap.Literal) error

// UpdateMessagesFlags alters flags for the specified message(s).
//
// If the Backend implements Updater, it must notify the client immediately
// via a message update.
UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error
UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, silent bool, flags []string) error

// CopyMessages copies the specified message(s) to the end of the specified
// destination mailbox. The flags and internal date of the message(s) SHOULD
Expand All @@ -75,4 +56,10 @@ type Mailbox interface {
// If the Backend implements Updater, it must notify the client immediately
// via an expunge update.
Expunge() error

// Idle allows backend to send updates without explicit Poll calls or any other
// commands running.
// When called - it should block indefinitely and return immediately when
// done channel is written to.
Idle(done <-chan struct{})
}
Loading