x-1/identity.go

243 lines
5.5 KiB
Go

package main
import (
"bytes"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"net/url"
"strings"
)
type Identities struct {
ByName map[string]*tls.Config
ByDomain map[string]*tls.Config
ByPage map[string]*tls.Config
ByFolder map[string]*tls.Config
}
func findIdentity(state *BrowserState, prefix string) (string, error) {
found := 0
value := ""
for name := range state.Identities.ByName {
if strings.HasPrefix(name, prefix) {
found += 1
value = name
}
}
switch found {
case 0:
return "", errors.New("no matching identity found")
case 1:
return value, nil
default:
return "", fmt.Errorf("too ambiguous - that name matched %d identities", found)
}
}
func (ids Identities) Get(u *url.URL) *tls.Config {
if conf, ok := ids.ByPage[u.String()]; ok {
return conf
}
if conf, ok := ids.ByFolder[u.Hostname()+u.Path]; ok {
return conf
}
pathsegments := strings.Split(strings.TrimLeft(u.Path, "/"), "/")
for len(pathsegments) > 0 {
pathsegments = pathsegments[0 : len(pathsegments)-1]
if conf, ok := ids.ByFolder[u.Hostname()+"/"+strings.Join(pathsegments, "/")]; ok {
return conf
}
}
if conf, ok := ids.ByDomain[u.Hostname()]; ok {
return conf
}
return nil
}
func IdentityCreate(state *BrowserState, name string) error {
ident, err := createIdentity(state, name)
if err != nil {
return err
}
state.Identities.ByName[name] = ident
if err := saveIdentities(state.Identities); err != nil {
return err
}
state.Modal = []byte(fmt.Sprintf("Created new identity %s\n", name))
return Print(state)
}
func IdentityList(state *BrowserState) error {
buf := &bytes.Buffer{}
for name, ident := range state.Identities.ByName {
if _, err := fmt.Fprintf(buf, "%s (%s):\n", name, showIdent(ident)); err != nil {
return err
}
for domain, id := range state.Identities.ByDomain {
if id == ident {
if _, err := fmt.Fprintf(buf, " domain %s\n", domain); err != nil {
return err
}
}
}
for folder, id := range state.Identities.ByFolder {
if id == ident {
if _, err := fmt.Fprintf(buf, " folder %s\n", folder); err != nil {
return err
}
}
}
for page, id := range state.Identities.ByPage {
if id == ident {
if _, err := fmt.Fprintf(buf, " page %s\n", page); err != nil {
return err
}
}
}
}
state.Modal = buf.Bytes()
if len(state.Modal) == 0 {
state.Modal = []byte("(no identities)\n")
}
return Print(state)
}
func IdentityDelete(state *BrowserState, name string) error {
name, err := findIdentity(state, name)
if err != nil {
return err
}
ident := state.Identities.ByName[name]
delete(state.Identities.ByName, name)
for domain, id := range state.Identities.ByDomain {
if id == ident {
delete(state.Identities.ByDomain, domain)
}
}
for folder, id := range state.Identities.ByFolder {
if id == ident {
delete(state.Identities.ByFolder, folder)
}
}
for page, id := range state.Identities.ByPage {
if id == ident {
delete(state.Identities.ByPage, page)
}
}
if err := removeIdentity(name); err != nil {
return err
}
if err := saveIdentities(state.Identities); err != nil {
return err
}
state.Modal = []byte(fmt.Sprintf("Removed identity %s\n", name))
return Print(state)
}
func IdentityUseDomain(state *BrowserState, name string, domain string) error {
name, err := findIdentity(state, name)
if err != nil {
return err
}
ident := state.Identities.ByName[name]
u, _, err := parseURL(domain, state, "gemini")
if errors.Is(err, invalidLinkErr("")) {
u, err = url.Parse(domain)
if err != nil {
return ErrInvalidLink(domain)
}
if u.Hostname() == "" {
u.Host = domain
}
} else if err != nil {
return err
}
state.Identities.ByDomain[u.Hostname()] = ident
if err := saveIdentities(state.Identities); err != nil {
return err
}
state.Modal = []byte(fmt.Sprintf("Identity %s will be used across domain %s\n", name, u.Hostname()))
return Print(state)
}
func IdentityUseFolder(state *BrowserState, name string, domain string) error {
name, err := findIdentity(state, name)
if err != nil {
return err
}
ident := state.Identities.ByName[name]
u, _, err := parseURL(domain, state, "gemini")
if errors.Is(err, invalidLinkErr("")) {
u, err = url.Parse(domain)
if err != nil {
return ErrInvalidLink(domain)
}
if u.Hostname() == "" {
u.Host = domain
}
} else if err != nil {
return err
}
u.Path = strings.TrimRight(u.Path, "/")
state.Identities.ByFolder[u.Hostname()+u.Path] = ident
if err := saveIdentities(state.Identities); err != nil {
return err
}
state.Modal = []byte(fmt.Sprintf("Identity %s will be used within folder %s%s\n", name, u.Hostname(), u.Path))
return Print(state)
}
func IdentityUsePage(state *BrowserState, name string, domain string) error {
name, err := findIdentity(state, name)
if err != nil {
return err
}
ident := state.Identities.ByName[name]
u, _, err := parseURL(domain, state, "gemini")
if errors.Is(err, invalidLinkErr("")) {
u, err = url.Parse(domain)
if err != nil {
return ErrInvalidLink(domain)
}
if u.Hostname() == "" {
u.Host = domain
}
} else if err != nil {
return err
}
state.Identities.ByPage[u.String()] = ident
if err := saveIdentities(state.Identities); err != nil {
return err
}
state.Modal = []byte(fmt.Sprintf("Identity %s will be used on page %s\n", name, u.String()))
return Print(state)
}
func showIdent(ident *tls.Config) string {
hash := sha256.Sum256(ident.Certificates[0].Certificate[0])
return strings.ToUpper(hex.EncodeToString(hash[:])[:10])
}