Files
go-proton-api/contact_card.go
Leander Beernaert 65479b90c4 fix(GODT-3117): Improve Contact Info Retrieval
Rather than only fetching the total in on request and discarding all the
data, re-use the first page of data and then collect more of them if the
data set exceeds the page size.

This patch also includes various fixes to the GPA server to mimic
proton server behavior.
2023-11-20 16:01:19 +01:00

389 lines
7.0 KiB
Go

package proton
import (
"bytes"
"errors"
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/bradenaw/juniper/xslices"
"github.com/emersion/go-vcard"
)
const (
FieldPMScheme = "X-PM-SCHEME"
FieldPMSign = "X-PM-SIGN"
FieldPMEncrypt = "X-PM-ENCRYPT"
FieldPMEncryptUntrusted = "X-PM-ENCRYPT-UNTRUSTED"
FieldPMMIMEType = "X-PM-MIMETYPE"
)
type Cards []*Card
func (c *Cards) Merge(kr *crypto.KeyRing) (vcard.Card, error) {
merged := newVCard()
for _, card := range *c {
dec, err := card.decode(kr)
if err != nil {
return nil, err
}
for k, fields := range dec {
for _, f := range fields {
merged.Add(k, f)
}
}
}
return merged, nil
}
func (c *Cards) Get(cardType CardType) (*Card, bool) {
for _, card := range *c {
if card.Type == cardType {
return card, true
}
}
return nil, false
}
type Card struct {
Type CardType
Data string
Signature string
}
type CardType int
const (
CardTypeClear CardType = iota
CardTypeEncrypted
CardTypeSigned
)
func NewCard(kr *crypto.KeyRing, cardType CardType) (*Card, error) {
card := &Card{Type: cardType}
if err := card.encode(kr, newVCard()); err != nil {
return nil, err
}
return card, nil
}
func newVCard() vcard.Card {
card := make(vcard.Card)
card.AddValue(vcard.FieldVersion, "4.0")
return card
}
func (c Card) Get(kr *crypto.KeyRing, key string) ([]*vcard.Field, error) {
dec, err := c.decode(kr)
if err != nil {
return nil, err
}
return dec[key], nil
}
func (c *Card) Set(kr *crypto.KeyRing, key string, value *vcard.Field) error {
dec, err := c.decode(kr)
if err != nil {
return err
}
if field := dec.Get(key); field != nil {
field.Value = value.Value
field.Params = value.Params
field.Group = value.Group
return c.encode(kr, dec)
}
dec.Add(key, value)
return c.encode(kr, dec)
}
func (c *Card) Add(kr *crypto.KeyRing, key string, value *vcard.Field) error {
dec, err := c.decode(kr)
if err != nil {
return err
}
dec.Add(key, value)
return c.encode(kr, dec)
}
func (c *Card) ChangeType(kr *crypto.KeyRing, cardType CardType) error {
dec, err := c.decode(kr)
if err != nil {
return err
}
c.Type = cardType
return c.encode(kr, dec)
}
// GetGroup returns a type to manipulate the group defined by the given key/value pair.
func (c Card) GetGroup(kr *crypto.KeyRing, groupKey, groupValue string) (CardGroup, error) {
group, err := c.getGroup(kr, groupKey, groupValue)
if err != nil {
return CardGroup{}, err
}
return CardGroup{Card: c, kr: kr, group: group}, nil
}
// DeleteGroup removes all values in the group defined by the given key/value pair.
func (c *Card) DeleteGroup(kr *crypto.KeyRing, groupKey, groupValue string) error {
group, err := c.getGroup(kr, groupKey, groupValue)
if err != nil {
return err
}
return c.deleteGroup(kr, group)
}
type CardGroup struct {
Card
kr *crypto.KeyRing
group string
}
// Get returns the values in the group with the given key.
func (g CardGroup) Get(key string) ([]string, error) {
dec, err := g.decode(g.kr)
if err != nil {
return nil, err
}
var fields []*vcard.Field
for _, field := range dec[key] {
if field.Group != g.group {
continue
}
fields = append(fields, field)
}
return xslices.Map(fields, func(field *vcard.Field) string {
return field.Value
}), nil
}
// Set sets the value in the group.
func (g *CardGroup) Set(key, value string, params vcard.Params) error {
dec, err := g.decode(g.kr)
if err != nil {
return err
}
for _, field := range dec[key] {
if field.Group != g.group {
continue
}
field.Value = value
return g.encode(g.kr, dec)
}
dec.Add(key, &vcard.Field{
Value: value,
Group: g.group,
Params: params,
})
return g.encode(g.kr, dec)
}
// Add adds a value to the group.
func (g *CardGroup) Add(key, value string, params vcard.Params) error {
dec, err := g.decode(g.kr)
if err != nil {
return err
}
dec.Add(key, &vcard.Field{
Value: value,
Group: g.group,
Params: params,
})
return g.encode(g.kr, dec)
}
// Remove removes the value in the group with the given key/value.
func (g *CardGroup) Remove(key, value string) error {
dec, err := g.decode(g.kr)
if err != nil {
return err
}
fields, ok := dec[key]
if !ok {
return errors.New("no such key")
}
var rest []*vcard.Field
for _, field := range fields {
if field.Group != g.group {
rest = append(rest, field)
} else if field.Value != value {
rest = append(rest, field)
}
}
if len(rest) > 0 {
dec[key] = rest
} else {
delete(dec, key)
}
return g.encode(g.kr, dec)
}
// RemoveAll removes all values in the group with the given key.
func (g *CardGroup) RemoveAll(key string) error {
dec, err := g.decode(g.kr)
if err != nil {
return err
}
fields, ok := dec[key]
if !ok {
return errors.New("no such key")
}
var rest []*vcard.Field
for _, field := range fields {
if field.Group != g.group {
rest = append(rest, field)
}
}
if len(rest) > 0 {
dec[key] = rest
} else {
delete(dec, key)
}
return g.encode(g.kr, dec)
}
func (c Card) getGroup(kr *crypto.KeyRing, groupKey, groupValue string) (string, error) {
fields, err := c.Get(kr, groupKey)
if err != nil {
return "", err
}
for _, field := range fields {
if field.Value != groupValue {
continue
}
return field.Group, nil
}
return "", errors.New("no such field")
}
func (c *Card) deleteGroup(kr *crypto.KeyRing, group string) error {
dec, err := c.decode(kr)
if err != nil {
return err
}
for key, fields := range dec {
var rest []*vcard.Field
for _, field := range fields {
if field.Group != group {
rest = append(rest, field)
}
}
if len(rest) > 0 {
dec[key] = rest
} else {
delete(dec, key)
}
}
return c.encode(kr, dec)
}
func (c Card) decode(kr *crypto.KeyRing) (vcard.Card, error) {
if c.Type&CardTypeEncrypted != 0 {
enc, err := crypto.NewPGPMessageFromArmored(c.Data)
if err != nil {
return nil, err
}
dec, err := kr.Decrypt(enc, nil, crypto.GetUnixTime())
if err != nil {
return nil, err
}
c.Data = dec.GetString()
}
if c.Type&CardTypeSigned != 0 {
sig, err := crypto.NewPGPSignatureFromArmored(c.Signature)
if err != nil {
return nil, err
}
if err := kr.VerifyDetached(crypto.NewPlainMessageFromString(c.Data), sig, crypto.GetUnixTime()); err != nil {
return nil, err
}
}
return vcard.NewDecoder(strings.NewReader(c.Data)).Decode()
}
func (c *Card) encode(kr *crypto.KeyRing, card vcard.Card) error {
buf := new(bytes.Buffer)
if err := vcard.NewEncoder(buf).Encode(card); err != nil {
return err
}
if c.Type&CardTypeSigned != 0 {
sig, err := kr.SignDetached(crypto.NewPlainMessageFromString(buf.String()))
if err != nil {
return err
}
if c.Signature, err = sig.GetArmored(); err != nil {
return err
}
}
if c.Type&CardTypeEncrypted != 0 {
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(buf.String()), nil)
if err != nil {
return err
}
if c.Data, err = enc.GetArmored(); err != nil {
return err
}
} else {
c.Data = buf.String()
}
return nil
}