6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-27 09:18:22 +00:00
prologic-saltyim/identity.go
xuu 754fcc7323 feat: add compression negotiation for sent messages (#91)
feat: add compression negotiation for sent messages
fix: unix homedir handling

the service will negotiate a compression algo for sending messages
when a user chats someone during the auto discovery, the service returns an `Accept-Encoding: br, gzip, deflate`

the client saves that response and so when it makes POSTs of messages adds the best `Content-Encoding` and compresses the message

example:
```
>> GET /.well-known/salty/c765c69040d98f3af2181237f47ec01398d80f8ab2690fe929e4311ab05dec01.json

<< Accept-Encoding: br, gzip, deflate
<<
<< {"endpoint":"https://salty.home.arpa/inbox/01FZBR8Y2E6TH949JA3925WF71","key":"kex1wurry09ftqjuxgjl0jxmqypv4axqvzqljkgeadxjcpwtfuhcedcslck52d"}

>> POST /inbox/01FZBR8Y2E6TH949JA3925WF71
>> Content-Encoding: br
>>
>> [Brotli Compressed data]
```

this PR depends on https://git.mills.io/prologic/msgbus/pulls/24

Co-authored-by: Jon Lundy <jon@xuu.cc>
Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/91
Co-authored-by: xuu <xuu@noreply@mills.io>
Co-committed-by: xuu <xuu@noreply@mills.io>
2022-03-29 22:23:16 +00:00

205 lines
4.5 KiB
Go

package saltyim
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/keys-pub/keys"
"go.mills.io/salty"
)
func readUser(fd io.Reader) (*Addr, error) {
scan := bufio.NewScanner(fd)
addr := &Addr{}
for scan.Scan() {
if strings.HasPrefix(scan.Text(), "# user:") {
user := strings.Split(strings.TrimSpace(strings.TrimPrefix(scan.Text(), "# user:")), "@")
if len(user) != 2 {
return nil, nil
}
addr.User, addr.Domain = user[0], user[1]
}
}
return addr, scan.Err()
}
// DefaultIdentity returns a default identity file (if one exists) otherwise
// returns an empty string
func DefaultIdentity() string {
return os.ExpandEnv("$HOME/.config/salty/$USER.key")
}
// CreateIdentity ...
func CreateIdentity(options ...IdentityOption) (*Identity, error) {
ident := &Identity{}
for _, option := range options {
option(ident)
}
if ident.addr == nil || ident.addr.IsZero() {
return nil, fmt.Errorf("unable to find user address in options")
}
buf := &bytes.Buffer{}
key, _ := salty.GenerateKeys(buf)
ident.key = key
buf.Write([]byte(fmt.Sprintf("# user: %s\n", ident.addr.String())))
ident.contents = buf.Bytes()
if ident.path == "" {
return ident, nil
}
fn := ident.path
fn = FixUnixHome(fn)
f, err := os.OpenFile(fn, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
if err != nil {
return ident, fmt.Errorf("error opening identity %q for writing: %w", fn, err)
}
defer f.Close()
if _, err := f.Write(ident.contents); err != nil {
return ident, fmt.Errorf("error writing identity %q: %w", fn, err)
}
if err := f.Sync(); err != nil {
return ident, fmt.Errorf("error syncing identity %q for writing: %w", fn, err)
}
if err := f.Close(); err != nil {
return ident, fmt.Errorf("error closing identity %q for writing: %w", fn, err)
}
return ident, nil
}
// GetIdentity ...
func GetIdentity(options ...IdentityOption) (*Identity, error) {
ident := &Identity{}
for _, option := range options {
option(ident)
}
if ident.key != nil && ident.addr != nil {
return ident, nil
}
if ident.path != "" {
fn := ident.path
fn = FixUnixHome(fn)
id, err := os.Open(fn)
if err != nil {
return ident, fmt.Errorf("error opening identity %q: %w", ident.Source(), err)
}
defer id.Close()
identityBytes, err := ioutil.ReadAll(id)
if err != nil {
return ident, fmt.Errorf("error opening identity %q: %w", ident.Source(), err)
}
ident.contents = identityBytes
}
key, err := salty.ParseIdentity(bytes.NewBuffer(ident.contents))
if err != nil {
return ident, fmt.Errorf("error reading identity %q: %w", ident.Source(), err)
}
ident.key = key
me, err := readUser(bytes.NewBuffer(ident.contents))
ident.addr = me
return ident, err
}
// GetOrCreateIdentity ...
func GetOrCreateIdentity(options ...IdentityOption) (*Identity, error) {
ident, err := GetIdentity(options...)
if err != nil {
ident, err = CreateIdentity(options...)
}
return ident, err
}
// Identity allows interaction with CreateIdentity, GetIdentity, and NewClient
type Identity struct {
// path where identity is read or written to
path string
// contents where identity is read from or stored
contents []byte
// key is the identity key
key *keys.EdX25519Key
// addr is the user / endpoint
addr *Addr
}
func (i *Identity) Source() string {
if i != nil && i.path != "" {
return i.path
}
return "[]byte"
}
func (i *Identity) Contents() []byte {
return i.contents
}
func (i *Identity) Key() *keys.EdX25519Key {
return i.key
}
func (i *Identity) Addr() *Addr {
return i.addr
}
// IdentityOption represents functional options for various identity operations
type IdentityOption func(*Identity)
// WithIdentityAddr sets the identity Addr option
func WithIdentityAddr(addr *Addr) IdentityOption {
return func(i *Identity) { i.addr = addr }
}
// WithIdentity indicates that an identity should be passed in
func WithIdentity(ident *Identity) IdentityOption {
return func(i *Identity) {
i.key = ident.key
i.addr = ident.addr
}
}
// WithIdentityPath indicates that an identity should be read / written from a file path
func WithIdentityPath(path string) IdentityOption {
return func(i *Identity) { i.path = path }
}
// WithIdentityBytes indicates that the identity should be read from a byte array
func WithIdentityBytes(contents []byte) IdentityOption {
return func(i *Identity) { i.contents = contents }
}
// Handle unix home with `~`
func FixUnixHome(p string) string {
// Handle unix home with `~`
if strings.HasPrefix(p, "~/") {
dirname, _ := os.UserHomeDir()
return filepath.Join(dirname, p[2:])
}
return p
}