hydroxide-push/carddav/carddav.go

194 lines
3.5 KiB
Go
Raw Normal View History

2017-09-03 21:11:01 +03:00
package carddav
import (
"net/http"
2017-09-04 13:06:36 +03:00
"os"
2017-09-03 21:11:01 +03:00
"strings"
2017-09-09 17:37:14 +03:00
"time"
2017-09-03 21:11:01 +03:00
"github.com/emersion/hydroxide/protonmail"
"github.com/emersion/go-vcard"
"github.com/emersion/go-webdav/carddav"
)
2017-09-09 16:37:03 +03:00
type contextKey string
const ClientContextKey = contextKey("client")
2017-09-09 17:37:14 +03:00
type addressFileInfo struct {
contact *protonmail.Contact
}
func (fi *addressFileInfo) Name() string {
return fi.contact.ID + ".vcf"
}
func (fi *addressFileInfo) Size() int64 {
return int64(fi.contact.Size)
}
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
}
2017-09-03 21:11:01 +03:00
type addressObject struct {
c *protonmail.Client
2017-09-09 17:37:14 +03:00
contact *protonmail.Contact
2017-09-03 21:11:01 +03:00
}
func (ao *addressObject) ID() string {
return ao.contact.ID
}
func (ao *addressObject) Card() (vcard.Card, error) {
card := make(vcard.Card)
for _, c := range ao.contact.Cards {
if c.Type.Encrypted() {
// TODO: decrypt
continue
}
if c.Type.Signed() {
// TODO: check signature
}
decoded, err := vcard.NewDecoder(strings.NewReader(c.Data)).Decode()
if err != nil {
return nil, err
}
for k, fields := range decoded {
for _, f := range fields {
card.Add(k, f)
}
}
}
return card, nil
}
2017-09-04 13:06:36 +03:00
func (ao *addressObject) Stat() (os.FileInfo, error) {
2017-09-09 17:37:14 +03:00
return &addressFileInfo{ao.contact}, nil
2017-09-04 13:06:36 +03:00
}
2017-09-03 21:11:01 +03:00
type addressBook struct {
2017-09-04 12:41:26 +03:00
c *protonmail.Client
2017-09-09 17:37:14 +03:00
cache map[string]*addressObject
2017-09-04 12:41:26 +03:00
total int
}
2017-09-09 17:45:05 +03:00
func (ab *addressBook) Info() (*carddav.AddressBookInfo, error) {
return &carddav.AddressBookInfo{
Name: "ProtonMail",
Description: "ProtonMail contacts",
MaxResourceSize: 100 * 1024,
}, nil
}
2017-09-04 12:41:26 +03:00
func (ab *addressBook) cacheComplete() bool {
return ab.total >= 0 && len(ab.cache) == ab.total
2017-09-03 21:11:01 +03:00
}
func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
2017-09-04 12:41:26 +03:00
if ab.cacheComplete() {
aos := make([]carddav.AddressObject, 0, len(ab.cache))
for _, ao := range ab.cache {
aos = append(aos, ao)
}
return aos, nil
}
2017-09-09 17:37:14 +03:00
// Get a list of all contacts
// TODO: paging support
total, contacts, err := ab.c.ListContacts(0, 0)
if err != nil {
return nil, err
}
ab.total = total
for _, contact := range contacts {
if _, ok := ab.cache[contact.ID]; !ok {
ab.cache[contact.ID] = &addressObject{
c: ab.c,
contact: contact,
}
}
}
// Get all contacts cards
2017-09-04 12:46:14 +03:00
var aos []carddav.AddressObject
page := 0
for {
total, contacts, err := ab.c.ListContactsExport(page, 0)
if err != nil {
return nil, err
}
ab.total = total
2017-09-03 21:11:01 +03:00
2017-09-04 12:46:14 +03:00
if aos == nil {
aos = make([]carddav.AddressObject, 0, total)
}
2017-09-04 12:41:26 +03:00
2017-09-04 12:46:14 +03:00
for _, contact := range contacts {
2017-09-09 17:37:14 +03:00
ao, ok := ab.cache[contact.ID]
if !ok {
ao = &addressObject{
c: ab.c,
contact: &protonmail.Contact{ID: contact.ID},
}
ab.cache[contact.ID] = ao
}
ao.contact.Cards = contact.Cards
2017-09-04 12:46:14 +03:00
aos = append(aos, ao)
}
if len(aos) == total || len(contacts) == 0 {
break
}
page++
2017-09-03 21:11:01 +03:00
}
return aos, nil
}
func (ab *addressBook) GetAddressObject(id string) (carddav.AddressObject, error) {
2017-09-04 12:41:26 +03:00
if ao, ok := ab.cache[id]; ok {
return ao, nil
} else if ab.cacheComplete() {
return nil, carddav.ErrNotFound
}
2017-09-03 21:11:01 +03:00
contact, err := ab.c.GetContact(id)
if err != nil {
return nil, err
}
2017-09-04 12:41:26 +03:00
ao := &addressObject{
2017-09-03 21:11:01 +03:00
c: ab.c,
2017-09-09 17:37:14 +03:00
contact: contact,
2017-09-04 12:41:26 +03:00
}
ab.cache[id] = ao
return ao, nil
2017-09-03 21:11:01 +03:00
}
func NewHandler(c *protonmail.Client) http.Handler {
2017-09-04 12:41:26 +03:00
return carddav.NewHandler(&addressBook{
c: c,
2017-09-09 17:37:14 +03:00
cache: make(map[string]*addressObject),
2017-09-04 12:41:26 +03:00
total: -1,
})
2017-09-03 21:11:01 +03:00
}