carddav: update to go-webdav v0.2.0
This commit is contained in:
parent
5fb5ec9775
commit
7d3d0ee71e
|
@ -3,11 +3,13 @@ package carddav
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,9 +19,8 @@ import (
|
||||||
"golang.org/x/crypto/openpgp"
|
"golang.org/x/crypto/openpgp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey string
|
// TODO: use a HTTP error
|
||||||
|
var errNotFound = errors.New("carddav: not found")
|
||||||
const ClientContextKey = contextKey("client")
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cleartextCardProps = []string{vcard.FieldVersion, vcard.FieldProductID, "X-PM-LABEL", "X-PM-GROUP"}
|
cleartextCardProps = []string{vcard.FieldVersion, vcard.FieldProductID, "X-PM-LABEL", "X-PM-GROUP"}
|
||||||
|
@ -80,52 +81,25 @@ func formatCard(card vcard.Card, privateKey *openpgp.Entity) (*protonmail.Contac
|
||||||
return &contactImport, nil
|
return &contactImport, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type addressFileInfo struct {
|
func parseAddressObjectPath(p string) (string, error) {
|
||||||
contact *protonmail.Contact
|
dirname, filename := path.Split(p)
|
||||||
|
ext := path.Ext(filename)
|
||||||
|
if dirname != "/" || ext != ".vcf" {
|
||||||
|
return "", errNotFound
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(filename, ext), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi *addressFileInfo) Name() string {
|
func formatAddressObjectPath(id string) string {
|
||||||
return fi.contact.ID
|
return "/" + id + ".vcf"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi *addressFileInfo) Size() int64 {
|
func (b *backend) toAddressObject(contact *protonmail.Contact, req *carddav.AddressDataRequest) (*carddav.AddressObject, error) {
|
||||||
return int64(fi.contact.Size)
|
// TODO: handle req
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *addressFileInfo) Mode() os.FileMode {
|
|
||||||
return os.ModePerm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *addressFileInfo) ModTime() time.Time {
|
|
||||||
return time.Unix(fi.contact.ModifyTime, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *addressFileInfo) IsDir() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *addressFileInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type addressObject struct {
|
|
||||||
ab *addressBook
|
|
||||||
contact *protonmail.Contact
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ao *addressObject) ID() string {
|
|
||||||
return ao.contact.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ao *addressObject) Stat() (os.FileInfo, error) {
|
|
||||||
return &addressFileInfo{ao.contact}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ao *addressObject) Card() (vcard.Card, error) {
|
|
||||||
card := make(vcard.Card)
|
card := make(vcard.Card)
|
||||||
|
for _, c := range contact.Cards {
|
||||||
for _, c := range ao.contact.Cards {
|
md, err := c.Read(b.privateKeys)
|
||||||
md, err := c.Read(ao.ab.privateKeys)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -149,80 +123,93 @@ func (ao *addressObject) Card() (vcard.Card, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return card, nil
|
return &carddav.AddressObject{
|
||||||
|
Path: formatAddressObjectPath(contact.ID),
|
||||||
|
ModTime: time.Unix(contact.ModifyTime, 0),
|
||||||
|
// TODO: stronger ETag
|
||||||
|
ETag: fmt.Sprintf("%x%x", contact.ModifyTime, contact.Size),
|
||||||
|
Card: card,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ao *addressObject) SetCard(card vcard.Card) error {
|
type backend struct {
|
||||||
contactImport, err := formatCard(card, ao.ab.privateKeys[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
contact, err := ao.ab.c.UpdateContact(ao.contact.ID, contactImport)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
contact.Cards = contactImport.Cards // Not returned by the server
|
|
||||||
|
|
||||||
ao.contact = contact
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ao *addressObject) Remove() error {
|
|
||||||
resps, err := ao.ab.c.DeleteContacts([]string{ao.contact.ID})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(resps) != 1 {
|
|
||||||
return errors.New("hydroxide/carddav: expected exactly one response when deleting contact")
|
|
||||||
}
|
|
||||||
resp := resps[0]
|
|
||||||
return resp.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
type addressBook struct {
|
|
||||||
c *protonmail.Client
|
c *protonmail.Client
|
||||||
cache map[string]*addressObject
|
cache map[string]*protonmail.Contact
|
||||||
locker sync.Mutex
|
locker sync.Mutex
|
||||||
total int
|
total int
|
||||||
privateKeys openpgp.EntityList
|
privateKeys openpgp.EntityList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) Info() (*carddav.AddressBookInfo, error) {
|
func (b *backend) AddressBook() (*carddav.AddressBook, error) {
|
||||||
return &carddav.AddressBookInfo{
|
return &carddav.AddressBook{
|
||||||
|
Path: "/",
|
||||||
Name: "ProtonMail",
|
Name: "ProtonMail",
|
||||||
Description: "ProtonMail contacts",
|
Description: "ProtonMail contacts",
|
||||||
MaxResourceSize: 100 * 1024,
|
MaxResourceSize: 100 * 1024,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) cacheComplete() bool {
|
func (b *backend) cacheComplete() bool {
|
||||||
ab.locker.Lock()
|
b.locker.Lock()
|
||||||
defer ab.locker.Unlock()
|
defer b.locker.Unlock()
|
||||||
return ab.total >= 0 && len(ab.cache) == ab.total
|
return b.total >= 0 && len(b.cache) == b.total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) addressObject(id string) (*addressObject, bool) {
|
func (b *backend) getCache(id string) (*protonmail.Contact, bool) {
|
||||||
ab.locker.Lock()
|
b.locker.Lock()
|
||||||
defer ab.locker.Unlock()
|
contact, ok := b.cache[id]
|
||||||
ao, ok := ab.cache[id]
|
b.locker.Unlock()
|
||||||
return ao, ok
|
return contact, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) cacheAddressObject(ao *addressObject) {
|
func (b *backend) putCache(contact *protonmail.Contact) {
|
||||||
ab.locker.Lock()
|
b.locker.Lock()
|
||||||
defer ab.locker.Unlock()
|
b.cache[contact.ID] = contact
|
||||||
ab.cache[ao.contact.ID] = ao
|
b.locker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
|
func (b *backend) deleteCache(id string) {
|
||||||
if ab.cacheComplete() {
|
b.locker.Lock()
|
||||||
ab.locker.Lock()
|
delete(b.cache, id)
|
||||||
defer ab.locker.Unlock()
|
b.locker.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
aos := make([]carddav.AddressObject, 0, len(ab.cache))
|
func (b *backend) GetAddressObject(path string, req *carddav.AddressDataRequest) (*carddav.AddressObject, error) {
|
||||||
for _, ao := range ab.cache {
|
id, err := parseAddressObjectPath(path)
|
||||||
aos = append(aos, ao)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contact, ok := b.getCache(id)
|
||||||
|
if !ok {
|
||||||
|
if b.cacheComplete() {
|
||||||
|
return nil, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
contact, err = b.c.GetContact(id)
|
||||||
|
if apiErr, ok := err.(*protonmail.APIError); ok && apiErr.Code == 13051 {
|
||||||
|
return nil, errNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.putCache(contact)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.toAddressObject(contact, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) ListAddressObjects(req *carddav.AddressDataRequest) ([]carddav.AddressObject, error) {
|
||||||
|
if b.cacheComplete() {
|
||||||
|
b.locker.Lock()
|
||||||
|
defer b.locker.Unlock()
|
||||||
|
|
||||||
|
aos := make([]carddav.AddressObject, 0, len(b.cache))
|
||||||
|
for _, contact := range b.cache {
|
||||||
|
ao, err := b.toAddressObject(contact, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aos = append(aos, *ao)
|
||||||
}
|
}
|
||||||
|
|
||||||
return aos, nil
|
return aos, nil
|
||||||
|
@ -230,48 +217,41 @@ func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
|
||||||
|
|
||||||
// Get a list of all contacts
|
// Get a list of all contacts
|
||||||
// TODO: paging support
|
// TODO: paging support
|
||||||
total, contacts, err := ab.c.ListContacts(0, 0)
|
total, contacts, err := b.c.ListContacts(0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ab.locker.Lock()
|
b.locker.Lock()
|
||||||
ab.total = total
|
b.total = total
|
||||||
ab.locker.Unlock()
|
b.locker.Unlock()
|
||||||
|
|
||||||
|
m := make(map[string]*protonmail.Contact, total)
|
||||||
for _, contact := range contacts {
|
for _, contact := range contacts {
|
||||||
if _, ok := ab.addressObject(contact.ID); !ok {
|
m[contact.ID] = contact
|
||||||
ab.cacheAddressObject(&addressObject{
|
|
||||||
ab: ab,
|
|
||||||
contact: contact,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all contacts cards
|
// Get all contacts cards
|
||||||
var aos []carddav.AddressObject
|
aos := make([]carddav.AddressObject, 0, total)
|
||||||
page := 0
|
page := 0
|
||||||
for {
|
for {
|
||||||
_, contacts, err := ab.c.ListContactsExport(page, 0)
|
_, contacts, err := b.c.ListContactsExport(page, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if aos == nil {
|
for _, contactExport := range contacts {
|
||||||
aos = make([]carddav.AddressObject, 0, total)
|
contact, ok := m[contactExport.ID]
|
||||||
}
|
|
||||||
|
|
||||||
for _, contact := range contacts {
|
|
||||||
ao, ok := ab.addressObject(contact.ID)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
ao = &addressObject{
|
continue
|
||||||
ab: ab,
|
|
||||||
contact: &protonmail.Contact{ID: contact.ID},
|
|
||||||
}
|
|
||||||
ab.cacheAddressObject(ao)
|
|
||||||
}
|
}
|
||||||
|
contact.Cards = contactExport.Cards
|
||||||
|
b.putCache(contact)
|
||||||
|
|
||||||
ao.contact.Cards = contact.Cards
|
ao, err := b.toAddressObject(contact, req)
|
||||||
aos = append(aos, ao)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aos = append(aos, *ao)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(aos) >= total || len(contacts) == 0 {
|
if len(aos) >= total || len(contacts) == 0 {
|
||||||
|
@ -283,79 +263,93 @@ func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
|
||||||
return aos, nil
|
return aos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) GetAddressObject(id string) (carddav.AddressObject, error) {
|
func (b *backend) QueryAddressObjects(query *carddav.AddressBookQuery) ([]carddav.AddressObject, error) {
|
||||||
if ao, ok := ab.addressObject(id); ok {
|
panic("TODO")
|
||||||
return ao, nil
|
|
||||||
} else if ab.cacheComplete() {
|
|
||||||
return nil, carddav.ErrNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contact, err := ab.c.GetContact(id)
|
func (b *backend) PutAddressObject(path string, card vcard.Card) (loc string, err error) {
|
||||||
|
id, err := parseAddressObjectPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: return carddav.ErrNotFound if appropriate
|
return "", err
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ao := &addressObject{
|
contactImport, err := formatCard(card, b.privateKeys[0])
|
||||||
ab: ab,
|
|
||||||
contact: contact,
|
|
||||||
}
|
|
||||||
ab.cacheAddressObject(ao)
|
|
||||||
return ao, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ab *addressBook) CreateAddressObject(card vcard.Card) (carddav.AddressObject, error) {
|
|
||||||
contactImport, err := formatCard(card, ab.privateKeys[0])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
resps, err := ab.c.CreateContacts([]*protonmail.ContactImport{contactImport})
|
var contact *protonmail.Contact
|
||||||
|
|
||||||
|
var req carddav.AddressDataRequest
|
||||||
|
if _, getErr := b.GetAddressObject(path, &req); getErr == nil {
|
||||||
|
contact, err = b.c.UpdateContact(id, contactImport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resps, err := b.c.CreateContacts([]*protonmail.ContactImport{contactImport})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
if len(resps) != 1 {
|
if len(resps) != 1 {
|
||||||
return nil, errors.New("hydroxide/carddav: expected exactly one response when creating contact")
|
return "", errors.New("hydroxide/carddav: expected exactly one response when creating contact")
|
||||||
}
|
}
|
||||||
resp := resps[0]
|
resp := resps[0]
|
||||||
if err := resp.Err(); err != nil {
|
if err := resp.Err(); err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
|
}
|
||||||
|
contact = resp.Response.Contact
|
||||||
}
|
}
|
||||||
contact := resp.Response.Contact
|
|
||||||
contact.Cards = contactImport.Cards // Not returned by the server
|
contact.Cards = contactImport.Cards // Not returned by the server
|
||||||
|
|
||||||
ao := &addressObject{
|
// TODO: increment b.total if necessary
|
||||||
ab: ab,
|
b.putCache(contact)
|
||||||
contact: contact,
|
return formatAddressObjectPath(contact.ID), nil
|
||||||
}
|
|
||||||
ab.cacheAddressObject(ao)
|
|
||||||
return ao, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *addressBook) receiveEvents(events <-chan *protonmail.Event) {
|
func (b *backend) DeleteAddressObject(path string) error {
|
||||||
|
id, err := parseAddressObjectPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resps, err := b.c.DeleteContacts([]string{id})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(resps) != 1 {
|
||||||
|
return errors.New("hydroxide/carddav: expected exactly one response when deleting contact")
|
||||||
|
}
|
||||||
|
resp := resps[0]
|
||||||
|
// TODO: decrement b.total if necessary
|
||||||
|
b.deleteCache(id)
|
||||||
|
return resp.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) receiveEvents(events <-chan *protonmail.Event) {
|
||||||
for event := range events {
|
for event := range events {
|
||||||
ab.locker.Lock()
|
b.locker.Lock()
|
||||||
if event.Refresh&protonmail.EventRefreshContacts != 0 {
|
if event.Refresh&protonmail.EventRefreshContacts != 0 {
|
||||||
ab.cache = make(map[string]*addressObject)
|
b.cache = make(map[string]*protonmail.Contact)
|
||||||
ab.total = -1
|
b.total = -1
|
||||||
} else if len(event.Contacts) > 0 {
|
} else if len(event.Contacts) > 0 {
|
||||||
for _, eventContact := range event.Contacts {
|
for _, eventContact := range event.Contacts {
|
||||||
switch eventContact.Action {
|
switch eventContact.Action {
|
||||||
case protonmail.EventCreate:
|
case protonmail.EventCreate:
|
||||||
ab.total++
|
if b.total >= 0 {
|
||||||
|
b.total++
|
||||||
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case protonmail.EventUpdate:
|
case protonmail.EventUpdate:
|
||||||
ab.cache[eventContact.ID] = &addressObject{
|
b.cache[eventContact.ID] = eventContact.Contact
|
||||||
ab: ab,
|
|
||||||
contact: eventContact.Contact,
|
|
||||||
}
|
|
||||||
case protonmail.EventDelete:
|
case protonmail.EventDelete:
|
||||||
delete(ab.cache, eventContact.ID)
|
delete(b.cache, eventContact.ID)
|
||||||
ab.total--
|
if b.total >= 0 {
|
||||||
|
b.total--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ab.locker.Unlock()
|
}
|
||||||
|
b.locker.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,16 +358,16 @@ func NewHandler(c *protonmail.Client, privateKeys openpgp.EntityList, events <-c
|
||||||
panic("hydroxide/carddav: no private key available")
|
panic("hydroxide/carddav: no private key available")
|
||||||
}
|
}
|
||||||
|
|
||||||
ab := &addressBook{
|
b := &backend{
|
||||||
c: c,
|
c: c,
|
||||||
cache: make(map[string]*addressObject),
|
cache: make(map[string]*protonmail.Contact),
|
||||||
total: -1,
|
total: -1,
|
||||||
privateKeys: privateKeys,
|
privateKeys: privateKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
if events != nil {
|
if events != nil {
|
||||||
go ab.receiveEvents(events)
|
go b.receiveEvents(events)
|
||||||
}
|
}
|
||||||
|
|
||||||
return carddav.NewHandler(ab)
|
return &carddav.Handler{b}
|
||||||
}
|
}
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -5,19 +5,17 @@ go 1.13
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
github.com/emersion/go-bcrypt v0.0.0-20170822072041-6e724a1baa63
|
github.com/emersion/go-bcrypt v0.0.0-20170822072041-6e724a1baa63
|
||||||
github.com/emersion/go-imap v1.0.2
|
github.com/emersion/go-imap v1.0.3
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
||||||
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62
|
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62
|
||||||
github.com/emersion/go-message v0.11.1
|
github.com/emersion/go-message v0.11.1
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b // indirect
|
|
||||||
github.com/emersion/go-smtp v0.12.1
|
github.com/emersion/go-smtp v0.12.1
|
||||||
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7
|
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7
|
||||||
github.com/emersion/go-webdav v0.1.0
|
github.com/emersion/go-webdav v0.2.1-0.20200203205455-3ea3818dd842
|
||||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
|
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/stretchr/testify v1.4.0 // indirect
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
|
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
)
|
)
|
||||||
|
|
24
go.sum
24
go.sum
|
@ -6,14 +6,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/emersion/go-bcrypt v0.0.0-20170822072041-6e724a1baa63 h1:7aCSuwTBzg7BCPRRaBJD0weKZYdeAykOrY6ktpx8Vvc=
|
github.com/emersion/go-bcrypt v0.0.0-20170822072041-6e724a1baa63 h1:7aCSuwTBzg7BCPRRaBJD0weKZYdeAykOrY6ktpx8Vvc=
|
||||||
github.com/emersion/go-bcrypt v0.0.0-20170822072041-6e724a1baa63/go.mod h1:eRwwJnuLVFtYTC+AI2JDJTMcuQUTYhBIK4I6bC5tpqw=
|
github.com/emersion/go-bcrypt v0.0.0-20170822072041-6e724a1baa63/go.mod h1:eRwwJnuLVFtYTC+AI2JDJTMcuQUTYhBIK4I6bC5tpqw=
|
||||||
github.com/emersion/go-imap v1.0.2 h1:Uf+Bv8SeV067xYBWyo7rLZ7ZbkrAXLSc1G+2IVDp5JU=
|
github.com/emersion/go-imap v1.0.3 h1:5eEee8/DTSIPfliiWqwfvjPGkU8bBtvOy/Wx+eeXzO4=
|
||||||
github.com/emersion/go-imap v1.0.2/go.mod h1:TjT+1ncDso8j/VXeUHcZeQknho5hjyQLqEIybJJjjDI=
|
github.com/emersion/go-imap v1.0.3/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
||||||
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62 h1:4ZAfwfc8aDlj26kkEap1UDSwwDnJp9Ie8Uj1MSXAkPk=
|
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62 h1:4ZAfwfc8aDlj26kkEap1UDSwwDnJp9Ie8Uj1MSXAkPk=
|
||||||
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62/go.mod h1:/nybxhI8kXom8Tw6BrHMl42usALvka6meORflnnYwe4=
|
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62/go.mod h1:/nybxhI8kXom8Tw6BrHMl42usALvka6meORflnnYwe4=
|
||||||
github.com/emersion/go-message v0.10.8 h1:1l1Vb+0By9U1ITTH3FgKfJQWQ9sTI3N1smPe6SS3QXY=
|
|
||||||
github.com/emersion/go-message v0.10.8/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
|
||||||
github.com/emersion/go-message v0.11.1 h1:0C/S4JIXDTSfXB1vpqdimAYyK4+79fgEAMQ0dSL+Kac=
|
github.com/emersion/go-message v0.11.1 h1:0C/S4JIXDTSfXB1vpqdimAYyK4+79fgEAMQ0dSL+Kac=
|
||||||
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
||||||
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=
|
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=
|
||||||
|
@ -24,12 +22,10 @@ github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5s
|
||||||
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
|
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
|
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
|
|
||||||
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7 h1:SE+tcd+0kn0cT4MqTo66gmkjqWHF1Z+Yha5/rhLs/H8=
|
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7 h1:SE+tcd+0kn0cT4MqTo66gmkjqWHF1Z+Yha5/rhLs/H8=
|
||||||
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
github.com/emersion/go-webdav v0.1.0 h1:d45qVFZebAj8Elzk59pfb4DgIjW3bmXjtzvmD83mjQw=
|
github.com/emersion/go-webdav v0.2.1-0.20200203205455-3ea3818dd842 h1:CkiVEfQsswQP265LwFfylFiMhQVNQx0LrROT6dlv9ZQ=
|
||||||
github.com/emersion/go-webdav v0.1.0/go.mod h1:PDMbYd9nLfHDxQw18jkbpGCCYO8gzByT3M1VnG9L05c=
|
github.com/emersion/go-webdav v0.2.1-0.20200203205455-3ea3818dd842/go.mod h1:5xPS/W9bnqkHMLtgygkgGFvm6uAsbeVRSIZk0ewu4d4=
|
||||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk=
|
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk=
|
||||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
@ -48,17 +44,13 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
|
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w=
|
||||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c h1:gUYreENmqtjZb2brVfUas1sC6UivSY8XwKwPo8tloLs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||||
golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
|
Loading…
Reference in New Issue