diff options
Diffstat (limited to 'vendor/maunium.net/go/mautrix/id')
-rw-r--r-- | vendor/maunium.net/go/mautrix/id/contenturi.go | 158 | ||||
-rw-r--r-- | vendor/maunium.net/go/mautrix/id/crypto.go | 149 | ||||
-rw-r--r-- | vendor/maunium.net/go/mautrix/id/matrixuri.go | 293 | ||||
-rw-r--r-- | vendor/maunium.net/go/mautrix/id/opaque.go | 83 | ||||
-rw-r--r-- | vendor/maunium.net/go/mautrix/id/trust.go | 87 | ||||
-rw-r--r-- | vendor/maunium.net/go/mautrix/id/userid.go | 224 |
6 files changed, 994 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/mautrix/id/contenturi.go b/vendor/maunium.net/go/mautrix/id/contenturi.go new file mode 100644 index 00000000..cfd00c3e --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/contenturi.go @@ -0,0 +1,158 @@ +// Copyright (c) 2020 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package id + +import ( + "bytes" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "strings" +) + +var ( + InvalidContentURI = errors.New("invalid Matrix content URI") + InputNotJSONString = errors.New("input doesn't look like a JSON string") +) + +// ContentURIString is a string that's expected to be a Matrix content URI. +// It's useful for delaying the parsing of the content URI to move errors from the event content +// JSON parsing step to a later step where more appropriate errors can be produced. +type ContentURIString string + +func (uriString ContentURIString) Parse() (ContentURI, error) { + return ParseContentURI(string(uriString)) +} + +func (uriString ContentURIString) ParseOrIgnore() ContentURI { + parsed, _ := ParseContentURI(string(uriString)) + return parsed +} + +// ContentURI represents a Matrix content URI. +// https://spec.matrix.org/v1.2/client-server-api/#matrix-content-mxc-uris +type ContentURI struct { + Homeserver string + FileID string +} + +func MustParseContentURI(uri string) ContentURI { + parsed, err := ParseContentURI(uri) + if err != nil { + panic(err) + } + return parsed +} + +// ParseContentURI parses a Matrix content URI. +func ParseContentURI(uri string) (parsed ContentURI, err error) { + if len(uri) == 0 { + return + } else if !strings.HasPrefix(uri, "mxc://") { + err = InvalidContentURI + } else if index := strings.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 { + err = InvalidContentURI + } else { + parsed.Homeserver = uri[6 : 6+index] + parsed.FileID = uri[6+index+1:] + } + return +} + +var mxcBytes = []byte("mxc://") + +func ParseContentURIBytes(uri []byte) (parsed ContentURI, err error) { + if len(uri) == 0 { + return + } else if !bytes.HasPrefix(uri, mxcBytes) { + err = InvalidContentURI + } else if index := bytes.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 { + err = InvalidContentURI + } else { + parsed.Homeserver = string(uri[6 : 6+index]) + parsed.FileID = string(uri[6+index+1:]) + } + return +} + +func (uri *ContentURI) UnmarshalJSON(raw []byte) (err error) { + if string(raw) == "null" { + *uri = ContentURI{} + return nil + } else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' { + return InputNotJSONString + } + parsed, err := ParseContentURIBytes(raw[1 : len(raw)-1]) + if err != nil { + return err + } + *uri = parsed + return nil +} + +func (uri *ContentURI) MarshalJSON() ([]byte, error) { + if uri == nil || uri.IsEmpty() { + return []byte("null"), nil + } + return json.Marshal(uri.String()) +} + +func (uri *ContentURI) UnmarshalText(raw []byte) (err error) { + parsed, err := ParseContentURIBytes(raw) + if err != nil { + return err + } + *uri = parsed + return nil +} + +func (uri ContentURI) MarshalText() ([]byte, error) { + return []byte(uri.String()), nil +} + +func (uri *ContentURI) Scan(i interface{}) error { + var parsed ContentURI + var err error + switch value := i.(type) { + case nil: + // don't do anything, set uri to empty + case []byte: + parsed, err = ParseContentURIBytes(value) + case string: + parsed, err = ParseContentURI(value) + default: + return fmt.Errorf("invalid type %T for ContentURI.Scan", i) + } + if err != nil { + return err + } + *uri = parsed + return nil +} + +func (uri *ContentURI) Value() (driver.Value, error) { + if uri == nil { + return nil, nil + } + return uri.String(), nil +} + +func (uri ContentURI) String() string { + if uri.IsEmpty() { + return "" + } + return fmt.Sprintf("mxc://%s/%s", uri.Homeserver, uri.FileID) +} + +func (uri ContentURI) CUString() ContentURIString { + return ContentURIString(uri.String()) +} + +func (uri ContentURI) IsEmpty() bool { + return len(uri.Homeserver) == 0 || len(uri.FileID) == 0 +} diff --git a/vendor/maunium.net/go/mautrix/id/crypto.go b/vendor/maunium.net/go/mautrix/id/crypto.go new file mode 100644 index 00000000..84fcd67f --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/crypto.go @@ -0,0 +1,149 @@ +// Copyright (c) 2020 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package id + +import ( + "fmt" + "strings" +) + +// OlmMsgType is an Olm message type +type OlmMsgType int + +const ( + OlmMsgTypePreKey OlmMsgType = 0 + OlmMsgTypeMsg OlmMsgType = 1 +) + +// Algorithm is a Matrix message encryption algorithm. +// https://spec.matrix.org/v1.2/client-server-api/#messaging-algorithm-names +type Algorithm string + +const ( + AlgorithmOlmV1 Algorithm = "m.olm.v1.curve25519-aes-sha2" + AlgorithmMegolmV1 Algorithm = "m.megolm.v1.aes-sha2" +) + +type KeyAlgorithm string + +const ( + KeyAlgorithmCurve25519 KeyAlgorithm = "curve25519" + KeyAlgorithmEd25519 KeyAlgorithm = "ed25519" + KeyAlgorithmSignedCurve25519 KeyAlgorithm = "signed_curve25519" +) + +type CrossSigningUsage string + +const ( + XSUsageMaster CrossSigningUsage = "master" + XSUsageSelfSigning CrossSigningUsage = "self_signing" + XSUsageUserSigning CrossSigningUsage = "user_signing" +) + +// A SessionID is an arbitrary string that identifies an Olm or Megolm session. +type SessionID string + +func (sessionID SessionID) String() string { + return string(sessionID) +} + +// Ed25519 is the base64 representation of an Ed25519 public key +type Ed25519 string +type SigningKey = Ed25519 + +func (ed25519 Ed25519) String() string { + return string(ed25519) +} + +func (ed25519 Ed25519) Fingerprint() string { + spacedSigningKey := make([]byte, len(ed25519)+(len(ed25519)-1)/4) + var ptr = 0 + for i, chr := range ed25519 { + spacedSigningKey[ptr] = byte(chr) + ptr++ + if i%4 == 3 { + spacedSigningKey[ptr] = ' ' + ptr++ + } + } + return string(spacedSigningKey) +} + +// Curve25519 is the base64 representation of an Curve25519 public key +type Curve25519 string +type SenderKey = Curve25519 +type IdentityKey = Curve25519 + +func (curve25519 Curve25519) String() string { + return string(curve25519) +} + +// A DeviceID is an arbitrary string that references a specific device. +type DeviceID string + +func (deviceID DeviceID) String() string { + return string(deviceID) +} + +// A DeviceKeyID is a string formatted as <algorithm>:<device_id> that is used as the key in deviceid-key mappings. +type DeviceKeyID string + +func NewDeviceKeyID(algorithm KeyAlgorithm, deviceID DeviceID) DeviceKeyID { + return DeviceKeyID(fmt.Sprintf("%s:%s", algorithm, deviceID)) +} + +func (deviceKeyID DeviceKeyID) String() string { + return string(deviceKeyID) +} + +func (deviceKeyID DeviceKeyID) Parse() (Algorithm, DeviceID) { + index := strings.IndexRune(string(deviceKeyID), ':') + if index < 0 || len(deviceKeyID) <= index+1 { + return "", "" + } + return Algorithm(deviceKeyID[:index]), DeviceID(deviceKeyID[index+1:]) +} + +// A KeyID a string formatted as <keyalgorithm>:<key_id> that is used as the key in one-time-key mappings. +type KeyID string + +func NewKeyID(algorithm KeyAlgorithm, keyID string) KeyID { + return KeyID(fmt.Sprintf("%s:%s", algorithm, keyID)) +} + +func (keyID KeyID) String() string { + return string(keyID) +} + +func (keyID KeyID) Parse() (KeyAlgorithm, string) { + index := strings.IndexRune(string(keyID), ':') + if index < 0 || len(keyID) <= index+1 { + return "", "" + } + return KeyAlgorithm(keyID[:index]), string(keyID[index+1:]) +} + +// Device contains the identity details of a device and some additional info. +type Device struct { + UserID UserID + DeviceID DeviceID + IdentityKey Curve25519 + SigningKey Ed25519 + + Trust TrustState + Deleted bool + Name string +} + +func (device *Device) Fingerprint() string { + return device.SigningKey.Fingerprint() +} + +type CrossSigningKey struct { + Key Ed25519 + First Ed25519 +} diff --git a/vendor/maunium.net/go/mautrix/id/matrixuri.go b/vendor/maunium.net/go/mautrix/id/matrixuri.go new file mode 100644 index 00000000..5ec403e9 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/matrixuri.go @@ -0,0 +1,293 @@ +// Copyright (c) 2021 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package id + +import ( + "errors" + "fmt" + "net/url" + "strings" +) + +// Errors that can happen when parsing matrix: URIs +var ( + ErrInvalidScheme = errors.New("matrix URI scheme must be exactly 'matrix'") + ErrInvalidPartCount = errors.New("matrix URIs must have exactly 2 or 4 segments") + ErrInvalidFirstSegment = errors.New("invalid identifier in first segment of matrix URI") + ErrEmptySecondSegment = errors.New("the second segment of the matrix URI must not be empty") + ErrInvalidThirdSegment = errors.New("invalid identifier in third segment of matrix URI") + ErrEmptyFourthSegment = errors.New("the fourth segment of the matrix URI must not be empty when the third segment is present") +) + +// Errors that can happen when parsing matrix.to URLs +var ( + ErrNotMatrixTo = errors.New("that URL is not a matrix.to URL") + ErrInvalidMatrixToPartCount = errors.New("matrix.to URLs must have exactly 1 or 2 segments") + ErrEmptyMatrixToPrimaryIdentifier = errors.New("the primary identifier in the matrix.to URL is empty") + ErrInvalidMatrixToPrimaryIdentifier = errors.New("the primary identifier in the matrix.to URL has an invalid sigil") + ErrInvalidMatrixToSecondaryIdentifier = errors.New("the secondary identifier in the matrix.to URL has an invalid sigil") +) + +var ErrNotMatrixToOrMatrixURI = errors.New("that URL is not a matrix.to URL nor matrix: URI") + +// MatrixURI contains the result of parsing a matrix: URI using ParseMatrixURI +type MatrixURI struct { + Sigil1 rune + Sigil2 rune + MXID1 string + MXID2 string + Via []string + Action string +} + +// SigilToPathSegment contains a mapping from Matrix identifier sigils to matrix: URI path segments. +var SigilToPathSegment = map[rune]string{ + '$': "e", + '#': "r", + '!': "roomid", + '@': "u", +} + +func (uri *MatrixURI) getQuery() url.Values { + q := make(url.Values) + if uri.Via != nil && len(uri.Via) > 0 { + q["via"] = uri.Via + } + if len(uri.Action) > 0 { + q.Set("action", uri.Action) + } + return q +} + +// String converts the parsed matrix: URI back into the string representation. +func (uri *MatrixURI) String() string { + parts := []string{ + SigilToPathSegment[uri.Sigil1], + url.PathEscape(uri.MXID1), + } + if uri.Sigil2 != 0 { + parts = append(parts, SigilToPathSegment[uri.Sigil2], url.PathEscape(uri.MXID2)) + } + return (&url.URL{ + Scheme: "matrix", + Opaque: strings.Join(parts, "/"), + RawQuery: uri.getQuery().Encode(), + }).String() +} + +// MatrixToURL converts to parsed matrix: URI into a matrix.to URL +func (uri *MatrixURI) MatrixToURL() string { + fragment := fmt.Sprintf("#/%s", url.PathEscape(uri.PrimaryIdentifier())) + if uri.Sigil2 != 0 { + fragment = fmt.Sprintf("%s/%s", fragment, url.PathEscape(uri.SecondaryIdentifier())) + } + query := uri.getQuery().Encode() + if len(query) > 0 { + fragment = fmt.Sprintf("%s?%s", fragment, query) + } + // It would be nice to use URL{...}.String() here, but figuring out the Fragment vs RawFragment stuff is a pain + return fmt.Sprintf("https://matrix.to/%s", fragment) +} + +// PrimaryIdentifier returns the first Matrix identifier in the URI. +// Currently room IDs, room aliases and user IDs can be in the primary identifier slot. +func (uri *MatrixURI) PrimaryIdentifier() string { + return fmt.Sprintf("%c%s", uri.Sigil1, uri.MXID1) +} + +// SecondaryIdentifier returns the second Matrix identifier in the URI. +// Currently only event IDs can be in the secondary identifier slot. +func (uri *MatrixURI) SecondaryIdentifier() string { + if uri.Sigil2 == 0 { + return "" + } + return fmt.Sprintf("%c%s", uri.Sigil2, uri.MXID2) +} + +// UserID returns the user ID from the URI if the primary identifier is a user ID. +func (uri *MatrixURI) UserID() UserID { + if uri.Sigil1 == '@' { + return UserID(uri.PrimaryIdentifier()) + } + return "" +} + +// RoomID returns the room ID from the URI if the primary identifier is a room ID. +func (uri *MatrixURI) RoomID() RoomID { + if uri.Sigil1 == '!' { + return RoomID(uri.PrimaryIdentifier()) + } + return "" +} + +// RoomAlias returns the room alias from the URI if the primary identifier is a room alias. +func (uri *MatrixURI) RoomAlias() RoomAlias { + if uri.Sigil1 == '#' { + return RoomAlias(uri.PrimaryIdentifier()) + } + return "" +} + +// EventID returns the event ID from the URI if the primary identifier is a room ID or alias and the secondary identifier is an event ID. +func (uri *MatrixURI) EventID() EventID { + if (uri.Sigil1 == '!' || uri.Sigil1 == '#') && uri.Sigil2 == '$' { + return EventID(uri.SecondaryIdentifier()) + } + return "" +} + +// ParseMatrixURIOrMatrixToURL parses the given matrix.to URL or matrix: URI into a unified representation. +func ParseMatrixURIOrMatrixToURL(uri string) (*MatrixURI, error) { + parsed, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse URI: %w", err) + } + if parsed.Scheme == "matrix" { + return ProcessMatrixURI(parsed) + } else if strings.HasSuffix(parsed.Hostname(), "matrix.to") { + return ProcessMatrixToURL(parsed) + } else { + return nil, ErrNotMatrixToOrMatrixURI + } +} + +// ParseMatrixURI implements the matrix: URI parsing algorithm. +// +// Currently specified in https://github.com/matrix-org/matrix-doc/blob/master/proposals/2312-matrix-uri.md#uri-parsing-algorithm +func ParseMatrixURI(uri string) (*MatrixURI, error) { + // Step 1: parse the URI according to RFC 3986 + parsed, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse URI: %w", err) + } + return ProcessMatrixURI(parsed) +} + +// ProcessMatrixURI implements steps 2-7 of the matrix: URI parsing algorithm +// (i.e. everything except parsing the URI itself, which is done with url.Parse or ParseMatrixURI) +func ProcessMatrixURI(uri *url.URL) (*MatrixURI, error) { + // Step 2: check that scheme is exactly `matrix` + if uri.Scheme != "matrix" { + return nil, ErrInvalidScheme + } + + // Step 3: split the path into segments separated by / + parts := strings.Split(uri.Opaque, "/") + + // Step 4: Check that the URI contains either 2 or 4 segments + if len(parts) != 2 && len(parts) != 4 { + return nil, ErrInvalidPartCount + } + + var parsed MatrixURI + + // Step 5: Construct the top-level Matrix identifier + // a: find the sigil from the first segment + switch parts[0] { + case "u", "user": + parsed.Sigil1 = '@' + case "r", "room": + parsed.Sigil1 = '#' + case "roomid": + parsed.Sigil1 = '!' + default: + return nil, fmt.Errorf("%w: '%s'", ErrInvalidFirstSegment, parts[0]) + } + // b: find the identifier from the second segment + if len(parts[1]) == 0 { + return nil, ErrEmptySecondSegment + } + parsed.MXID1 = parts[1] + + // Step 6: if the first part is a room and the URI has 4 segments, construct a second level identifier + if (parsed.Sigil1 == '!' || parsed.Sigil1 == '#') && len(parts) == 4 { + // a: find the sigil from the third segment + switch parts[2] { + case "e", "event": + parsed.Sigil2 = '$' + default: + return nil, fmt.Errorf("%w: '%s'", ErrInvalidThirdSegment, parts[0]) + } + + // b: find the identifier from the fourth segment + if len(parts[3]) == 0 { + return nil, ErrEmptyFourthSegment + } + parsed.MXID2 = parts[3] + } + + // Step 7: parse the query and extract via and action items + via, ok := uri.Query()["via"] + if ok && len(via) > 0 { + parsed.Via = via + } + action, ok := uri.Query()["action"] + if ok && len(action) > 0 { + parsed.Action = action[len(action)-1] + } + + return &parsed, nil +} + +// ParseMatrixToURL parses a matrix.to URL into the same container as ParseMatrixURI parses matrix: URIs. +func ParseMatrixToURL(uri string) (*MatrixURI, error) { + parsed, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse URL: %w", err) + } + return ProcessMatrixToURL(parsed) +} + +// ProcessMatrixToURL is the equivalent of ProcessMatrixURI for matrix.to URLs. +func ProcessMatrixToURL(uri *url.URL) (*MatrixURI, error) { + if !strings.HasSuffix(uri.Hostname(), "matrix.to") { + return nil, ErrNotMatrixTo + } + + initialSplit := strings.SplitN(uri.Fragment, "?", 2) + parts := strings.Split(initialSplit[0], "/") + if len(initialSplit) > 1 { + uri.RawQuery = initialSplit[1] + } + + if len(parts) < 2 || len(parts) > 3 { + return nil, ErrInvalidMatrixToPartCount + } + + if len(parts[1]) == 0 { + return nil, ErrEmptyMatrixToPrimaryIdentifier + } + + var parsed MatrixURI + + parsed.Sigil1 = rune(parts[1][0]) + parsed.MXID1 = parts[1][1:] + _, isKnown := SigilToPathSegment[parsed.Sigil1] + if !isKnown { + return nil, ErrInvalidMatrixToPrimaryIdentifier + } + + if len(parts) == 3 && len(parts[2]) > 0 { + parsed.Sigil2 = rune(parts[2][0]) + parsed.MXID2 = parts[2][1:] + _, isKnown = SigilToPathSegment[parsed.Sigil2] + if !isKnown { + return nil, ErrInvalidMatrixToSecondaryIdentifier + } + } + + via, ok := uri.Query()["via"] + if ok && len(via) > 0 { + parsed.Via = via + } + action, ok := uri.Query()["action"] + if ok && len(action) > 0 { + parsed.Action = action[len(action)-1] + } + + return &parsed, nil +} diff --git a/vendor/maunium.net/go/mautrix/id/opaque.go b/vendor/maunium.net/go/mautrix/id/opaque.go new file mode 100644 index 00000000..16863b95 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/opaque.go @@ -0,0 +1,83 @@ +// Copyright (c) 2020 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package id + +import ( + "fmt" +) + +// A RoomID is a string starting with ! that references a specific room. +// https://matrix.org/docs/spec/appendices#room-ids-and-event-ids +type RoomID string + +// A RoomAlias is a string starting with # that can be resolved into. +// https://matrix.org/docs/spec/appendices#room-aliases +type RoomAlias string + +func NewRoomAlias(localpart, server string) RoomAlias { + return RoomAlias(fmt.Sprintf("#%s:%s", localpart, server)) +} + +// An EventID is a string starting with $ that references a specific event. +// +// https://matrix.org/docs/spec/appendices#room-ids-and-event-ids +// https://matrix.org/docs/spec/rooms/v4#event-ids +type EventID string + +// A BatchID is a string identifying a batch of events being backfilled to a room. +// https://github.com/matrix-org/matrix-doc/pull/2716 +type BatchID string + +func (roomID RoomID) String() string { + return string(roomID) +} + +func (roomID RoomID) URI(via ...string) *MatrixURI { + return &MatrixURI{ + Sigil1: '!', + MXID1: string(roomID)[1:], + Via: via, + } +} + +func (roomID RoomID) EventURI(eventID EventID, via ...string) *MatrixURI { + return &MatrixURI{ + Sigil1: '!', + MXID1: string(roomID)[1:], + Sigil2: '$', + MXID2: string(eventID)[1:], + Via: via, + } +} + +func (roomAlias RoomAlias) String() string { + return string(roomAlias) +} + +func (roomAlias RoomAlias) URI() *MatrixURI { + return &MatrixURI{ + Sigil1: '#', + MXID1: string(roomAlias)[1:], + } +} + +func (roomAlias RoomAlias) EventURI(eventID EventID) *MatrixURI { + return &MatrixURI{ + Sigil1: '#', + MXID1: string(roomAlias)[1:], + Sigil2: '$', + MXID2: string(eventID)[1:], + } +} + +func (eventID EventID) String() string { + return string(eventID) +} + +func (batchID BatchID) String() string { + return string(batchID) +} diff --git a/vendor/maunium.net/go/mautrix/id/trust.go b/vendor/maunium.net/go/mautrix/id/trust.go new file mode 100644 index 00000000..04f6e36b --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/trust.go @@ -0,0 +1,87 @@ +// Copyright (c) 2022 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package id + +import ( + "fmt" + "strings" +) + +// TrustState determines how trusted a device is. +type TrustState int + +const ( + TrustStateBlacklisted TrustState = -100 + TrustStateUnset TrustState = 0 + TrustStateUnknownDevice TrustState = 10 + TrustStateForwarded TrustState = 20 + TrustStateCrossSignedUntrusted TrustState = 50 + TrustStateCrossSignedTOFU TrustState = 100 + TrustStateCrossSignedVerified TrustState = 200 + TrustStateVerified TrustState = 300 + TrustStateInvalid TrustState = (1 << 31) - 1 +) + +func (ts *TrustState) UnmarshalText(data []byte) error { + strData := string(data) + state := ParseTrustState(strData) + if state == TrustStateInvalid { + return fmt.Errorf("invalid trust state %q", strData) + } + *ts = state + return nil +} + +func (ts *TrustState) MarshalText() ([]byte, error) { + return []byte(ts.String()), nil +} + +func ParseTrustState(val string) TrustState { + switch strings.ToLower(val) { + case "blacklisted": + return TrustStateBlacklisted + case "unverified": + return TrustStateUnset + case "cross-signed-untrusted": + return TrustStateCrossSignedUntrusted + case "unknown-device": + return TrustStateUnknownDevice + case "forwarded": + return TrustStateForwarded + case "cross-signed-tofu", "cross-signed": + return TrustStateCrossSignedTOFU + case "cross-signed-verified", "cross-signed-trusted": + return TrustStateCrossSignedVerified + case "verified": + return TrustStateVerified + default: + return TrustStateInvalid + } +} + +func (ts TrustState) String() string { + switch ts { + case TrustStateBlacklisted: + return "blacklisted" + case TrustStateUnset: + return "unverified" + case TrustStateCrossSignedUntrusted: + return "cross-signed-untrusted" + case TrustStateUnknownDevice: + return "unknown-device" + case TrustStateForwarded: + return "forwarded" + case TrustStateCrossSignedTOFU: + return "cross-signed-tofu" + case TrustStateCrossSignedVerified: + return "cross-signed-verified" + case TrustStateVerified: + return "verified" + default: + return "invalid" + } +} diff --git a/vendor/maunium.net/go/mautrix/id/userid.go b/vendor/maunium.net/go/mautrix/id/userid.go new file mode 100644 index 00000000..0522b54c --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/userid.go @@ -0,0 +1,224 @@ +// Copyright (c) 2021 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package id + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "regexp" + "strings" +) + +// UserID represents a Matrix user ID. +// https://matrix.org/docs/spec/appendices#user-identifiers +type UserID string + +const UserIDMaxLength = 255 + +func NewUserID(localpart, homeserver string) UserID { + return UserID(fmt.Sprintf("@%s:%s", localpart, homeserver)) +} + +func NewEncodedUserID(localpart, homeserver string) UserID { + return NewUserID(EncodeUserLocalpart(localpart), homeserver) +} + +var ( + ErrInvalidUserID = errors.New("is not a valid user ID") + ErrNoncompliantLocalpart = errors.New("contains characters that are not allowed") + ErrUserIDTooLong = errors.New("the given user ID is longer than 255 characters") + ErrEmptyLocalpart = errors.New("empty localparts are not allowed") +) + +// Parse parses the user ID into the localpart and server name. +// +// Note that this only enforces very basic user ID formatting requirements: user IDs start with +// a @, and contain a : after the @. If you want to enforce localpart validity, see the +// ParseAndValidate and ValidateUserLocalpart functions. +func (userID UserID) Parse() (localpart, homeserver string, err error) { + if len(userID) == 0 || userID[0] != '@' || !strings.ContainsRune(string(userID), ':') { + // This error wrapping lets you use errors.Is() nicely even though the message contains the user ID + err = fmt.Errorf("'%s' %w", userID, ErrInvalidUserID) + return + } + parts := strings.SplitN(string(userID), ":", 2) + localpart, homeserver = strings.TrimPrefix(parts[0], "@"), parts[1] + return +} + +func (userID UserID) Localpart() string { + localpart, _, _ := userID.Parse() + return localpart +} + +func (userID UserID) Homeserver() string { + _, homeserver, _ := userID.Parse() + return homeserver +} + +// URI returns the user ID as a MatrixURI struct, which can then be stringified into a matrix: URI or a matrix.to URL. +// +// This does not parse or validate the user ID. Use the ParseAndValidate method if you want to ensure the user ID is valid first. +func (userID UserID) URI() *MatrixURI { + return &MatrixURI{ + Sigil1: '@', + MXID1: string(userID)[1:], + } +} + +var ValidLocalpartRegex = regexp.MustCompile("^[0-9a-z-.=_/]+$") + +// ValidateUserLocalpart validates a Matrix user ID localpart using the grammar +// in https://matrix.org/docs/spec/appendices#user-identifier +func ValidateUserLocalpart(localpart string) error { + if len(localpart) == 0 { + return ErrEmptyLocalpart + } else if !ValidLocalpartRegex.MatchString(localpart) { + return fmt.Errorf("'%s' %w", localpart, ErrNoncompliantLocalpart) + } + return nil +} + +// ParseAndValidate parses the user ID into the localpart and server name like Parse, +// and also validates that the localpart is allowed according to the user identifiers spec. +func (userID UserID) ParseAndValidate() (localpart, homeserver string, err error) { + localpart, homeserver, err = userID.Parse() + if err == nil { + err = ValidateUserLocalpart(localpart) + } + if err == nil && len(userID) > UserIDMaxLength { + err = ErrUserIDTooLong + } + return +} + +func (userID UserID) ParseAndDecode() (localpart, homeserver string, err error) { + localpart, homeserver, err = userID.ParseAndValidate() + if err == nil { + localpart, err = DecodeUserLocalpart(localpart) + } + return +} + +func (userID UserID) String() string { + return string(userID) +} + +const lowerhex = "0123456789abcdef" + +// encode the given byte using quoted-printable encoding (e.g "=2f") +// and writes it to the buffer +// See https://golang.org/src/mime/quotedprintable/writer.go +func encode(buf *bytes.Buffer, b byte) { + buf.WriteByte('=') + buf.WriteByte(lowerhex[b>>4]) + buf.WriteByte(lowerhex[b&0x0f]) +} + +// escape the given alpha character and writes it to the buffer +func escape(buf *bytes.Buffer, b byte) { + buf.WriteByte('_') + if b == '_' { + buf.WriteByte('_') // another _ + } else { + buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z + } +} + +func shouldEncode(b byte) bool { + return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z') +} + +func shouldEscape(b byte) bool { + return (b >= 'A' && b <= 'Z') || b == '_' +} + +func isValidByte(b byte) bool { + return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-' +} + +func isValidEscapedChar(b byte) bool { + return b == '_' || (b >= 'a' && b <= 'z') +} + +// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form. +// See https://spec.matrix.org/v1.2/appendices/#mapping-from-other-character-sets +// +// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z +// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges +// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs) +// and converted to lower-case hex with a leading "=". For example: +// +// Alph@Bet_50up => _alph=40_bet=5f50up +func EncodeUserLocalpart(str string) string { + strBytes := []byte(str) + var outputBuffer bytes.Buffer + for _, b := range strBytes { + if shouldEncode(b) { + encode(&outputBuffer, b) + } else if shouldEscape(b) { + escape(&outputBuffer, b) + } else { + outputBuffer.WriteByte(b) + } + } + return outputBuffer.String() +} + +// DecodeUserLocalpart decodes the given string back into the original input string. +// Returns an error if the given string is not a valid user ID localpart encoding. +// See https://spec.matrix.org/v1.2/appendices/#mapping-from-other-character-sets +// +// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For +// example: +// +// _alph=40_bet=5f50up => Alph@Bet_50up +// +// Returns an error if the input string contains characters outside the +// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has +// an invalid _ escaped byte (e.g. "_5"). +func DecodeUserLocalpart(str string) (string, error) { + strBytes := []byte(str) + var outputBuffer bytes.Buffer + for i := 0; i < len(strBytes); i++ { + b := strBytes[i] + if !isValidByte(b) { + return "", fmt.Errorf("Byte pos %d: Invalid byte", i) + } + + if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _ + if i+1 >= len(strBytes) { + return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i) + } + if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping + return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i) + } + if strBytes[i+1] == '_' { + outputBuffer.WriteByte('_') + } else { + outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z + } + i++ // skip next byte since we just handled it + } else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8 + if i+2 >= len(strBytes) { + return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i) + } + dst := make([]byte, 1) + _, err := hex.Decode(dst, strBytes[i+1:i+3]) + if err != nil { + return "", err + } + outputBuffer.WriteByte(dst[0]) + i += 2 // skip next 2 bytes since we just handled it + } else { // pass through + outputBuffer.WriteByte(b) + } + } + return outputBuffer.String(), nil +} |