From 23e785deb98b2673ea193b939d244b3d4b3e2dde Mon Sep 17 00:00:00 2001 From: Simon THOBY Date: Thu, 8 Jul 2021 19:55:08 +0200 Subject: [PATCH 1/3] Matrix: use per-room nicknames Fixes #1336 --- bridge/matrix/helpers.go | 62 +++++++++++++++++++++++----------------- bridge/matrix/matrix.go | 13 +++++---- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/bridge/matrix/helpers.go b/bridge/matrix/helpers.go index 03e448da81..71a15ed2d5 100644 --- a/bridge/matrix/helpers.go +++ b/bridge/matrix/helpers.go @@ -51,17 +51,19 @@ func interface2Struct(in interface{}, out interface{}) error { return json.Unmarshal(jsonObj, out) } -// getDisplayName retrieves the displayName for mxid, querying the homserver if the mxid is not in the cache. -func (b *Bmatrix) getDisplayName(mxid string) string { +// getDisplayName retrieves the displayName for mxid, querying the homeserver if the mxid is not in the cache. +func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { if b.GetBool("UseUserName") { return mxid[1:] } b.RLock() - if val, present := b.NicknameMap[mxid]; present { - b.RUnlock() + if channel, channelPresent := b.NicknameMap[channelID]; channelPresent { + if val, present := channel[mxid]; present { + b.RUnlock() - return val.displayName + return val.displayName + } } b.RUnlock() @@ -72,48 +74,54 @@ func (b *Bmatrix) getDisplayName(mxid string) string { } if err != nil { - return b.cacheDisplayName(mxid, mxid[1:]) + return b.cacheDisplayName(channelID, mxid, mxid[1:]) } - return b.cacheDisplayName(mxid, displayName.DisplayName) + return b.cacheDisplayName(channelID, mxid, displayName.DisplayName) } -// cacheDisplayName stores the mapping between a mxid and a display name, to be reused later without performing a query to the homserver. +// cacheDisplayName stores the mapping between a mxid and a display name, to be reused later without performing a query to the homeserver. // Note that old entries are cleaned when this function is called. -func (b *Bmatrix) cacheDisplayName(mxid string, displayName string) string { +func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName string) string { now := time.Now() - // scan to delete old entries, to stop memory usage from becoming too high with old entries. - // In addition, we also detect if another user have the same username, and if so, we append their mxids to their usernames to differentiate them. - toDelete := []string{} + // scan to delete old entries, to stop memory usage from becoming high with obsolete entries. + // In addition, we detect if another user have the same username, and if so, we append their mxids to their usernames to differentiate them. + toDelete := map[string]string{} conflict := false b.Lock() - for mxid, v := range b.NicknameMap { - // to prevent username reuse across matrix servers - or even on the same server, append - // the mxid to the username when there is a conflict - if v.displayName == displayName { - conflict = true - // TODO: it would be nice to be able to rename previous messages from this user. - // The current behavior is that only users with clashing usernames and *that have spoken since the bridge last started* will get their mxids shown, and I don't know if that's the expected behavior. - v.displayName = fmt.Sprintf("%s (%s)", displayName, mxid) - b.NicknameMap[mxid] = v - } + for channelIDIter, channelEntriesIter := range b.NicknameMap { + for mxidIter, NicknameCacheIter := range channelEntriesIter { + // to prevent username reuse across matrix rooms - or even inside the same room, if a user uses multiple servers - + // append the mxid to the username when there is a conflict + if NicknameCacheIter.displayName == displayName { + conflict = true + // TODO: it would be nice to be able to rename previous messages from this user. + // The current behavior is that only users with clashing usernames and *that have spoken since the bridge last started* will get their mxids shown, and I don't know if that's the expected behavior. + NicknameCacheIter.displayName = fmt.Sprintf("%s (%s)", displayName, mxidIter) + b.NicknameMap[channelIDIter][mxidIter] = NicknameCacheIter + } - if now.Sub(v.lastUpdated) > 10*time.Minute { - toDelete = append(toDelete, mxid) + if now.Sub(NicknameCacheIter.lastUpdated) > 10*time.Minute { + toDelete[channelIDIter] = mxidIter + } } } + for channelIDIter, mxidIter := range toDelete { + delete(b.NicknameMap[channelIDIter], mxidIter) + } + if conflict { displayName = fmt.Sprintf("%s (%s)", displayName, mxid) } - for _, v := range toDelete { - delete(b.NicknameMap, v) + if _, channelPresent := b.NicknameMap[channelID]; !channelPresent { + b.NicknameMap[channelID] = make(map[string]NicknameCacheEntry) } - b.NicknameMap[mxid] = NicknameCacheEntry{ + b.NicknameMap[channelID][mxid] = NicknameCacheEntry{ displayName: displayName, lastUpdated: now, } diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go index b50c9d8bf8..7541174d02 100644 --- a/bridge/matrix/matrix.go +++ b/bridge/matrix/matrix.go @@ -26,9 +26,10 @@ type NicknameCacheEntry struct { } type Bmatrix struct { - mc *matrix.Client - UserID string - NicknameMap map[string]NicknameCacheEntry + mc *matrix.Client + UserID string + // channelId -> mxid -> NickNameCacheEntry + NicknameMap map[string]map[string]NicknameCacheEntry RoomMap map[string]string rateMutex sync.RWMutex sync.RWMutex @@ -68,7 +69,7 @@ type EditedMessage struct { func New(cfg *bridge.Config) bridge.Bridger { b := &Bmatrix{Config: cfg} b.RoomMap = make(map[string]string) - b.NicknameMap = make(map[string]NicknameCacheEntry) + b.NicknameMap = make(map[string]map[string]NicknameCacheEntry) return b } @@ -342,7 +343,7 @@ func (b *Bmatrix) handleMemberChange(ev *matrix.Event) { // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information if ev.Content["membership"] == "join" { if dn, ok := ev.Content["displayname"].(string); ok { - b.cacheDisplayName(ev.Sender, dn) + b.cacheDisplayName(ev.RoomID, ev.Sender, dn) } } } @@ -360,7 +361,7 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { // Create our message rmsg := config.Message{ - Username: b.getDisplayName(ev.Sender), + Username: b.getDisplayName(ev.RoomID, ev.Sender), Channel: channel, Account: b.Account, UserID: ev.Sender, From 9818f322037321e97c8096bb4653b48668b65e49 Mon Sep 17 00:00:00 2001 From: Simon THOBY Date: Thu, 8 Jul 2021 23:35:37 +0200 Subject: [PATCH 2/3] WIP: mautrix --- bridge/matrix/helpers.go | 112 +- bridge/matrix/matrix.go | 468 +++--- go.mod | 3 +- go.sum | 49 +- vendor/github.com/btcsuite/btcutil/LICENSE | 16 + .../btcsuite/btcutil/base58/README.md | 34 + .../btcsuite/btcutil/base58/alphabet.go | 49 + .../btcsuite/btcutil/base58/base58.go | 75 + .../btcsuite/btcutil/base58/base58check.go | 52 + .../btcsuite/btcutil/base58/cov_report.sh | 17 + .../github.com/btcsuite/btcutil/base58/doc.go | 29 + .../github.com/matrix-org/gomatrix/.gitignore | 28 - .../matrix-org/gomatrix/.golangci.yml | 21 - .../matrix-org/gomatrix/.travis.yml | 7 - .../matrix-org/gomatrix/CHANGELOG.md | 1 - vendor/github.com/matrix-org/gomatrix/LICENSE | 201 --- .../github.com/matrix-org/gomatrix/README.md | 71 - .../github.com/matrix-org/gomatrix/client.go | 805 ---------- .../github.com/matrix-org/gomatrix/events.go | 157 -- .../github.com/matrix-org/gomatrix/filter.go | 90 -- vendor/github.com/matrix-org/gomatrix/go.mod | 3 - .../matrix-org/gomatrix/identifier.go | 69 - .../matrix-org/gomatrix/requests.go | 79 - .../matrix-org/gomatrix/responses.go | 210 --- vendor/github.com/matrix-org/gomatrix/room.go | 63 - .../github.com/matrix-org/gomatrix/store.go | 65 - vendor/github.com/matrix-org/gomatrix/sync.go | 168 --- vendor/github.com/matrix-org/gomatrix/tags.go | 26 - vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go | 77 + vendor/maunium.net/go/mautrix/.gitignore | 2 + vendor/maunium.net/go/mautrix/LICENSE | 374 +++++ vendor/maunium.net/go/mautrix/README.md | 24 + vendor/maunium.net/go/mautrix/client.go | 1344 +++++++++++++++++ .../mautrix/crypto/attachment/attachments.go | 171 +++ .../go/mautrix/crypto/utils/utils.go | 133 ++ vendor/maunium.net/go/mautrix/error.go | 144 ++ .../go/mautrix/event/accountdata.go | 45 + .../maunium.net/go/mautrix/event/content.go | 457 ++++++ .../go/mautrix/event/encryption.go | 141 ++ .../maunium.net/go/mautrix/event/ephemeral.go | 80 + vendor/maunium.net/go/mautrix/event/events.go | 54 + vendor/maunium.net/go/mautrix/event/member.go | 53 + .../maunium.net/go/mautrix/event/message.go | 231 +++ .../go/mautrix/event/powerlevels.go | 128 ++ .../maunium.net/go/mautrix/event/relations.go | 200 +++ vendor/maunium.net/go/mautrix/event/reply.go | 108 ++ vendor/maunium.net/go/mautrix/event/state.go | 110 ++ vendor/maunium.net/go/mautrix/event/type.go | 235 +++ .../go/mautrix/event/verification.go | 306 ++++ vendor/maunium.net/go/mautrix/event/voip.go | 116 ++ vendor/maunium.net/go/mautrix/filter.go | 93 ++ vendor/maunium.net/go/mautrix/go.mod | 19 + vendor/maunium.net/go/mautrix/go.sum | 77 + .../maunium.net/go/mautrix/id/contenturi.go | 133 ++ vendor/maunium.net/go/mautrix/id/crypto.go | 114 ++ vendor/maunium.net/go/mautrix/id/matrixuri.go | 293 ++++ vendor/maunium.net/go/mautrix/id/opaque.go | 41 + .../go/mautrix/id/userid.go} | 97 +- .../go/mautrix/pushrules/action.go | 124 ++ .../go/mautrix/pushrules/condition.go | 149 ++ .../maunium.net/go/mautrix/pushrules/doc.go | 2 + .../go/mautrix/pushrules/glob/LICENSE | 22 + .../go/mautrix/pushrules/glob/README.md | 28 + .../go/mautrix/pushrules/glob/glob.go | 108 ++ .../go/mautrix/pushrules/pushrules.go | 37 + .../maunium.net/go/mautrix/pushrules/rule.go | 154 ++ .../go/mautrix/pushrules/ruleset.go | 88 ++ vendor/maunium.net/go/mautrix/requests.go | 302 ++++ vendor/maunium.net/go/mautrix/responses.go | 291 ++++ vendor/maunium.net/go/mautrix/room.go | 52 + vendor/maunium.net/go/mautrix/store.go | 162 ++ vendor/maunium.net/go/mautrix/sync.go | 283 ++++ vendor/maunium.net/go/mautrix/version.go | 5 + vendor/modules.txt | 13 +- 74 files changed, 7731 insertions(+), 2457 deletions(-) create mode 100644 vendor/github.com/btcsuite/btcutil/LICENSE create mode 100644 vendor/github.com/btcsuite/btcutil/base58/README.md create mode 100644 vendor/github.com/btcsuite/btcutil/base58/alphabet.go create mode 100644 vendor/github.com/btcsuite/btcutil/base58/base58.go create mode 100644 vendor/github.com/btcsuite/btcutil/base58/base58check.go create mode 100644 vendor/github.com/btcsuite/btcutil/base58/cov_report.sh create mode 100644 vendor/github.com/btcsuite/btcutil/base58/doc.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/.gitignore delete mode 100644 vendor/github.com/matrix-org/gomatrix/.golangci.yml delete mode 100644 vendor/github.com/matrix-org/gomatrix/.travis.yml delete mode 100644 vendor/github.com/matrix-org/gomatrix/CHANGELOG.md delete mode 100644 vendor/github.com/matrix-org/gomatrix/LICENSE delete mode 100644 vendor/github.com/matrix-org/gomatrix/README.md delete mode 100644 vendor/github.com/matrix-org/gomatrix/client.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/events.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/filter.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/go.mod delete mode 100644 vendor/github.com/matrix-org/gomatrix/identifier.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/requests.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/responses.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/room.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/store.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/sync.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/tags.go create mode 100644 vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go create mode 100644 vendor/maunium.net/go/mautrix/.gitignore create mode 100644 vendor/maunium.net/go/mautrix/LICENSE create mode 100644 vendor/maunium.net/go/mautrix/README.md create mode 100644 vendor/maunium.net/go/mautrix/client.go create mode 100644 vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go create mode 100644 vendor/maunium.net/go/mautrix/crypto/utils/utils.go create mode 100644 vendor/maunium.net/go/mautrix/error.go create mode 100644 vendor/maunium.net/go/mautrix/event/accountdata.go create mode 100644 vendor/maunium.net/go/mautrix/event/content.go create mode 100644 vendor/maunium.net/go/mautrix/event/encryption.go create mode 100644 vendor/maunium.net/go/mautrix/event/ephemeral.go create mode 100644 vendor/maunium.net/go/mautrix/event/events.go create mode 100644 vendor/maunium.net/go/mautrix/event/member.go create mode 100644 vendor/maunium.net/go/mautrix/event/message.go create mode 100644 vendor/maunium.net/go/mautrix/event/powerlevels.go create mode 100644 vendor/maunium.net/go/mautrix/event/relations.go create mode 100644 vendor/maunium.net/go/mautrix/event/reply.go create mode 100644 vendor/maunium.net/go/mautrix/event/state.go create mode 100644 vendor/maunium.net/go/mautrix/event/type.go create mode 100644 vendor/maunium.net/go/mautrix/event/verification.go create mode 100644 vendor/maunium.net/go/mautrix/event/voip.go create mode 100644 vendor/maunium.net/go/mautrix/filter.go create mode 100644 vendor/maunium.net/go/mautrix/go.mod create mode 100644 vendor/maunium.net/go/mautrix/go.sum create mode 100644 vendor/maunium.net/go/mautrix/id/contenturi.go create mode 100644 vendor/maunium.net/go/mautrix/id/crypto.go create mode 100644 vendor/maunium.net/go/mautrix/id/matrixuri.go create mode 100644 vendor/maunium.net/go/mautrix/id/opaque.go rename vendor/{github.com/matrix-org/gomatrix/userids.go => maunium.net/go/mautrix/id/userid.go} (57%) create mode 100644 vendor/maunium.net/go/mautrix/pushrules/action.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/condition.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/doc.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE create mode 100644 vendor/maunium.net/go/mautrix/pushrules/glob/README.md create mode 100644 vendor/maunium.net/go/mautrix/pushrules/glob/glob.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/pushrules.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/rule.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/ruleset.go create mode 100644 vendor/maunium.net/go/mautrix/requests.go create mode 100644 vendor/maunium.net/go/mautrix/responses.go create mode 100644 vendor/maunium.net/go/mautrix/room.go create mode 100644 vendor/maunium.net/go/mautrix/store.go create mode 100644 vendor/maunium.net/go/mautrix/sync.go create mode 100644 vendor/maunium.net/go/mautrix/version.go diff --git a/bridge/matrix/helpers.go b/bridge/matrix/helpers.go index 71a15ed2d5..e0ed4bae07 100644 --- a/bridge/matrix/helpers.go +++ b/bridge/matrix/helpers.go @@ -1,14 +1,13 @@ package bmatrix import ( - "encoding/json" "errors" "fmt" "html" - "strings" "time" - matrix "github.com/matrix-org/gomatrix" + matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/id" ) func newMatrixUsername(username string) *matrixUsername { @@ -28,7 +27,7 @@ func newMatrixUsername(username string) *matrixUsername { } // getRoomID retrieves a matching room ID from the channel name. -func (b *Bmatrix) getRoomID(channel string) string { +func (b *Bmatrix) getRoomID(channel string) id.RoomID { b.RLock() defer b.RUnlock() for ID, name := range b.RoomMap { @@ -40,21 +39,10 @@ func (b *Bmatrix) getRoomID(channel string) string { return "" } -// interface2Struct marshals and immediately unmarshals an interface. -// Useful for converting map[string]interface{} to a struct. -func interface2Struct(in interface{}, out interface{}) error { - jsonObj, err := json.Marshal(in) - if err != nil { - return err //nolint:wrapcheck - } - - return json.Unmarshal(jsonObj, out) -} - // getDisplayName retrieves the displayName for mxid, querying the homeserver if the mxid is not in the cache. -func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { +func (b *Bmatrix) getDisplayName(channelID id.RoomID, mxid id.UserID) string { if b.GetBool("UseUserName") { - return mxid[1:] + return string(mxid)[1:] } b.RLock() @@ -74,7 +62,7 @@ func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { } if err != nil { - return b.cacheDisplayName(channelID, mxid, mxid[1:]) + return b.cacheDisplayName(channelID, mxid, string(mxid)[1:]) } return b.cacheDisplayName(channelID, mxid, displayName.DisplayName) @@ -82,12 +70,12 @@ func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { // cacheDisplayName stores the mapping between a mxid and a display name, to be reused later without performing a query to the homeserver. // Note that old entries are cleaned when this function is called. -func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName string) string { +func (b *Bmatrix) cacheDisplayName(channelID id.RoomID, mxid id.UserID, displayName string) string { now := time.Now() // scan to delete old entries, to stop memory usage from becoming high with obsolete entries. // In addition, we detect if another user have the same username, and if so, we append their mxids to their usernames to differentiate them. - toDelete := map[string]string{} + toDelete := map[id.RoomID]id.UserID{} conflict := false b.Lock() @@ -118,7 +106,7 @@ func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName st } if _, channelPresent := b.NicknameMap[channelID]; !channelPresent { - b.NicknameMap[channelID] = make(map[string]NicknameCacheEntry) + b.NicknameMap[channelID] = make(map[id.UserID]NicknameCacheEntry) } b.NicknameMap[channelID][mxid] = NicknameCacheEntry{ @@ -130,77 +118,43 @@ func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName st return displayName } -// handleError converts errors into httpError. -//nolint:exhaustivestruct -func handleError(err error) *httpError { - var mErr matrix.HTTPError - if !errors.As(err, &mErr) { - return &httpError{ - Err: "not a HTTPError", - } - } - - var httpErr httpError - - if err := json.Unmarshal(mErr.Contents, &httpErr); err != nil { - return &httpError{ - Err: "unmarshal failed", - } - } - - return &httpErr -} - -func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool { - // Skip empty messages - if content["msgtype"] == nil { - return false - } - - // Only allow image,video or file msgtypes - if !(content["msgtype"].(string) == "m.image" || - content["msgtype"].(string) == "m.video" || - content["msgtype"].(string) == "m.file") { - return false - } - - return true -} - // getAvatarURL returns the avatar URL of the specified sender. -func (b *Bmatrix) getAvatarURL(sender string) string { - urlPath := b.mc.BuildURL("profile", sender, "avatar_url") - - s := struct { - AvatarURL string `json:"avatar_url"` - }{} - - err := b.mc.MakeRequest("GET", urlPath, nil, &s) +func (b *Bmatrix) getAvatarURL(sender id.UserID) string { + url, err := b.mc.GetAvatarURL(sender) if err != nil { - b.Log.Errorf("getAvatarURL failed: %s", err) - + b.Log.Errorf("Couldn't retrieve the URL of the avatar for MXID %s", sender) return "" } - url := strings.ReplaceAll(s.AvatarURL, "mxc://", b.GetString("Server")+"/_matrix/media/r0/thumbnail/") - if url != "" { - url += "?width=37&height=37&method=crop" - } - - return url + return url.String() } // handleRatelimit handles the ratelimit errors and return if we're ratelimited and the amount of time to sleep func (b *Bmatrix) handleRatelimit(err error) (time.Duration, bool) { - httpErr := handleError(err) - if httpErr.Errcode != "M_LIMIT_EXCEEDED" { + var mErr matrix.HTTPError + if !errors.As(err, &mErr) { + b.Log.Errorf("Received a non-HTTPError, don't know what to make of it:\n%#v", err) return 0, false } - b.Log.Debugf("ratelimited: %s", httpErr.Err) - b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before retrying", httpErr.RetryAfterMs/1000) + if mErr.RespError.ErrCode != "M_LIMIT_EXCEEDED" { + return 0, false + } + + b.Log.Debugf("ratelimited: %s", mErr.RespError.Err) + + // fallback to a one-second delay + retryDelayMs := 1000 + + if retryDelayString, present := mErr.RespError.ExtraData["retry_after_ms"]; present { + if retryDelayInt, correct := retryDelayString.(int); correct && retryDelayInt > retryDelayMs { + retryDelayMs = retryDelayInt + } + } + + b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before retrying", retryDelayMs/1000) - return time.Duration(httpErr.RetryAfterMs) * time.Millisecond, true + return time.Duration(retryDelayMs) * time.Millisecond, true } // retry function will check if we're ratelimited and retries again when backoff time expired diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go index 7541174d02..4bf9cb507d 100644 --- a/bridge/matrix/matrix.go +++ b/bridge/matrix/matrix.go @@ -1,3 +1,4 @@ +//nolint: exhaustivestruct package bmatrix import ( @@ -9,10 +10,13 @@ import ( "sync" "time" + matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" - matrix "github.com/matrix-org/gomatrix" ) var ( @@ -26,20 +30,15 @@ type NicknameCacheEntry struct { } type Bmatrix struct { - mc *matrix.Client - UserID string - // channelId -> mxid -> NickNameCacheEntry - NicknameMap map[string]map[string]NicknameCacheEntry - RoomMap map[string]string + mc *matrix.Client + UserID id.UserID + NicknameMap map[id.RoomID]map[id.UserID]NicknameCacheEntry + RoomMap map[id.RoomID]string rateMutex sync.RWMutex sync.RWMutex *bridge.Config -} - -type httpError struct { - Errcode string `json:"errcode"` - Err string `json:"error"` - RetryAfterMs int `json:"retry_after_ms"` + stop chan int + stopAck chan int } type matrixUsername struct { @@ -47,29 +46,12 @@ type matrixUsername struct { formatted string } -// SubTextMessage represents the new content of the message in edit messages. -type SubTextMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` -} - -// MessageRelation explains how the current message relates to a previous message. -// Notably used for message edits. -type MessageRelation struct { - EventID string `json:"event_id"` - Type string `json:"rel_type"` -} - -type EditedMessage struct { - NewContent SubTextMessage `json:"m.new_content"` - RelatedTo MessageRelation `json:"m.relates_to"` - matrix.TextMessage -} - func New(cfg *bridge.Config) bridge.Bridger { b := &Bmatrix{Config: cfg} - b.RoomMap = make(map[string]string) - b.NicknameMap = make(map[string]map[string]NicknameCacheEntry) + b.RoomMap = make(map[id.RoomID]string) + b.NicknameMap = make(map[id.RoomID]map[id.UserID]NicknameCacheEntry) + b.stop = make(chan int, 1) + b.stopAck = make(chan int, 1) return b } @@ -77,13 +59,13 @@ func (b *Bmatrix) Connect() error { var err error b.Log.Infof("Connecting %s", b.GetString("Server")) if b.GetString("MxID") != "" && b.GetString("Token") != "" { + b.UserID = id.UserID(b.GetString("MxID")) b.mc, err = matrix.NewClient( - b.GetString("Server"), b.GetString("MxID"), b.GetString("Token"), + b.GetString("Server"), b.UserID, b.GetString("Token"), ) if err != nil { return err } - b.UserID = b.GetString("MxID") b.Log.Info("Using existing Matrix credentials") } else { b.mc, err = matrix.NewClient(b.GetString("Server"), "", "") @@ -91,15 +73,14 @@ func (b *Bmatrix) Connect() error { return err } resp, err := b.mc.Login(&matrix.ReqLogin{ - Type: "m.login.password", - User: b.GetString("Login"), - Password: b.GetString("Password"), - Identifier: matrix.NewUserIdentifier(b.GetString("Login")), + Type: matrix.AuthTypePassword, + Password: b.GetString("Password"), + Identifier: matrix.UserIdentifier{Type: matrix.IdentifierTypeUser, User: b.GetString("Login")}, + StoreCredentials: true, }) if err != nil { return err } - b.mc.SetCredentials(resp.UserID, resp.AccessToken) b.UserID = resp.UserID b.Log.Info("Connection succeeded") } @@ -108,6 +89,13 @@ func (b *Bmatrix) Connect() error { } func (b *Bmatrix) Disconnect() error { + // tell the Sync() loop to exit + b.stop <- 0 + b.mc.StopSync() + + // wait for the syncer to terminate + <-b.stopAck + return nil } @@ -136,26 +124,13 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Make a action /me of the message if msg.Event == config.EventUserAction { - m := matrix.TextMessage{ - MsgType: "m.emote", + m := event.MessageEventContent{ + MsgType: event.MsgEmote, Body: username.plain + msg.Text, FormattedBody: username.formatted + msg.Text, } - msgID := "" - - err := b.retry(func() error { - resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m) - if err != nil { - return err - } - - msgID = resp.EventID - - return err - }) - - return msgID, err + return b.sendEventWithRetries(channel, event.EventMessage, m) } // Delete message @@ -167,12 +142,12 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { msgID := "" err := b.retry(func() error { - resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{}) + resp, err := b.mc.RedactEvent(channel, id.EventID(msg.ID), matrix.ReqRedact{}) if err != nil { return err } - msgID = resp.EventID + msgID = string(resp.EventID) return err }) @@ -183,13 +158,12 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Upload a file if it exists if msg.Extra != nil { for _, rmsg := range helper.HandleExtra(&msg, b.General) { - rmsg := rmsg - - err := b.retry(func() error { - _, err := b.mc.SendText(channel, rmsg.Username+rmsg.Text) + m := event.MessageEventContent{ + MsgType: event.MsgText, + Body: rmsg.Username + rmsg.Text, + } - return err - }) + _, err := b.sendEventWithRetries(channel, event.EventMessage, m) if err != nil { b.Log.Errorf("sendText failed: %s", err) } @@ -202,89 +176,84 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Edit message if we have an ID if msg.ID != "" { - rmsg := EditedMessage{TextMessage: matrix.TextMessage{ + rmsg := event.MessageEventContent{ Body: username.plain + msg.Text, - MsgType: "m.text", - }} + MsgType: event.MsgText, + } if b.GetBool("HTMLDisable") { - rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text + rmsg.FormattedBody = username.formatted + "* " + msg.Text } else { - rmsg.Format = "org.matrix.custom.html" - rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text) + rmsg.Format = event.FormatHTML + rmsg.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text) } - rmsg.NewContent = SubTextMessage{ - Body: rmsg.TextMessage.Body, - MsgType: "m.text", + rmsg.NewContent = &event.MessageEventContent{ + Body: rmsg.Body, + MsgType: event.MsgText, } - rmsg.RelatedTo = MessageRelation{ - EventID: msg.ID, - Type: "m.replace", + rmsg.RelatesTo = &event.RelatesTo{ + EventID: id.EventID(msg.ID), + Type: event.RelReplace, } - err := b.retry(func() error { - _, err := b.mc.SendMessageEvent(channel, "m.room.message", rmsg) - - return err - }) - if err != nil { - return "", err - } + return b.sendEventWithRetries(channel, event.EventMessage, rmsg) + } - return msg.ID, nil + m := event.MessageEventContent{ + Body: username.plain + msg.Text, + FormattedBody: username.formatted + msg.Text, } // Use notices to send join/leave events if msg.Event == config.EventJoinLeave { - m := matrix.TextMessage{ - MsgType: "m.notice", - Body: username.plain + msg.Text, - FormattedBody: username.formatted + msg.Text, + m.MsgType = event.MsgNotice + } else { + m.MsgType = event.MsgText + if b.GetBool("HTMLDisable") { + m.FormattedBody = "" + } else { + m.FormattedBody = username.formatted + helper.ParseMarkdown(msg.Text) } + } - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m) + return b.sendEventWithRetries(channel, event.EventMessage, m) +} - return err - }) - if err != nil { - return "", err - } +func (b *Bmatrix) handlematrix() { + syncer, ok := b.mc.Syncer.(*matrix.DefaultSyncer) + if !ok { + b.Log.Errorln("couldn't convert the Syncer object to a DefaultSyncer structure, the matrix bridge won't work") - return resp.EventID, err + return } - if b.GetBool("HTMLDisable") { - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendText(channel, username.plain+msg.Text) - - return err - }) - if err != nil { - return "", err + syncer.OnEventType(event.EventRedaction, b.handleEvent) + syncer.OnEventType(event.EventMessage, b.handleEvent) + syncer.OnEventType(event.StateMember, b.handleMemberChange) + go func() { + for { + select { + case <-b.stop: + b.stopAck <- 1 + + return + default: + if err := b.mc.Sync(); err != nil { + b.Log.Println("Sync() returned ", err) + } + } } + }() +} - return resp.EventID, err - } - - // Post normal message with HTML support (eg riot.im) +//nolint: wrapcheck +func (b *Bmatrix) sendEventWithRetries(channel id.RoomID, eventType event.Type, eventData interface{}) (string, error) { var ( resp *matrix.RespSendEvent err error ) err = b.retry(func() error { - resp, err = b.mc.SendFormattedText(channel, username.plain+msg.Text, - username.formatted+helper.ParseMarkdown(msg.Text)) + resp, err = b.mc.SendMessageEvent(channel, eventType, eventData) return err }) @@ -292,63 +261,57 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { return "", err } - return resp.EventID, err + return string(resp.EventID), err } -func (b *Bmatrix) handlematrix() { - syncer := b.mc.Syncer.(*matrix.DefaultSyncer) - syncer.OnEventType("m.room.redaction", b.handleEvent) - syncer.OnEventType("m.room.message", b.handleEvent) - syncer.OnEventType("m.room.member", b.handleMemberChange) - go func() { - for { - if err := b.mc.Sync(); err != nil { - b.Log.Println("Sync() returned ", err) - } - } - }() -} +func (b *Bmatrix) handleMemberChange(source matrix.EventSource, ev *event.Event) { + member := ev.Content.AsMember() + if member == nil { + b.Log.Errorf("Couldn't process a member event:\n%#v", ev) -func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool { - relationInterface, present := ev.Content["m.relates_to"] - newContentInterface, present2 := ev.Content["m.new_content"] - if !(present && present2) { - return false + return } + // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information + if member.Membership == event.MembershipJoin { + b.cacheDisplayName(ev.RoomID, ev.Sender, member.Displayname) + } +} - var relation MessageRelation - if err := interface2Struct(relationInterface, &relation); err != nil { - b.Log.Warnf("Couldn't parse 'm.relates_to' object with value %#v", relationInterface) - return false +func (b *Bmatrix) handleMessage(rmsg config.Message, msg event.MessageEventContent, ev *event.Event) { + rmsg.Text = msg.Body + + // Do we have a /me action + if msg.MsgType == event.MsgEmote { + rmsg.Event = config.EventUserAction } - var newContent SubTextMessage - if err := interface2Struct(newContentInterface, &newContent); err != nil { - b.Log.Warnf("Couldn't parse 'm.new_content' object with value %#v", newContentInterface) - return false + // Is it an edit? + if msg.RelatesTo != nil && msg.NewContent != nil && msg.RelatesTo.Type == event.RelReplace { + rmsg.ID = string(msg.RelatesTo.EventID) + rmsg.Text = msg.NewContent.Body + b.Remote <- rmsg + + return } - if relation.Type != "m.replace" { - return false + // Do we have attachments (we only allow image,video or file msgtypes) + if msg.MsgType == event.MsgImage || msg.MsgType == event.MsgVideo || msg.MsgType == event.MsgFile { + err := b.handleDownloadFile(&rmsg, msg) + if err != nil { + b.Log.Errorf("download failed: %#v", err) + } } - rmsg.ID = relation.EventID - rmsg.Text = newContent.Body + b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account) b.Remote <- rmsg - return true -} - -func (b *Bmatrix) handleMemberChange(ev *matrix.Event) { - // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information - if ev.Content["membership"] == "join" { - if dn, ok := ev.Content["displayname"].(string); ok { - b.cacheDisplayName(ev.RoomID, ev.Sender, dn) - } + // not crucial, so no ratelimit check here + if err := b.mc.MarkRead(ev.RoomID, ev.ID); err != nil { + b.Log.Errorf("couldn't mark message as read %s", err.Error()) } } -func (b *Bmatrix) handleEvent(ev *matrix.Event) { +func (b *Bmatrix) handleEvent(source matrix.EventSource, ev *event.Event) { b.Log.Debugf("== Receiving event: %#v", ev) if ev.Sender != b.UserID { b.RLock() @@ -364,8 +327,8 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { Username: b.getDisplayName(ev.RoomID, ev.Sender), Channel: channel, Account: b.Account, - UserID: ev.Sender, - ID: ev.ID, + UserID: string(ev.Sender), + ID: string(ev.ID), Avatar: b.getAvatarURL(ev.Sender), } @@ -376,95 +339,52 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { } // Delete event - if ev.Type == "m.room.redaction" { + if ev.Type == event.EventRedaction { rmsg.Event = config.EventMsgDelete - rmsg.ID = ev.Redacts + rmsg.ID = string(ev.Redacts) rmsg.Text = config.EventMsgDelete b.Remote <- rmsg return } - // Text must be a string - if rmsg.Text, ok = ev.Content["body"].(string); !ok { - b.Log.Errorf("Content[body] is not a string: %T\n%#v", - ev.Content["body"], ev.Content) - return - } - - // Do we have a /me action - if ev.Content["msgtype"].(string) == "m.emote" { - rmsg.Event = config.EventUserAction - } + msg := ev.Content.AsMessage() + if msg == nil { + b.Log.Errorf("matterbridge don't support this event type: %s", ev.Type.Type) + b.Log.Debugf("Full event: %#v", ev) - // Is it an edit? - if b.handleEdit(ev, rmsg) { return } - // Do we have attachments - if b.containsAttachment(ev.Content) { - err := b.handleDownloadFile(&rmsg, ev.Content) - if err != nil { - b.Log.Errorf("download failed: %#v", err) - } - } - - b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account) - b.Remote <- rmsg - - // not crucial, so no ratelimit check here - if err := b.mc.MarkRead(ev.RoomID, ev.ID); err != nil { - b.Log.Errorf("couldn't mark message as read %s", err.Error()) - } + b.handleMessage(rmsg, *msg, ev) } } // handleDownloadFile handles file download -func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]interface{}) error { - var ( - ok bool - url, name, msgtype, mtype string - info map[string]interface{} - size float64 - ) - +func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, msg event.MessageEventContent) error { rmsg.Extra = make(map[string][]interface{}) - if url, ok = content["url"].(string); !ok { - return fmt.Errorf("url isn't a %T", url) + if msg.URL == "" || msg.Info == nil { + b.Log.Error("couldn't download a file with no URL or no file informations (invalid event ?)") + b.Log.Debugf("Full Message content:\n%#v", msg) } - url = strings.Replace(url, "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/", -1) - if info, ok = content["info"].(map[string]interface{}); !ok { - return fmt.Errorf("info isn't a %T", info) - } - if size, ok = info["size"].(float64); !ok { - return fmt.Errorf("size isn't a %T", size) - } - if name, ok = content["body"].(string); !ok { - return fmt.Errorf("name isn't a %T", name) - } - if msgtype, ok = content["msgtype"].(string); !ok { - return fmt.Errorf("msgtype isn't a %T", msgtype) - } - if mtype, ok = info["mimetype"].(string); !ok { - return fmt.Errorf("mtype isn't a %T", mtype) - } + url := strings.ReplaceAll(string(msg.URL), "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/") + filename := msg.Body // check if we have an image uploaded without extension - if !strings.Contains(name, ".") { - if msgtype == "m.image" { - mext, _ := mime.ExtensionsByType(mtype) - if len(mext) > 0 { - name += mext[0] - } + if !strings.Contains(filename, ".") { + mext, _ := mime.ExtensionsByType(msg.Info.MimeType) + if len(mext) > 0 { + filename += mext[0] } else { - // just a default .png extension if we don't have mime info - name += ".png" + if msg.MsgType == event.MsgImage { + // just a default .png extension if we don't have mime info + filename += ".png" + } } } // check if the size is ok - err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General) + err := helper.HandleDownloadSize(b.Log, rmsg, filename, int64(msg.Info.Size), b.General) if err != nil { return err } @@ -474,12 +394,12 @@ func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]in return fmt.Errorf("download %s failed %#v", url, err) } // add the downloaded data to the message - helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General) + helper.HandleDownloadData(b.Log, rmsg, filename, "", url, data, b.General) return nil } // handleUploadFiles handles native upload of files. -func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string, error) { +func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel id.RoomID) (string, error) { for _, f := range msg.Extra["file"] { if fi, ok := f.(config.FileInfo); ok { b.handleUploadFile(msg, channel, &fi) @@ -489,17 +409,21 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string } // handleUploadFile handles native upload of a file. -func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) { +//nolint: funlen +func (b *Bmatrix) handleUploadFile(msg *config.Message, channel id.RoomID, fi *config.FileInfo) { username := newMatrixUsername(msg.Username) content := bytes.NewReader(*fi.Data) sp := strings.Split(fi.Name, ".") mtype := mime.TypeByExtension("." + sp[len(sp)-1]) + // image and video uploads send no username, we have to do this ourself here #715 - err := b.retry(func() error { - _, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment) + m := event.MessageEventContent{ + MsgType: event.MsgText, + Body: username.plain + fi.Comment, + FormattedBody: username.formatted + fi.Comment, + } - return err - }) + _, err := b.sendEventWithRetries(channel, event.EventMessage, m) if err != nil { b.Log.Errorf("file comment failed: %#v", err) } @@ -507,9 +431,14 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf b.Log.Debugf("uploading file: %s %s", fi.Name, mtype) var res *matrix.RespMediaUpload + req := matrix.ReqUploadMedia{ + Content: content, + ContentType: mtype, + ContentLength: fi.Size, + } err = b.retry(func() error { - res, err = b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data))) + res, err = b.mc.UploadMedia(req) return err }) @@ -519,63 +448,42 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf return } + b.Log.Debugf("result: %#v", res) + + m = event.MessageEventContent{ + Body: fi.Name, + URL: res.ContentURI.CUString(), + } + switch { case strings.Contains(mtype, "video"): b.Log.Debugf("sendVideo %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI) - return err - }) - if err != nil { - b.Log.Errorf("sendVideo failed: %#v", err) - } + m.MsgType = event.MsgVideo case strings.Contains(mtype, "image"): b.Log.Debugf("sendImage %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendImage(channel, fi.Name, res.ContentURI) - return err - }) - if err != nil { - b.Log.Errorf("sendImage failed: %#v", err) - } + m.MsgType = event.MsgImage case strings.Contains(mtype, "audio"): b.Log.Debugf("sendAudio %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{ - MsgType: "m.audio", - Body: fi.Name, - URL: res.ContentURI, - Info: matrix.AudioInfo{ - Mimetype: mtype, - Size: uint(len(*fi.Data)), - }, - }) - return err - }) - if err != nil { - b.Log.Errorf("sendAudio failed: %#v", err) + m.MsgType = event.MsgAudio + m.Info = &event.FileInfo{ + MimeType: mtype, + Size: len(*fi.Data), } default: b.Log.Debugf("sendFile %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{ - MsgType: "m.file", - Body: fi.Name, - URL: res.ContentURI, - Info: matrix.FileInfo{ - Mimetype: mtype, - Size: uint(len(*fi.Data)), - }, - }) - return err - }) - if err != nil { - b.Log.Errorf("sendFile failed: %#v", err) + m.MsgType = event.MsgFile + m.Info = &event.FileInfo{ + MimeType: mtype, + Size: len(*fi.Data), } } - b.Log.Debugf("result: %#v", res) + + _, err = b.sendEventWithRetries(channel, event.EventMessage, m) + if err != nil { + b.Log.Errorf("sending the message referencing the uploaded file failed: %#v", err) + } } diff --git a/go.mod b/go.mod index 69914eee9d..b8b0d372a2 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/kyokomi/emoji/v2 v2.2.8 github.com/labstack/echo/v4 v4.3.0 github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b - github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 + github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 // indirect github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050 @@ -55,6 +55,7 @@ require ( gomod.garykim.dev/nc-talk v0.3.0 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 layeh.com/gumble v0.0.0-20200818122324-146f9205029b + maunium.net/go/mautrix v0.9.14 ) go 1.15 diff --git a/go.sum b/go.sum index 63f18f9c5f..6cc1278caa 100644 --- a/go.sum +++ b/go.sum @@ -56,7 +56,6 @@ github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= @@ -94,6 +93,7 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrU github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/advancedlogic/GoOse v0.0.0-20191112112754-e742535969c1/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= github.com/advancedlogic/GoOse v0.0.0-20200830213114-1225d531e0ad/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -146,6 +146,16 @@ github.com/blevesearch/zap/v14 v14.0.3/go.mod h1:oObAhcDHw7p1ahiTCqhRkdxdl7UA8qp github.com/blevesearch/zap/v15 v15.0.1/go.mod h1:ho0frqAex2ktT9cYFAxQpoQXsxb/KEfdjpx4s49rf/M= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -192,6 +202,7 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/d5/tengo/v2 v2.7.0 h1:oAGQ+gcas0/T0bdqvNxfKzjOJhpQRwceWIF5gldB3aM= github.com/d5/tengo/v2 v2.7.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -284,7 +295,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81 h1:FdZThbRF0R+2qgyBl3KCVNWWBmKm68E+stT3rnQ02Ww= github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81/go.mod h1:lDm2E64X4OjFdBUA4hlN4mEvbSitvhJdKw7rsA8KHgI= -github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= @@ -348,7 +358,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -382,7 +391,6 @@ github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbV github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -468,6 +476,7 @@ github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvz github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -481,6 +490,7 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -489,7 +499,6 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -507,6 +516,7 @@ github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2F github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -523,12 +533,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= @@ -544,6 +552,7 @@ github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2w github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b h1:jrLvME7VuLW6NRysbiZtenTB9QcNlR9RPKK4LFfZn60= @@ -556,7 +565,6 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4= github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d h1:Csy27nDj00vRGp2+QvwSanaW0RA60SZUHXCk4BBT0AU= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A= @@ -600,6 +608,7 @@ github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/godown v0.0.1 h1:39uk50ufLVQFs0eapIJVX5fCS74a1Fs2g5f1MVqIHdE= github.com/mattn/godown v0.0.1/go.mod h1:/ivCKurgV/bx6yqtP/Jtc2Xmrv3beCYBvlfAUl4X5g4= @@ -667,7 +676,6 @@ github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9/go.mod h1:A5SRAcpTe github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -683,13 +691,11 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/oov/psd v0.0.0-20201002182931-74231384897f/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -791,6 +797,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -843,10 +850,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/slack-go/slack v0.9.1 h1:pekQBs0RmrdAgoqzcMCzUCWSyIkhzUU3F83ExAdZrKo= github.com/slack-go/slack v0.9.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= @@ -899,6 +904,10 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7 github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/throttled/throttled v2.2.5+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -944,7 +953,6 @@ github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPy github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= -github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= @@ -1010,6 +1018,7 @@ go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1 golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1022,10 +1031,12 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1124,6 +1135,7 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= @@ -1229,6 +1241,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1315,10 +1328,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomod.garykim.dev/nc-talk v0.2.2 h1:+U+daJFPPuwM7yRXYazeMHZgIBSGP6SeQURO0O5a32I= -gomod.garykim.dev/nc-talk v0.2.2/go.mod h1:q/Adot/H7iqi+H4lANopV7/xcMf+sX3AZXUXqiITwok= gomod.garykim.dev/nc-talk v0.3.0 h1:MZxLc/gX2/+bdOw4xt6pi+qQFUQld1woGfw1hEJ0fbM= gomod.garykim.dev/nc-talk v0.3.0/go.mod h1:q/Adot/H7iqi+H4lANopV7/xcMf+sX3AZXUXqiITwok= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -1456,7 +1466,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1477,7 +1486,6 @@ gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJc gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M= gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1507,6 +1515,9 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0= layeh.com/gumble v0.0.0-20200818122324-146f9205029b h1:Kne6wkHqbqrygRsqs5XUNhSs84DFG5TYMeCkCbM56sY= layeh.com/gumble v0.0.0-20200818122324-146f9205029b/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc= +maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= +maunium.net/go/mautrix v0.9.14 h1:2MMJ630VM+xfa4Q5AooMAhPG1+wQnQybSr/z8PlRZ8A= +maunium.net/go/mautrix v0.9.14/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/vendor/github.com/btcsuite/btcutil/LICENSE b/vendor/github.com/btcsuite/btcutil/LICENSE new file mode 100644 index 0000000000..3e7b16791f --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/LICENSE @@ -0,0 +1,16 @@ +ISC License + +Copyright (c) 2013-2017 The btcsuite developers +Copyright (c) 2016-2017 The Lightning Network Developers + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/btcsuite/btcutil/base58/README.md b/vendor/github.com/btcsuite/btcutil/base58/README.md new file mode 100644 index 0000000000..98dfb1db88 --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/README.md @@ -0,0 +1,34 @@ +base58 +========== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcutil/base58) + +Package base58 provides an API for encoding and decoding to and from the +modified base58 encoding. It also provides an API to do Base58Check encoding, +as described [here](https://en.bitcoin.it/wiki/Base58Check_encoding). + +A comprehensive suite of tests is provided to ensure proper functionality. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcutil/base58 +``` + +## Examples + +* [Decode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-Decode) + Demonstrates how to decode modified base58 encoded data. +* [Encode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-Encode) + Demonstrates how to encode data using the modified base58 encoding scheme. +* [CheckDecode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-CheckDecode) + Demonstrates how to decode Base58Check encoded data. +* [CheckEncode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-CheckEncode) + Demonstrates how to encode data using the Base58Check encoding scheme. + +## License + +Package base58 is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/vendor/github.com/btcsuite/btcutil/base58/alphabet.go b/vendor/github.com/btcsuite/btcutil/base58/alphabet.go new file mode 100644 index 0000000000..6bb39fef11 --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/alphabet.go @@ -0,0 +1,49 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// AUTOGENERATED by genalphabet.go; do not edit. + +package base58 + +const ( + // alphabet is the modified base58 alphabet used by Bitcoin. + alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + alphabetIdx0 = '1' +) + +var b58 = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 255, 255, 255, 255, 255, 255, + 255, 9, 10, 11, 12, 13, 14, 15, + 16, 255, 17, 18, 19, 20, 21, 255, + 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 255, 255, 255, 255, 255, + 255, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 255, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, +} diff --git a/vendor/github.com/btcsuite/btcutil/base58/base58.go b/vendor/github.com/btcsuite/btcutil/base58/base58.go new file mode 100644 index 0000000000..19a72de2c4 --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/base58.go @@ -0,0 +1,75 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +import ( + "math/big" +) + +//go:generate go run genalphabet.go + +var bigRadix = big.NewInt(58) +var bigZero = big.NewInt(0) + +// Decode decodes a modified base58 string to a byte slice. +func Decode(b string) []byte { + answer := big.NewInt(0) + j := big.NewInt(1) + + scratch := new(big.Int) + for i := len(b) - 1; i >= 0; i-- { + tmp := b58[b[i]] + if tmp == 255 { + return []byte("") + } + scratch.SetInt64(int64(tmp)) + scratch.Mul(j, scratch) + answer.Add(answer, scratch) + j.Mul(j, bigRadix) + } + + tmpval := answer.Bytes() + + var numZeros int + for numZeros = 0; numZeros < len(b); numZeros++ { + if b[numZeros] != alphabetIdx0 { + break + } + } + flen := numZeros + len(tmpval) + val := make([]byte, flen) + copy(val[numZeros:], tmpval) + + return val +} + +// Encode encodes a byte slice to a modified base58 string. +func Encode(b []byte) string { + x := new(big.Int) + x.SetBytes(b) + + answer := make([]byte, 0, len(b)*136/100) + for x.Cmp(bigZero) > 0 { + mod := new(big.Int) + x.DivMod(x, bigRadix, mod) + answer = append(answer, alphabet[mod.Int64()]) + } + + // leading zero bytes + for _, i := range b { + if i != 0 { + break + } + answer = append(answer, alphabetIdx0) + } + + // reverse + alen := len(answer) + for i := 0; i < alen/2; i++ { + answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] + } + + return string(answer) +} diff --git a/vendor/github.com/btcsuite/btcutil/base58/base58check.go b/vendor/github.com/btcsuite/btcutil/base58/base58check.go new file mode 100644 index 0000000000..7cdafeeece --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/base58check.go @@ -0,0 +1,52 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +import ( + "crypto/sha256" + "errors" +) + +// ErrChecksum indicates that the checksum of a check-encoded string does not verify against +// the checksum. +var ErrChecksum = errors.New("checksum error") + +// ErrInvalidFormat indicates that the check-encoded string has an invalid format. +var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing") + +// checksum: first four bytes of sha256^2 +func checksum(input []byte) (cksum [4]byte) { + h := sha256.Sum256(input) + h2 := sha256.Sum256(h[:]) + copy(cksum[:], h2[:4]) + return +} + +// CheckEncode prepends a version byte and appends a four byte checksum. +func CheckEncode(input []byte, version byte) string { + b := make([]byte, 0, 1+len(input)+4) + b = append(b, version) + b = append(b, input[:]...) + cksum := checksum(b) + b = append(b, cksum[:]...) + return Encode(b) +} + +// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum. +func CheckDecode(input string) (result []byte, version byte, err error) { + decoded := Decode(input) + if len(decoded) < 5 { + return nil, 0, ErrInvalidFormat + } + version = decoded[0] + var cksum [4]byte + copy(cksum[:], decoded[len(decoded)-4:]) + if checksum(decoded[:len(decoded)-4]) != cksum { + return nil, 0, ErrChecksum + } + payload := decoded[1 : len(decoded)-4] + result = append(result, payload...) + return +} diff --git a/vendor/github.com/btcsuite/btcutil/base58/cov_report.sh b/vendor/github.com/btcsuite/btcutil/base58/cov_report.sh new file mode 100644 index 0000000000..307f05b76c --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/cov_report.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script uses gocov to generate a test coverage report. +# The gocov tool my be obtained with the following command: +# go get github.com/axw/gocov/gocov +# +# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH. + +# Check for gocov. +type gocov >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo >&2 "This script requires the gocov tool." + echo >&2 "You may obtain it with the following command:" + echo >&2 "go get github.com/axw/gocov/gocov" + exit 1 +fi +gocov test | gocov report diff --git a/vendor/github.com/btcsuite/btcutil/base58/doc.go b/vendor/github.com/btcsuite/btcutil/base58/doc.go new file mode 100644 index 0000000000..9a2c0e6e3d --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/doc.go @@ -0,0 +1,29 @@ +// Copyright (c) 2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package base58 provides an API for working with modified base58 and Base58Check +encodings. + +Modified Base58 Encoding + +Standard base58 encoding is similar to standard base64 encoding except, as the +name implies, it uses a 58 character alphabet which results in an alphanumeric +string and allows some characters which are problematic for humans to be +excluded. Due to this, there can be various base58 alphabets. + +The modified base58 alphabet used by Bitcoin, and hence this package, omits the +0, O, I, and l characters that look the same in many fonts and are therefore +hard to humans to distinguish. + +Base58Check Encoding Scheme + +The Base58Check encoding scheme is primarily used for Bitcoin addresses at the +time of this writing, however it can be used to generically encode arbitrary +byte arrays into human-readable strings along with a version byte that can be +used to differentiate the same payload. For Bitcoin addresses, the extra +version is used to differentiate the network of otherwise identical public keys +which helps prevent using an address intended for one network on another. +*/ +package base58 diff --git a/vendor/github.com/matrix-org/gomatrix/.gitignore b/vendor/github.com/matrix-org/gomatrix/.gitignore deleted file mode 100644 index 0dd56286b9..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so -*.out - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof - -# test editor files -*.swp diff --git a/vendor/github.com/matrix-org/gomatrix/.golangci.yml b/vendor/github.com/matrix-org/gomatrix/.golangci.yml deleted file mode 100644 index 15eb6ef77d..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/.golangci.yml +++ /dev/null @@ -1,21 +0,0 @@ -run: - timeout: 5m - linters: - enable: - - vet - - vetshadow - - typecheck - - deadcode - - gocyclo - - golint - - varcheck - - structcheck - - maligned - - ineffassign - - misspell - - unparam - - goimports - - goconst - - unconvert - - errcheck - - interfacer diff --git a/vendor/github.com/matrix-org/gomatrix/.travis.yml b/vendor/github.com/matrix-org/gomatrix/.travis.yml deleted file mode 100644 index 46997ab67d..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go -go: - - 1.13.10 -install: - - go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.24.0 - - go build -script: ./hooks/pre-commit diff --git a/vendor/github.com/matrix-org/gomatrix/CHANGELOG.md b/vendor/github.com/matrix-org/gomatrix/CHANGELOG.md deleted file mode 100644 index b6a9a16705..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -## Release 0.1.0 (UNRELEASED) diff --git a/vendor/github.com/matrix-org/gomatrix/LICENSE b/vendor/github.com/matrix-org/gomatrix/LICENSE deleted file mode 100644 index 8dada3edaf..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/matrix-org/gomatrix/README.md b/vendor/github.com/matrix-org/gomatrix/README.md deleted file mode 100644 index a083b46c18..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# gomatrix -[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix) - -A Golang Matrix client. - -**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.** - -# Contributing - -All contributions are greatly appreciated! - -## How to report issues - -Please check the current open issues for similar reports -in order to avoid duplicates. - -Some general guidelines: - -- Include a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) when possible. -- Describe the expected behaviour and what actually happened - including a full trace-back in case of exceptions. -- Make sure to list details about your environment - -## Setting up your environment - -If you intend to contribute to gomatrix you'll first need Go installed on your machine (version 1.12+ is required). Also, make sure to have golangci-lint properly set up since we use it for pre-commit hooks (for instructions on how to install it, check the [official docs](https://golangci-lint.run/usage/install/#local-installation)). - -- Fork gomatrix to your GitHub account by clicking the [Fork](https://github.com/matrix-org/gomatrix/fork) button. -- [Clone](https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork) the main repository (not your fork) to your local machine. - - - $ git clone https://github.com/matrix-org/gomatrix - $ cd gomatrix - - -- Add your fork as a remote to push your contributions.Replace - ``{username}`` with your username. - - git remote add fork https://github.com/{username}/gomatrix - -- Create a new branch to identify what feature you are working on. - - $ git fetch origin - $ git checkout -b your-branch-name origin/master - - -- Make your changes, including tests that cover any code changes you make, and run them as described below. - -- Execute pre-commit hooks by running - - /hooks/pre-commit - -- Push your changes to your fork and [create a pull request](https://help.github.com/en/articles/creating-a-pull-request) describing your changes. - - $ git push --set-upstream fork your-branch-name - -- Finally, create a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) - -## How to run tests - -You can run the test suite and example code with `$ go test -v` - -# Running Coverage - -To run coverage, first generate the coverage report using `go test` - - go test -v -cover -coverprofile=coverage.out - -You can now show the generated report as a html page with `go tool` - - go tool cover -html=coverage.out diff --git a/vendor/github.com/matrix-org/gomatrix/client.go b/vendor/github.com/matrix-org/gomatrix/client.go deleted file mode 100644 index d0bb0c5eba..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/client.go +++ /dev/null @@ -1,805 +0,0 @@ -// Package gomatrix implements the Matrix Client-Server API. -// -// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html -package gomatrix - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "path" - "strconv" - "strings" - "sync" - "time" -) - -// Client represents a Matrix client. -type Client struct { - HomeserverURL *url.URL // The base homeserver URL - Prefix string // The API prefix eg '/_matrix/client/r0' - UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID. - AccessToken string // The access_token for the client. - Client *http.Client // The underlying HTTP client which will be used to make HTTP requests. - Syncer Syncer // The thing which can process /sync responses - Store Storer // The thing which can store rooms/tokens/ids - - // The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty, - // no user_id parameter will be sent. - // See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion - AppServiceUserID string - - syncingMutex sync.Mutex // protects syncingID - syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time. -} - -// HTTPError An HTTP Error response, which may wrap an underlying native Go Error. -type HTTPError struct { - Contents []byte - WrappedError error - Message string - Code int -} - -func (e HTTPError) Error() string { - var wrappedErrMsg string - if e.WrappedError != nil { - wrappedErrMsg = e.WrappedError.Error() - } - return fmt.Sprintf("contents=%v msg=%s code=%d wrapped=%s", e.Contents, e.Message, e.Code, wrappedErrMsg) -} - -// BuildURL builds a URL with the Client's homeserver/prefix set already. -func (cli *Client) BuildURL(urlPath ...string) string { - ps := append([]string{cli.Prefix}, urlPath...) - return cli.BuildBaseURL(ps...) -} - -// BuildBaseURL builds a URL with the Client's homeserver set already. You must -// supply the prefix in the path. -func (cli *Client) BuildBaseURL(urlPath ...string) string { - // copy the URL. Purposefully ignore error as the input is from a valid URL already - hsURL, _ := url.Parse(cli.HomeserverURL.String()) - parts := []string{hsURL.Path} - parts = append(parts, urlPath...) - hsURL.Path = path.Join(parts...) - // Manually add the trailing slash back to the end of the path if it's explicitly needed - if strings.HasSuffix(urlPath[len(urlPath)-1], "/") { - hsURL.Path = hsURL.Path + "/" - } - query := hsURL.Query() - if cli.AppServiceUserID != "" { - query.Set("user_id", cli.AppServiceUserID) - } - hsURL.RawQuery = query.Encode() - return hsURL.String() -} - -// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix set already. -func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string { - u, _ := url.Parse(cli.BuildURL(urlPath...)) - q := u.Query() - for k, v := range urlQuery { - q.Set(k, v) - } - u.RawQuery = q.Encode() - return u.String() -} - -// SetCredentials sets the user ID and access token on this client instance. -func (cli *Client) SetCredentials(userID, accessToken string) { - cli.AccessToken = accessToken - cli.UserID = userID -} - -// ClearCredentials removes the user ID and access token on this client instance. -func (cli *Client) ClearCredentials() { - cli.AccessToken = "" - cli.UserID = "" -} - -// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the -// error will be nil. -// -// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine. -// Fatal sync errors can be caused by: -// - The failure to create a filter. -// - Client.Syncer.OnFailedSync returning an error in response to a failed sync. -// - Client.Syncer.ProcessResponse returning an error. -// If you wish to continue retrying in spite of these fatal errors, call Sync() again. -func (cli *Client) Sync() error { - // Mark the client as syncing. - // We will keep syncing until the syncing state changes. Either because - // Sync is called or StopSync is called. - syncingID := cli.incrementSyncingID() - nextBatch := cli.Store.LoadNextBatch(cli.UserID) - filterID := cli.Store.LoadFilterID(cli.UserID) - if filterID == "" { - filterJSON := cli.Syncer.GetFilterJSON(cli.UserID) - resFilter, err := cli.CreateFilter(filterJSON) - if err != nil { - return err - } - filterID = resFilter.FilterID - cli.Store.SaveFilterID(cli.UserID, filterID) - } - - for { - resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "") - if err != nil { - duration, err2 := cli.Syncer.OnFailedSync(resSync, err) - if err2 != nil { - return err2 - } - time.Sleep(duration) - continue - } - - // Check that the syncing state hasn't changed - // Either because we've stopped syncing or another sync has been started. - // We discard the response from our sync. - if cli.getSyncingID() != syncingID { - return nil - } - - // Save the token now *before* processing it. This means it's possible - // to not process some events, but it means that we won't get constantly stuck processing - // a malformed/buggy event which keeps making us panic. - cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch) - if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil { - return err - } - - nextBatch = resSync.NextBatch - } -} - -func (cli *Client) incrementSyncingID() uint32 { - cli.syncingMutex.Lock() - defer cli.syncingMutex.Unlock() - cli.syncingID++ - return cli.syncingID -} - -func (cli *Client) getSyncingID() uint32 { - cli.syncingMutex.Lock() - defer cli.syncingMutex.Unlock() - return cli.syncingID -} - -// StopSync stops the ongoing sync started by Sync. -func (cli *Client) StopSync() { - // Advance the syncing state so that any running Syncs will terminate. - cli.incrementSyncingID() -} - -// MakeRequest makes a JSON HTTP request to the given URL. -// The response body will be stream decoded into an interface. This will automatically stop if the response -// body is nil. -// -// Returns an error if the response is not 2xx along with the HTTP body bytes if it got that far. This error is -// an HTTPError which includes the returned HTTP status code, byte contents of the response body and possibly a -// RespError as the WrappedError, if the HTTP body could be decoded as a RespError. -func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) error { - var req *http.Request - var err error - if reqBody != nil { - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(reqBody); err != nil { - return err - } - req, err = http.NewRequest(method, httpURL, buf) - } else { - req, err = http.NewRequest(method, httpURL, nil) - } - - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - - if cli.AccessToken != "" { - req.Header.Set("Authorization", "Bearer "+cli.AccessToken) - } - - res, err := cli.Client.Do(req) - if res != nil { - defer res.Body.Close() - } - if err != nil { - return err - } - if res.StatusCode/100 != 2 { // not 2xx - contents, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - - var wrap error - var respErr RespError - if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" { - wrap = respErr - } - - // If we failed to decode as RespError, don't just drop the HTTP body, include it in the - // HTTP error instead (e.g proxy errors which return HTML). - msg := "Failed to " + method + " JSON to " + req.URL.Path - if wrap == nil { - msg = msg + ": " + string(contents) - } - - return HTTPError{ - Contents: contents, - Code: res.StatusCode, - Message: msg, - WrappedError: wrap, - } - } - - if resBody != nil && res.Body != nil { - return json.NewDecoder(res.Body).Decode(&resBody) - } - - return nil -} - -// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter -func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) { - urlPath := cli.BuildURL("user", cli.UserID, "filter") - err = cli.MakeRequest("POST", urlPath, &filter, &resp) - return -} - -// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync -func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) { - query := map[string]string{ - "timeout": strconv.Itoa(timeout), - } - if since != "" { - query["since"] = since - } - if filterID != "" { - query["filter"] = filterID - } - if setPresence != "" { - query["set_presence"] = setPresence - } - if fullState { - query["full_state"] = "true" - } - urlPath := cli.BuildURLWithQuery([]string{"sync"}, query) - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { - err = cli.MakeRequest("POST", u, req, &resp) - if err != nil { - httpErr, ok := err.(HTTPError) - if !ok { // network error - return - } - if httpErr.Code == 401 { - // body should be RespUserInteractive, if it isn't, fail with the error - err = json.Unmarshal(httpErr.Contents, &uiaResp) - return - } - } - return -} - -// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -// -// Registers with kind=user. For kind=guest, see RegisterGuest. -func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { - u := cli.BuildURL("register") - return cli.register(u, req) -} - -// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -// with kind=guest. -// -// For kind=user, see Register. -func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { - query := map[string]string{ - "kind": "guest", - } - u := cli.BuildURLWithQuery([]string{"register"}, query) - return cli.register(u, req) -} - -// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth -// -// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration -// this way. If the homeserver does not, an error is returned. -// -// This does not set credentials on the client instance. See SetCredentials() instead. -// -// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{ -// Username: "alice", -// Password: "wonderland", -// }) -// if err != nil { -// panic(err) -// } -// token := res.AccessToken -func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { - res, uia, err := cli.Register(req) - if err != nil && uia == nil { - return nil, err - } - if uia != nil && uia.HasSingleStageFlow("m.login.dummy") { - req.Auth = struct { - Type string `json:"type"` - Session string `json:"session,omitempty"` - }{"m.login.dummy", uia.Session} - res, _, err = cli.Register(req) - if err != nil { - return nil, err - } - } - if res == nil { - return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?") - } - return res, nil -} - -// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login -// This does not set credentials on this client instance. See SetCredentials() instead. -func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) { - urlPath := cli.BuildURL("login") - err = cli.MakeRequest("POST", urlPath, req, &resp) - return -} - -// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-logout -// This does not clear the credentials from the client instance. See ClearCredentials() instead. -func (cli *Client) Logout() (resp *RespLogout, err error) { - urlPath := cli.BuildURL("logout") - err = cli.MakeRequest("POST", urlPath, nil, &resp) - return -} - -// LogoutAll logs the current user out on all devices. See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all -// This does not clear the credentials from the client instance. See ClearCredentails() instead. -func (cli *Client) LogoutAll() (resp *RespLogoutAll, err error) { - urlPath := cli.BuildURL("logout/all") - err = cli.MakeRequest("POST", urlPath, nil, &resp) - return -} - -// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions -func (cli *Client) Versions() (resp *RespVersions, err error) { - urlPath := cli.BuildBaseURL("_matrix", "client", "versions") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// PublicRooms returns the list of public rooms on target server. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-unstable-publicrooms -func (cli *Client) PublicRooms(limit int, since string, server string) (resp *RespPublicRooms, err error) { - args := map[string]string{} - - if limit != 0 { - args["limit"] = strconv.Itoa(limit) - } - if since != "" { - args["since"] = since - } - if server != "" { - args["server"] = server - } - - urlPath := cli.BuildURLWithQuery([]string{"publicRooms"}, args) - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// PublicRoomsFiltered returns a subset of PublicRooms filtered server side. -// See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-unstable-publicrooms -func (cli *Client) PublicRoomsFiltered(limit int, since string, server string, filter string) (resp *RespPublicRooms, err error) { - content := map[string]string{} - - if limit != 0 { - content["limit"] = strconv.Itoa(limit) - } - if since != "" { - content["since"] = since - } - if filter != "" { - content["filter"] = filter - } - - var urlPath string - if server == "" { - urlPath = cli.BuildURL("publicRooms") - } else { - urlPath = cli.BuildURLWithQuery([]string{"publicRooms"}, map[string]string{ - "server": server, - }) - } - - err = cli.MakeRequest("POST", urlPath, content, &resp) - return -} - -// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias -// -// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will -// be JSON encoded and used as the request body. -func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) { - var urlPath string - if serverName != "" { - urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{ - "server_name": serverName, - }) - } else { - urlPath = cli.BuildURL("join", roomIDorAlias) - } - err = cli.MakeRequest("POST", urlPath, content, &resp) - return -} - -// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname -func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) { - urlPath := cli.BuildURL("profile", mxid, "displayname") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname -func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) { - urlPath := cli.BuildURL("profile", cli.UserID, "displayname") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname -func (cli *Client) SetDisplayName(displayName string) (err error) { - urlPath := cli.BuildURL("profile", cli.UserID, "displayname") - s := struct { - DisplayName string `json:"displayname"` - }{displayName} - err = cli.MakeRequest("PUT", urlPath, &s, nil) - return -} - -// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url -func (cli *Client) GetAvatarURL() (string, error) { - urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") - s := struct { - AvatarURL string `json:"avatar_url"` - }{} - - err := cli.MakeRequest("GET", urlPath, nil, &s) - if err != nil { - return "", err - } - - return s.AvatarURL, nil -} - -// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url -func (cli *Client) SetAvatarURL(url string) error { - urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") - s := struct { - AvatarURL string `json:"avatar_url"` - }{url} - err := cli.MakeRequest("PUT", urlPath, &s, nil) - if err != nil { - return err - } - - return nil -} - -// GetStatus returns the status of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status -func (cli *Client) GetStatus(mxid string) (resp *RespUserStatus, err error) { - urlPath := cli.BuildURL("presence", mxid, "status") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// GetOwnStatus returns the user's status. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status -func (cli *Client) GetOwnStatus() (resp *RespUserStatus, err error) { - return cli.GetStatus(cli.UserID) -} - -// SetStatus sets the user's status. See https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-presence-userid-status -func (cli *Client) SetStatus(presence, status string) (err error) { - urlPath := cli.BuildURL("presence", cli.UserID, "status") - s := struct { - Presence string `json:"presence"` - StatusMsg string `json:"status_msg"` - }{presence, status} - err = cli.MakeRequest("PUT", urlPath, &s, nil) - return -} - -// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid -// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. -func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) { - txnID := txnID() - urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID) - err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) - return -} - -// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey -// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. -func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) { - urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey) - err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) - return -} - -// SendText sends an m.room.message event into the given room with a msgtype of m.text -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text -func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) { - return cli.SendMessageEvent(roomID, "m.room.message", - TextMessage{MsgType: "m.text", Body: text}) -} - -// SendFormattedText sends an m.room.message event into the given room with a msgtype of m.text, supports a subset of HTML for formatting. -// See https://matrix.org/docs/spec/client_server/r0.6.0#m-text -func (cli *Client) SendFormattedText(roomID, text, formattedText string) (*RespSendEvent, error) { - return cli.SendMessageEvent(roomID, "m.room.message", - TextMessage{MsgType: "m.text", Body: text, FormattedBody: formattedText, Format: "org.matrix.custom.html"}) -} - -// SendImage sends an m.room.message event into the given room with a msgtype of m.image -// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image -func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) { - return cli.SendMessageEvent(roomID, "m.room.message", - ImageMessage{ - MsgType: "m.image", - Body: body, - URL: url, - }) -} - -// SendVideo sends an m.room.message event into the given room with a msgtype of m.video -// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video -func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) { - return cli.SendMessageEvent(roomID, "m.room.message", - VideoMessage{ - MsgType: "m.video", - Body: body, - URL: url, - }) -} - -// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice -func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) { - return cli.SendMessageEvent(roomID, "m.room.message", - TextMessage{MsgType: "m.notice", Body: text}) -} - -// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid -func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) { - txnID := txnID() - urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID) - err = cli.MakeRequest("PUT", urlPath, req, &resp) - return -} - -// MarkRead marks eventID in roomID as read, signifying the event, and all before it have been read. See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-rooms-roomid-receipt-receipttype-eventid -func (cli *Client) MarkRead(roomID, eventID string) error { - urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID) - return cli.MakeRequest("POST", urlPath, nil, nil) -} - -// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{ -// Preset: "public_chat", -// }) -// fmt.Println("Room:", resp.RoomID) -func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) { - urlPath := cli.BuildURL("createRoom") - err = cli.MakeRequest("POST", urlPath, req, &resp) - return -} - -// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave -func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) { - u := cli.BuildURL("rooms", roomID, "leave") - err = cli.MakeRequest("POST", u, struct{}{}, &resp) - return -} - -// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget -func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) { - u := cli.BuildURL("rooms", roomID, "forget") - err = cli.MakeRequest("POST", u, struct{}{}, &resp) - return -} - -// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite -func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) { - u := cli.BuildURL("rooms", roomID, "invite") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint -func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) { - u := cli.BuildURL("rooms", roomID, "invite") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick -func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) { - u := cli.BuildURL("rooms", roomID, "kick") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban -func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) { - u := cli.BuildURL("rooms", roomID, "ban") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban -func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) { - u := cli.BuildURL("rooms", roomID, "unban") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid -func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) { - req := ReqTyping{Typing: typing, Timeout: timeout} - u := cli.BuildURL("rooms", roomID, "typing", cli.UserID) - err = cli.MakeRequest("PUT", u, req, &resp) - return -} - -// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with -// the HTTP response body, or return an error. -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey -func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) { - u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey) - err = cli.MakeRequest("GET", u, nil, outContent) - return -} - -// UploadLink uploads an HTTP URL and then returns an MXC URI. -func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) { - res, err := cli.Client.Get(link) - if res != nil { - defer res.Body.Close() - } - if err != nil { - return nil, err - } - return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength) -} - -// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI. -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload -func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) { - req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", contentType) - req.Header.Set("Authorization", "Bearer "+cli.AccessToken) - - req.ContentLength = contentLength - - res, err := cli.Client.Do(req) - if res != nil { - defer res.Body.Close() - } - - if err != nil { - return nil, err - } - - if res.StatusCode != 200 { - contents, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, HTTPError{ - Message: "Upload request failed - Failed to read response body: " + err.Error(), - Code: res.StatusCode, - } - } - return nil, HTTPError{ - Contents: contents, - Message: "Upload request failed: " + string(contents), - Code: res.StatusCode, - } - } - - var m RespMediaUpload - if err := json.NewDecoder(res.Body).Decode(&m); err != nil { - return nil, err - } - - return &m, nil -} - -// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680 -// -// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. -// This API is primarily designed for application services which may want to efficiently look up joined members in a room. -func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) { - u := cli.BuildURL("rooms", roomID, "joined_members") - err = cli.MakeRequest("GET", u, nil, &resp) - return -} - -// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680 -// -// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. -// This API is primarily designed for application services which may want to efficiently look up joined rooms. -func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) { - u := cli.BuildURL("joined_rooms") - err = cli.MakeRequest("GET", u, nil, &resp) - return -} - -// Messages returns a list of message and state events for a room. It uses -// pagination query parameters to paginate history in the room. -// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages -func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) { - query := map[string]string{ - "from": from, - "dir": string(dir), - } - if to != "" { - query["to"] = to - } - if limit != 0 { - query["limit"] = strconv.Itoa(limit) - } - - urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query) - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// TurnServer returns turn server details and credentials for the client to use when initiating calls. -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver -func (cli *Client) TurnServer() (resp *RespTurnServer, err error) { - urlPath := cli.BuildURL("voip", "turnServer") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -func txnID() string { - return "go" + strconv.FormatInt(time.Now().UnixNano(), 10) -} - -// NewClient creates a new Matrix Client ready for syncing -func NewClient(homeserverURL, userID, accessToken string) (*Client, error) { - hsURL, err := url.Parse(homeserverURL) - if err != nil { - return nil, err - } - // By default, use an in-memory store which will never save filter ids / next batch tokens to disk. - // The client will work with this storer: it just won't remember across restarts. - // In practice, a database backend should be used. - store := NewInMemoryStore() - cli := Client{ - AccessToken: accessToken, - HomeserverURL: hsURL, - UserID: userID, - Prefix: "/_matrix/client/r0", - Syncer: NewDefaultSyncer(userID, store), - Store: store, - } - // By default, use the default HTTP client. - cli.Client = http.DefaultClient - - return &cli, nil -} diff --git a/vendor/github.com/matrix-org/gomatrix/events.go b/vendor/github.com/matrix-org/gomatrix/events.go deleted file mode 100644 index cbc70a8228..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/events.go +++ /dev/null @@ -1,157 +0,0 @@ -package gomatrix - -import ( - "html" - "regexp" -) - -// Event represents a single Matrix event. -type Event struct { - StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events. - Sender string `json:"sender"` // The user ID of the sender of the event - Type string `json:"type"` // The event type - Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server - ID string `json:"event_id"` // The unique ID of this event - RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence) - Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event - Unsigned map[string]interface{} `json:"unsigned"` // The unsigned portions of the event, such as age and prev_content - Content map[string]interface{} `json:"content"` // The JSON content of the event. - PrevContent map[string]interface{} `json:"prev_content,omitempty"` // The JSON prev_content of the event. -} - -// Body returns the value of the "body" key in the event content if it is -// present and is a string. -func (event *Event) Body() (body string, ok bool) { - value, exists := event.Content["body"] - if !exists { - return - } - body, ok = value.(string) - return -} - -// MessageType returns the value of the "msgtype" key in the event content if -// it is present and is a string. -func (event *Event) MessageType() (msgtype string, ok bool) { - value, exists := event.Content["msgtype"] - if !exists { - return - } - msgtype, ok = value.(string) - return -} - -// TextMessage is the contents of a Matrix formated message event. -type TextMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - FormattedBody string `json:"formatted_body"` - Format string `json:"format"` -} - -// ThumbnailInfo contains info about an thumbnail image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image -type ThumbnailInfo struct { - Height uint `json:"h,omitempty"` - Width uint `json:"w,omitempty"` - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` -} - -// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image -type ImageInfo struct { - Height uint `json:"h,omitempty"` - Width uint `json:"w,omitempty"` - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` - ThumbnailInfo ThumbnailInfo `json:"thumbnail_info,omitempty"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` -} - -// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video -type VideoInfo struct { - Mimetype string `json:"mimetype,omitempty"` - ThumbnailInfo ThumbnailInfo `json:"thumbnail_info"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - Height uint `json:"h,omitempty"` - Width uint `json:"w,omitempty"` - Duration uint `json:"duration,omitempty"` - Size uint `json:"size,omitempty"` -} - -// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video -type VideoMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - URL string `json:"url"` - Info VideoInfo `json:"info"` -} - -// ImageMessage is an m.image event -type ImageMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - URL string `json:"url"` - Info ImageInfo `json:"info"` -} - -// An HTMLMessage is the contents of a Matrix HTML formated message event. -type HTMLMessage struct { - Body string `json:"body"` - MsgType string `json:"msgtype"` - Format string `json:"format"` - FormattedBody string `json:"formatted_body"` -} - -// FileInfo contains info about an file - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-file -type FileInfo struct { - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` //filesize in bytes -} - -// FileMessage is an m.file event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-file -type FileMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - URL string `json:"url"` - Filename string `json:"filename"` - Info FileInfo `json:"info,omitempty"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"` -} - -// LocationMessage is an m.location event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location -type LocationMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - GeoURI string `json:"geo_uri"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"` -} - -// AudioInfo contains info about an file - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio -type AudioInfo struct { - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` //filesize in bytes - Duration uint `json:"duration,omitempty"` //audio duration in ms -} - -// AudioMessage is an m.audio event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio -type AudioMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - URL string `json:"url"` - Info AudioInfo `json:"info,omitempty"` -} - -var htmlRegex = regexp.MustCompile("<[^<]+?>") - -// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition -// to the provided HTML. -func GetHTMLMessage(msgtype, htmlText string) HTMLMessage { - return HTMLMessage{ - Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")), - MsgType: msgtype, - Format: "org.matrix.custom.html", - FormattedBody: htmlText, - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/filter.go b/vendor/github.com/matrix-org/gomatrix/filter.go deleted file mode 100644 index 2a0c37facb..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/filter.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2017 Jan Christian Grünhage -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomatrix - -import "errors" - -//Filter is used by clients to specify how the server should filter responses to e.g. sync requests -//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering -type Filter struct { - AccountData FilterPart `json:"account_data,omitempty"` - EventFields []string `json:"event_fields,omitempty"` - EventFormat string `json:"event_format,omitempty"` - Presence FilterPart `json:"presence,omitempty"` - Room RoomFilter `json:"room,omitempty"` -} - -// RoomFilter is used to define filtering rules for room events -type RoomFilter struct { - AccountData FilterPart `json:"account_data,omitempty"` - Ephemeral FilterPart `json:"ephemeral,omitempty"` - IncludeLeave bool `json:"include_leave,omitempty"` - NotRooms []string `json:"not_rooms,omitempty"` - Rooms []string `json:"rooms,omitempty"` - State FilterPart `json:"state,omitempty"` - Timeline FilterPart `json:"timeline,omitempty"` -} - -// FilterPart is used to define filtering rules for specific categories of events -type FilterPart struct { - NotRooms []string `json:"not_rooms,omitempty"` - Rooms []string `json:"rooms,omitempty"` - Limit int `json:"limit,omitempty"` - NotSenders []string `json:"not_senders,omitempty"` - NotTypes []string `json:"not_types,omitempty"` - Senders []string `json:"senders,omitempty"` - Types []string `json:"types,omitempty"` - ContainsURL *bool `json:"contains_url,omitempty"` -} - -// Validate checks if the filter contains valid property values -func (filter *Filter) Validate() error { - if filter.EventFormat != "client" && filter.EventFormat != "federation" { - return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]") - } - return nil -} - -// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request -func DefaultFilter() Filter { - return Filter{ - AccountData: DefaultFilterPart(), - EventFields: nil, - EventFormat: "client", - Presence: DefaultFilterPart(), - Room: RoomFilter{ - AccountData: DefaultFilterPart(), - Ephemeral: DefaultFilterPart(), - IncludeLeave: false, - NotRooms: nil, - Rooms: nil, - State: DefaultFilterPart(), - Timeline: DefaultFilterPart(), - }, - } -} - -// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request -func DefaultFilterPart() FilterPart { - return FilterPart{ - NotRooms: nil, - Rooms: nil, - Limit: 20, - NotSenders: nil, - NotTypes: nil, - Senders: nil, - Types: nil, - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/go.mod b/vendor/github.com/matrix-org/gomatrix/go.mod deleted file mode 100644 index 18e69bf12f..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/matrix-org/gomatrix - -go 1.12 diff --git a/vendor/github.com/matrix-org/gomatrix/identifier.go b/vendor/github.com/matrix-org/gomatrix/identifier.go deleted file mode 100644 index 4a61d08050..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/identifier.go +++ /dev/null @@ -1,69 +0,0 @@ -package gomatrix - -// Identifier is the interface for https://matrix.org/docs/spec/client_server/r0.6.0#identifier-types -type Identifier interface { - // Returns the identifier type - // https://matrix.org/docs/spec/client_server/r0.6.0#identifier-types - Type() string -} - -// UserIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#matrix-user-id -type UserIdentifier struct { - IDType string `json:"type"` // Set by NewUserIdentifer - User string `json:"user"` -} - -// Type implements the Identifier interface -func (i UserIdentifier) Type() string { - return "m.id.user" -} - -// NewUserIdentifier creates a new UserIdentifier with IDType set to "m.id.user" -func NewUserIdentifier(user string) UserIdentifier { - return UserIdentifier{ - IDType: "m.id.user", - User: user, - } -} - -// ThirdpartyIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#third-party-id -type ThirdpartyIdentifier struct { - IDType string `json:"type"` // Set by NewThirdpartyIdentifier - Medium string `json:"medium"` - Address string `json:"address"` -} - -// Type implements the Identifier interface -func (i ThirdpartyIdentifier) Type() string { - return "m.id.thirdparty" -} - -// NewThirdpartyIdentifier creates a new UserIdentifier with IDType set to "m.id.user" -func NewThirdpartyIdentifier(medium, address string) ThirdpartyIdentifier { - return ThirdpartyIdentifier{ - IDType: "m.id.thirdparty", - Medium: medium, - Address: address, - } -} - -// PhoneIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#phone-number -type PhoneIdentifier struct { - IDType string `json:"type"` // Set by NewPhoneIdentifier - Country string `json:"country"` - Phone string `json:"phone"` -} - -// Type implements the Identifier interface -func (i PhoneIdentifier) Type() string { - return "m.id.phone" -} - -// NewPhoneIdentifier creates a new UserIdentifier with IDType set to "m.id.user" -func NewPhoneIdentifier(country, phone string) PhoneIdentifier { - return PhoneIdentifier{ - IDType: "m.id.phone", - Country: country, - Phone: phone, - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/requests.go b/vendor/github.com/matrix-org/gomatrix/requests.go deleted file mode 100644 index 31c426d441..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/requests.go +++ /dev/null @@ -1,79 +0,0 @@ -package gomatrix - -// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -type ReqRegister struct { - Username string `json:"username,omitempty"` - BindEmail bool `json:"bind_email,omitempty"` - Password string `json:"password,omitempty"` - DeviceID string `json:"device_id,omitempty"` - InitialDeviceDisplayName string `json:"initial_device_display_name"` - Auth interface{} `json:"auth,omitempty"` -} - -// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-login -type ReqLogin struct { - Type string `json:"type"` - Identifier Identifier `json:"identifier,omitempty"` - Password string `json:"password,omitempty"` - Medium string `json:"medium,omitempty"` - User string `json:"user,omitempty"` - Address string `json:"address,omitempty"` - Token string `json:"token,omitempty"` - DeviceID string `json:"device_id,omitempty"` - InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` -} - -// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -type ReqCreateRoom struct { - Visibility string `json:"visibility,omitempty"` - RoomAliasName string `json:"room_alias_name,omitempty"` - Name string `json:"name,omitempty"` - Topic string `json:"topic,omitempty"` - Invite []string `json:"invite,omitempty"` - Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"` - CreationContent map[string]interface{} `json:"creation_content,omitempty"` - InitialState []Event `json:"initial_state,omitempty"` - Preset string `json:"preset,omitempty"` - IsDirect bool `json:"is_direct,omitempty"` -} - -// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid -type ReqRedact struct { - Reason string `json:"reason,omitempty"` -} - -// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57 -// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -type ReqInvite3PID struct { - IDServer string `json:"id_server"` - Medium string `json:"medium"` - Address string `json:"address"` -} - -// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite -type ReqInviteUser struct { - UserID string `json:"user_id"` -} - -// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick -type ReqKickUser struct { - Reason string `json:"reason,omitempty"` - UserID string `json:"user_id"` -} - -// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban -type ReqBanUser struct { - Reason string `json:"reason,omitempty"` - UserID string `json:"user_id"` -} - -// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban -type ReqUnbanUser struct { - UserID string `json:"user_id"` -} - -// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid -type ReqTyping struct { - Typing bool `json:"typing"` - Timeout int64 `json:"timeout"` -} diff --git a/vendor/github.com/matrix-org/gomatrix/responses.go b/vendor/github.com/matrix-org/gomatrix/responses.go deleted file mode 100644 index f488e69e37..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/responses.go +++ /dev/null @@ -1,210 +0,0 @@ -package gomatrix - -// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface. -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards -type RespError struct { - ErrCode string `json:"errcode"` - Err string `json:"error"` -} - -// Error returns the errcode and error message. -func (e RespError) Error() string { - return e.ErrCode + ": " + e.Err -} - -// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter -type RespCreateFilter struct { - FilterID string `json:"filter_id"` -} - -// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions -type RespVersions struct { - Versions []string `json:"versions"` -} - -// RespPublicRooms is the JSON response for http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#get-matrix-client-unstable-publicrooms -type RespPublicRooms struct { - TotalRoomCountEstimate int `json:"total_room_count_estimate"` - PrevBatch string `json:"prev_batch"` - NextBatch string `json:"next_batch"` - Chunk []PublicRoom `json:"chunk"` -} - -// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join -type RespJoinRoom struct { - RoomID string `json:"room_id"` -} - -// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave -type RespLeaveRoom struct{} - -// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget -type RespForgetRoom struct{} - -// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite -type RespInviteUser struct{} - -// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick -type RespKickUser struct{} - -// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban -type RespBanUser struct{} - -// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban -type RespUnbanUser struct{} - -// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid -type RespTyping struct{} - -// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680 -type RespJoinedRooms struct { - JoinedRooms []string `json:"joined_rooms"` -} - -// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680 -type RespJoinedMembers struct { - Joined map[string]struct { - DisplayName *string `json:"display_name"` - AvatarURL *string `json:"avatar_url"` - } `json:"joined"` -} - -// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages -type RespMessages struct { - Start string `json:"start"` - Chunk []Event `json:"chunk"` - End string `json:"end"` -} - -// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid -type RespSendEvent struct { - EventID string `json:"event_id"` -} - -// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload -type RespMediaUpload struct { - ContentURI string `json:"content_uri"` -} - -// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api -type RespUserInteractive struct { - Flows []struct { - Stages []string `json:"stages"` - } `json:"flows"` - Params map[string]interface{} `json:"params"` - Session string `json:"session"` - Completed []string `json:"completed"` - ErrCode string `json:"errcode"` - Error string `json:"error"` -} - -// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName. -func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool { - for _, f := range r.Flows { - if len(f.Stages) == 1 && f.Stages[0] == stageName { - return true - } - } - return false -} - -// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname -type RespUserDisplayName struct { - DisplayName string `json:"displayname"` -} - -// RespUserStatus is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status -type RespUserStatus struct { - Presence string `json:"presence"` - StatusMsg string `json:"status_msg"` - LastActiveAgo int `json:"last_active_ago"` - CurrentlyActive bool `json:"currently_active"` -} - -// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -type RespRegister struct { - AccessToken string `json:"access_token"` - DeviceID string `json:"device_id"` - HomeServer string `json:"home_server"` - RefreshToken string `json:"refresh_token"` - UserID string `json:"user_id"` -} - -// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-login -type RespLogin struct { - AccessToken string `json:"access_token"` - DeviceID string `json:"device_id"` - HomeServer string `json:"home_server"` - UserID string `json:"user_id"` - WellKnown DiscoveryInformation `json:"well_known"` -} - -// DiscoveryInformation is the JSON Response for https://matrix.org/docs/spec/client_server/r0.6.0#get-well-known-matrix-client and a part of the JSON Response for https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login -type DiscoveryInformation struct { - Homeserver struct { - BaseURL string `json:"base_url"` - } `json:"m.homeserver"` - IdentityServer struct { - BaseURL string `json:"base_url"` - } `json:"m.identitiy_server"` -} - -// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-logout -type RespLogout struct{} - -// RespLogoutAll is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all -type RespLogoutAll struct{} - -// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -type RespCreateRoom struct { - RoomID string `json:"room_id"` -} - -// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync -type RespSync struct { - NextBatch string `json:"next_batch"` - AccountData struct { - Events []Event `json:"events"` - } `json:"account_data"` - Presence struct { - Events []Event `json:"events"` - } `json:"presence"` - Rooms struct { - Leave map[string]struct { - State struct { - Events []Event `json:"events"` - } `json:"state"` - Timeline struct { - Events []Event `json:"events"` - Limited bool `json:"limited"` - PrevBatch string `json:"prev_batch"` - } `json:"timeline"` - } `json:"leave"` - Join map[string]struct { - State struct { - Events []Event `json:"events"` - } `json:"state"` - Timeline struct { - Events []Event `json:"events"` - Limited bool `json:"limited"` - PrevBatch string `json:"prev_batch"` - } `json:"timeline"` - Ephemeral struct { - Events []Event `json:"events"` - } `json:"ephemeral"` - } `json:"join"` - Invite map[string]struct { - State struct { - Events []Event - } `json:"invite_state"` - } `json:"invite"` - } `json:"rooms"` -} - -// RespTurnServer is the JSON response from a Turn Server -type RespTurnServer struct { - Username string `json:"username"` - Password string `json:"password"` - TTL int `json:"ttl"` - URIs []string `json:"uris"` -} diff --git a/vendor/github.com/matrix-org/gomatrix/room.go b/vendor/github.com/matrix-org/gomatrix/room.go deleted file mode 100644 index 364deab26c..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/room.go +++ /dev/null @@ -1,63 +0,0 @@ -package gomatrix - -// Room represents a single Matrix room. -type Room struct { - ID string - State map[string]map[string]*Event -} - -// PublicRoom represents the information about a public room obtainable from the room directory -type PublicRoom struct { - CanonicalAlias string `json:"canonical_alias"` - Name string `json:"name"` - WorldReadable bool `json:"world_readable"` - Topic string `json:"topic"` - NumJoinedMembers int `json:"num_joined_members"` - AvatarURL string `json:"avatar_url"` - RoomID string `json:"room_id"` - GuestCanJoin bool `json:"guest_can_join"` - Aliases []string `json:"aliases"` -} - -// UpdateState updates the room's current state with the given Event. This will clobber events based -// on the type/state_key combination. -func (room Room) UpdateState(event *Event) { - _, exists := room.State[event.Type] - if !exists { - room.State[event.Type] = make(map[string]*Event) - } - room.State[event.Type][*event.StateKey] = event -} - -// GetStateEvent returns the state event for the given type/state_key combo, or nil. -func (room Room) GetStateEvent(eventType string, stateKey string) *Event { - stateEventMap := room.State[eventType] - event := stateEventMap[stateKey] - return event -} - -// GetMembershipState returns the membership state of the given user ID in this room. If there is -// no entry for this member, 'leave' is returned for consistency with left users. -func (room Room) GetMembershipState(userID string) string { - state := "leave" - event := room.GetStateEvent("m.room.member", userID) - if event != nil { - membershipState, found := event.Content["membership"] - if found { - mState, isString := membershipState.(string) - if isString { - state = mState - } - } - } - return state -} - -// NewRoom creates a new Room with the given ID -func NewRoom(roomID string) *Room { - // Init the State map and return a pointer to the Room - return &Room{ - ID: roomID, - State: make(map[string]map[string]*Event), - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/store.go b/vendor/github.com/matrix-org/gomatrix/store.go deleted file mode 100644 index 6dc687e52e..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/store.go +++ /dev/null @@ -1,65 +0,0 @@ -package gomatrix - -// Storer is an interface which must be satisfied to store client data. -// -// You can either write a struct which persists this data to disk, or you can use the -// provided "InMemoryStore" which just keeps data around in-memory which is lost on -// restarts. -type Storer interface { - SaveFilterID(userID, filterID string) - LoadFilterID(userID string) string - SaveNextBatch(userID, nextBatchToken string) - LoadNextBatch(userID string) string - SaveRoom(room *Room) - LoadRoom(roomID string) *Room -} - -// InMemoryStore implements the Storer interface. -// -// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs -// or next batch tokens on any goroutine other than the syncing goroutine: the one -// which called Client.Sync(). -type InMemoryStore struct { - Filters map[string]string - NextBatch map[string]string - Rooms map[string]*Room -} - -// SaveFilterID to memory. -func (s *InMemoryStore) SaveFilterID(userID, filterID string) { - s.Filters[userID] = filterID -} - -// LoadFilterID from memory. -func (s *InMemoryStore) LoadFilterID(userID string) string { - return s.Filters[userID] -} - -// SaveNextBatch to memory. -func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) { - s.NextBatch[userID] = nextBatchToken -} - -// LoadNextBatch from memory. -func (s *InMemoryStore) LoadNextBatch(userID string) string { - return s.NextBatch[userID] -} - -// SaveRoom to memory. -func (s *InMemoryStore) SaveRoom(room *Room) { - s.Rooms[room.ID] = room -} - -// LoadRoom from memory. -func (s *InMemoryStore) LoadRoom(roomID string) *Room { - return s.Rooms[roomID] -} - -// NewInMemoryStore constructs a new InMemoryStore. -func NewInMemoryStore() *InMemoryStore { - return &InMemoryStore{ - Filters: make(map[string]string), - NextBatch: make(map[string]string), - Rooms: make(map[string]*Room), - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/sync.go b/vendor/github.com/matrix-org/gomatrix/sync.go deleted file mode 100644 index ac326c3a47..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/sync.go +++ /dev/null @@ -1,168 +0,0 @@ -package gomatrix - -import ( - "encoding/json" - "fmt" - "runtime/debug" - "time" -) - -// Syncer represents an interface that must be satisfied in order to do /sync requests on a client. -type Syncer interface { - // Process the /sync response. The since parameter is the since= value that was used to produce the response. - // This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped - // permanently. - ProcessResponse(resp *RespSync, since string) error - // OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently. - OnFailedSync(res *RespSync, err error) (time.Duration, error) - // GetFilterJSON for the given user ID. NOT the filter ID. - GetFilterJSON(userID string) json.RawMessage -} - -// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively -// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer -// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information. -type DefaultSyncer struct { - UserID string - Store Storer - listeners map[string][]OnEventListener // event type to listeners array -} - -// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events. -type OnEventListener func(*Event) - -// NewDefaultSyncer returns an instantiated DefaultSyncer -func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer { - return &DefaultSyncer{ - UserID: userID, - Store: store, - listeners: make(map[string][]OnEventListener), - } -} - -// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of -// unrepeating events. Returns a fatal error if a listener panics. -func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) { - if !s.shouldProcessResponse(res, since) { - return - } - - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack()) - } - }() - - for roomID, roomData := range res.Rooms.Join { - room := s.getOrCreateRoom(roomID) - for _, event := range roomData.State.Events { - event.RoomID = roomID - room.UpdateState(&event) - s.notifyListeners(&event) - } - for _, event := range roomData.Timeline.Events { - event.RoomID = roomID - s.notifyListeners(&event) - } - for _, event := range roomData.Ephemeral.Events { - event.RoomID = roomID - s.notifyListeners(&event) - } - } - for roomID, roomData := range res.Rooms.Invite { - room := s.getOrCreateRoom(roomID) - for _, event := range roomData.State.Events { - event.RoomID = roomID - room.UpdateState(&event) - s.notifyListeners(&event) - } - } - for roomID, roomData := range res.Rooms.Leave { - room := s.getOrCreateRoom(roomID) - for _, event := range roomData.Timeline.Events { - if event.StateKey != nil { - event.RoomID = roomID - room.UpdateState(&event) - s.notifyListeners(&event) - } - } - } - return -} - -// OnEventType allows callers to be notified when there are new events for the given event type. -// There are no duplicate checks. -func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) { - _, exists := s.listeners[eventType] - if !exists { - s.listeners[eventType] = []OnEventListener{} - } - s.listeners[eventType] = append(s.listeners[eventType], callback) -} - -// shouldProcessResponse returns true if the response should be processed. May modify the response to remove -// stuff that shouldn't be processed. -func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool { - if since == "" { - return false - } - // This is a horrible hack because /sync will return the most recent messages for a room - // as soon as you /join it. We do NOT want to process those events in that particular room - // because they may have already been processed (if you toggle the bot in/out of the room). - // - // Work around this by inspecting each room's timeline and seeing if an m.room.member event for us - // exists and is "join" and then discard processing that room entirely if so. - // TODO: We probably want to process messages from after the last join event in the timeline. - for roomID, roomData := range resp.Rooms.Join { - for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- { - e := roomData.Timeline.Events[i] - if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID { - m := e.Content["membership"] - mship, ok := m.(string) - if !ok { - continue - } - if mship == "join" { - _, ok := resp.Rooms.Join[roomID] - if !ok { - continue - } - delete(resp.Rooms.Join, roomID) // don't re-process messages - delete(resp.Rooms.Invite, roomID) // don't re-process invites - break - } - } - } - } - return true -} - -// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse() -func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room { - room := s.Store.LoadRoom(roomID) - if room == nil { // create a new Room - room = NewRoom(roomID) - s.Store.SaveRoom(room) - } - return room -} - -func (s *DefaultSyncer) notifyListeners(event *Event) { - listeners, exists := s.listeners[event.Type] - if !exists { - return - } - for _, fn := range listeners { - fn(event) - } -} - -// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error. -func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) { - return 10 * time.Second, nil -} - -// GetFilterJSON returns a filter with a timeline limit of 50. -func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage { - return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`) -} diff --git a/vendor/github.com/matrix-org/gomatrix/tags.go b/vendor/github.com/matrix-org/gomatrix/tags.go deleted file mode 100644 index 956fb11e99..0000000000 --- a/vendor/github.com/matrix-org/gomatrix/tags.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 Sumukha PK -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomatrix - -// TagContent contains the data for an m.tag message type -// https://matrix.org/docs/spec/client_server/r0.4.0.html#m-tag -type TagContent struct { - Tags map[string]TagProperties `json:"tags"` -} - -// TagProperties contains the properties of a Tag -type TagProperties struct { - Order float32 `json:"order,omitempty"` // Empty values must be neglected -} diff --git a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go new file mode 100644 index 0000000000..593f653008 --- /dev/null +++ b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go @@ -0,0 +1,77 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC +2898 / PKCS #5 v2.0. + +A key derivation function is useful when encrypting data based on a password +or any other not-fully-random data. It uses a pseudorandom function to derive +a secure encryption key based on the password. + +While v2.0 of the standard defines only one pseudorandom function to use, +HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved +Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To +choose, you can pass the `New` functions from the different SHA packages to +pbkdf2.Key. +*/ +package pbkdf2 // import "golang.org/x/crypto/pbkdf2" + +import ( + "crypto/hmac" + "hash" +) + +// Key derives a key from the password, salt and iteration count, returning a +// []byte of length keylen that can be used as cryptographic key. The key is +// derived based on the method described as PBKDF2 with the HMAC variant using +// the supplied hash function. +// +// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you +// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by +// doing: +// +// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New) +// +// Remember to get a good random salt. At least 8 bytes is recommended by the +// RFC. +// +// Using a higher iteration count will increase the cost of an exhaustive +// search but will also make derivation proportionally slower. +func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLen] +} diff --git a/vendor/maunium.net/go/mautrix/.gitignore b/vendor/maunium.net/go/mautrix/.gitignore new file mode 100644 index 0000000000..66f8fb5027 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.vscode/ diff --git a/vendor/maunium.net/go/mautrix/LICENSE b/vendor/maunium.net/go/mautrix/LICENSE new file mode 100644 index 0000000000..52d135112e --- /dev/null +++ b/vendor/maunium.net/go/mautrix/LICENSE @@ -0,0 +1,374 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + 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/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/vendor/maunium.net/go/mautrix/README.md b/vendor/maunium.net/go/mautrix/README.md new file mode 100644 index 0000000000..5a893ddf73 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/README.md @@ -0,0 +1,24 @@ +# mautrix-go +[![GoDoc](https://godoc.org/maunium.net/go/mautrix?status.svg)](https://godoc.org/maunium.net/go/mautrix) + +A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/client/gomuks), +[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/tulir/mautrix-whatsapp) +and others. + +Matrix room: [`#maunium:maunium.net`](https://matrix.to/#/#maunium:maunium.net) + +This project is based on [matrix-org/gomatrix](https://github.com/matrix-org/gomatrix). +The original project is licensed under [Apache 2.0](https://github.com/matrix-org/gomatrix/blob/master/LICENSE). + +In addition to the basic client API features the original project has, this framework also has: + +* Appservice support (Intent API like mautrix-python, room state storage, etc) +* End-to-end encryption support (incl. interactive SAS verification) +* Structs for parsing event content +* Helpers for parsing and generating Matrix HTML +* Helpers for handling push rules + +This project contains modules that are licensed under Apache 2.0: + +* [maunium.net/go/mautrix/crypto/canonicaljson](crypto/canonicaljson) +* [maunium.net/go/mautrix/crypto/olm](crypto/olm) diff --git a/vendor/maunium.net/go/mautrix/client.go b/vendor/maunium.net/go/mautrix/client.go new file mode 100644 index 0000000000..8f78176d09 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/client.go @@ -0,0 +1,1344 @@ +// Package mautrix implements the Matrix Client-Server API. +// +// Specification can be found at http://matrix.org/docs/spec/client_server/r0.4.0.html +package mautrix + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + "sync/atomic" + "time" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "maunium.net/go/mautrix/pushrules" +) + +type Logger interface { + Debugfln(message string, args ...interface{}) +} + +type WarnLogger interface { + Logger + Warnfln(message string, args ...interface{}) +} + +type Stringifiable interface { + String() string +} + +// Client represents a Matrix client. +type Client struct { + HomeserverURL *url.URL // The base homeserver URL + Prefix URLPath // The API prefix eg '/_matrix/client/r0' + UserID id.UserID // The user ID of the client. Used for forming HTTP paths which use the client's user ID. + DeviceID id.DeviceID // The device ID of the client. + AccessToken string // The access_token for the client. + UserAgent string // The value for the User-Agent header + Client *http.Client // The underlying HTTP client which will be used to make HTTP requests. + Syncer Syncer // The thing which can process /sync responses + Store Storer // The thing which can store rooms/tokens/ids + Logger Logger + SyncPresence event.Presence + + // Number of times that mautrix will retry any HTTP request + // if the request fails entirely or returns a HTTP gateway error (502-504) + DefaultHTTPRetries int + + txnID int32 + + // The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty, + // no user_id parameter will be sent. + // See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion + AppServiceUserID id.UserID + + syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time. +} + +type ClientWellKnown struct { + Homeserver HomeserverInfo `json:"m.homeserver"` + IdentityServer IdentityServerInfo `json:"m.identity_server"` +} + +type HomeserverInfo struct { + BaseURL string `json:"base_url"` +} + +type IdentityServerInfo struct { + BaseURL string `json:"base_url"` +} + +// DiscoverClientAPI resolves the client API URL from a Matrix server name. +// Use ParseUserID to extract the server name from a user ID. +// https://matrix.org/docs/spec/client_server/r0.6.0#server-discovery +func DiscoverClientAPI(serverName string) (*ClientWellKnown, error) { + wellKnownURL := url.URL{ + Scheme: "https", + Host: serverName, + Path: "/.well-known/matrix/client", + } + + req, err := http.NewRequest("GET", wellKnownURL.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", DefaultUserAgent+" .well-known fetcher") + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, nil + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var wellKnown ClientWellKnown + err = json.Unmarshal(data, &wellKnown) + if err != nil { + return nil, errors.New(".well-known response not JSON") + } + + return &wellKnown, nil +} + +// BuildURL builds a URL with the Client's homserver/prefix/access_token set already. +func (cli *Client) BuildURL(urlPath ...interface{}) string { + return cli.BuildBaseURL(append(cli.Prefix, urlPath...)...) +} + +// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must +// supply the prefix in the path. +func (cli *Client) BuildBaseURL(urlPath ...interface{}) string { + // copy the URL. Purposefully ignore error as the input is from a valid URL already + hsURL, _ := url.Parse(cli.HomeserverURL.String()) + if hsURL.Scheme == "" { + hsURL.Scheme = "https" + } + rawParts := make([]string, len(urlPath)+1) + rawParts[0] = hsURL.RawPath + parts := make([]string, len(urlPath)+1) + parts[0] = hsURL.Path + for i, part := range urlPath { + switch casted := part.(type) { + case string: + parts[i+1] = casted + case int: + parts[i+1] = strconv.Itoa(casted) + case Stringifiable: + parts[i+1] = casted.String() + default: + parts[i+1] = fmt.Sprint(casted) + } + rawParts[i+1] = url.PathEscape(parts[i+1]) + } + hsURL.Path = strings.Join(parts, "/") + hsURL.RawPath = strings.Join(rawParts, "/") + query := hsURL.Query() + if cli.AppServiceUserID != "" { + query.Set("user_id", string(cli.AppServiceUserID)) + } + hsURL.RawQuery = query.Encode() + return hsURL.String() +} + +type URLPath = []interface{} + +// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already. +func (cli *Client) BuildURLWithQuery(urlPath URLPath, urlQuery map[string]string) string { + u, _ := url.Parse(cli.BuildURL(urlPath...)) + q := u.Query() + for k, v := range urlQuery { + q.Set(k, v) + } + u.RawQuery = q.Encode() + return u.String() +} + +// SetCredentials sets the user ID and access token on this client instance. +// +// Deprecated: use the StoreCredentials field in ReqLogin instead. +func (cli *Client) SetCredentials(userID id.UserID, accessToken string) { + cli.AccessToken = accessToken + cli.UserID = userID +} + +// ClearCredentials removes the user ID and access token on this client instance. +func (cli *Client) ClearCredentials() { + cli.AccessToken = "" + cli.UserID = "" + cli.DeviceID = "" +} + +// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the +// error will be nil. +// +// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine. +// Fatal sync errors can be caused by: +// - The failure to create a filter. +// - Client.Syncer.OnFailedSync returning an error in response to a failed sync. +// - Client.Syncer.ProcessResponse returning an error. +// If you wish to continue retrying in spite of these fatal errors, call Sync() again. +func (cli *Client) Sync() error { + return cli.SyncWithContext(context.Background()) +} + +func (cli *Client) SyncWithContext(ctx context.Context) error { + // Mark the client as syncing. + // We will keep syncing until the syncing state changes. Either because + // Sync is called or StopSync is called. + syncingID := cli.incrementSyncingID() + nextBatch := cli.Store.LoadNextBatch(cli.UserID) + filterID := cli.Store.LoadFilterID(cli.UserID) + if filterID == "" { + filterJSON := cli.Syncer.GetFilterJSON(cli.UserID) + resFilter, err := cli.CreateFilter(filterJSON) + if err != nil { + return err + } + filterID = resFilter.FilterID + cli.Store.SaveFilterID(cli.UserID, filterID) + } + for { + resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, cli.SyncPresence, ctx) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + duration, err2 := cli.Syncer.OnFailedSync(resSync, err) + if err2 != nil { + return err2 + } + time.Sleep(duration) + continue + } + + // Check that the syncing state hasn't changed + // Either because we've stopped syncing or another sync has been started. + // We discard the response from our sync. + if cli.getSyncingID() != syncingID { + return nil + } + + // Save the token now *before* processing it. This means it's possible + // to not process some events, but it means that we won't get constantly stuck processing + // a malformed/buggy event which keeps making us panic. + cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch) + if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil { + return err + } + + nextBatch = resSync.NextBatch + } +} + +func (cli *Client) incrementSyncingID() uint32 { + return atomic.AddUint32(&cli.syncingID, 1) +} + +func (cli *Client) getSyncingID() uint32 { + return atomic.LoadUint32(&cli.syncingID) +} + +// StopSync stops the ongoing sync started by Sync. +func (cli *Client) StopSync() { + // Advance the syncing state so that any running Syncs will terminate. + cli.incrementSyncingID() +} + +const logBodyContextKey = "fi.mau.mautrix.log_body" +const logRequestIDContextKey = "fi.mau.mautrix.request_id" + +func (cli *Client) LogRequest(req *http.Request) { + if cli.Logger == nil { + return + } + body, ok := req.Context().Value(logBodyContextKey).(string) + reqID, _ := req.Context().Value(logRequestIDContextKey).(int) + if ok && len(body) > 0 { + cli.Logger.Debugfln("req #%d: %s %s %s", reqID, req.Method, req.URL.String(), body) + } else { + cli.Logger.Debugfln("req #%d: %s %s", reqID, req.Method, req.URL.String()) + } +} + +func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) { + return cli.MakeFullRequest(FullRequest{Method: method, URL: httpURL, RequestJSON: reqBody, ResponseJSON: resBody}) +} + +type FullRequest struct { + Method string + URL string + Headers http.Header + RequestJSON interface{} + RequestBody io.Reader + RequestLength int64 + ResponseJSON interface{} + Context context.Context + MaxAttempts int +} + +var requestID int32 + +func (params *FullRequest) compileRequest() (*http.Request, error) { + var logBody string + reqBody := params.RequestBody + if params.Context == nil { + params.Context = context.Background() + } + if params.RequestJSON != nil { + jsonStr, err := json.Marshal(params.RequestJSON) + if err != nil { + return nil, HTTPError{ + Message: "failed to marshal JSON", + WrappedError: err, + } + } + logBody = string(jsonStr) + reqBody = bytes.NewBuffer(jsonStr) + } else if params.RequestLength > 0 && params.RequestBody != nil { + logBody = fmt.Sprintf("%d bytes", params.RequestLength) + } + ctx := context.WithValue(params.Context, logBodyContextKey, logBody) + reqID := atomic.AddInt32(&requestID, 1) + ctx = context.WithValue(ctx, logRequestIDContextKey, int(reqID)) + req, err := http.NewRequestWithContext(ctx, params.Method, params.URL, reqBody) + if err != nil { + return nil, HTTPError{ + Message: "failed to create request", + WrappedError: err, + } + } + if params.Headers != nil { + req.Header = params.Headers + } + if params.RequestJSON != nil { + req.Header.Set("Content-Type", "application/json") + } + if params.RequestLength > 0 && params.RequestBody != nil { + req.ContentLength = params.RequestLength + } + return req, nil +} + +// MakeFullRequest makes a JSON HTTP request to the given URL. +// If "resBody" is not nil, the response body will be json.Unmarshalled into it. +// +// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along +// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned +// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError. +func (cli *Client) MakeFullRequest(params FullRequest) ([]byte, error) { + if params.MaxAttempts == 0 { + params.MaxAttempts = 1 + cli.DefaultHTTPRetries + } + req, err := params.compileRequest() + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", cli.UserAgent) + if len(cli.AccessToken) > 0 { + req.Header.Set("Authorization", "Bearer "+cli.AccessToken) + } + return cli.executeCompiledRequest(req, params.MaxAttempts-1, 4*time.Second, params.ResponseJSON) +} + +func (cli *Client) logWarning(format string, args ...interface{}) { + warnLogger, ok := cli.Logger.(WarnLogger) + if ok { + warnLogger.Warnfln(format, args...) + } else { + cli.Logger.Debugfln(format, args...) + } +} + +func (cli *Client) doRetry(req *http.Request, cause error, retries int, backoff time.Duration, responseJSON interface{}) ([]byte, error) { + reqID, _ := req.Context().Value(logRequestIDContextKey).(int) + if req.Body != nil { + if req.GetBody == nil { + cli.logWarning("Failed to get new body to retry request #%d: GetBody is nil", reqID) + return nil, cause + } + var err error + req.Body, err = req.GetBody() + if err != nil { + cli.logWarning("Failed to get new body to retry request #%d: %v", reqID, err) + return nil, cause + } + } + cli.logWarning("Request #%d failed: %v, retrying in %d seconds", reqID, cause, int(backoff.Seconds())) + time.Sleep(backoff) + return cli.executeCompiledRequest(req, retries-1, backoff*2, responseJSON) +} + +func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backoff time.Duration, responseJSON interface{}) ([]byte, error) { + cli.LogRequest(req) + res, err := cli.Client.Do(req) + if res != nil { + defer res.Body.Close() + } + if err != nil { + if retries > 0 { + return cli.doRetry(req, err, retries, backoff, responseJSON) + } + return nil, HTTPError{ + Request: req, + Response: res, + + Message: "request error", + WrappedError: err, + } + } + + if retries > 0 && (res.StatusCode == http.StatusBadGateway || res.StatusCode == http.StatusServiceUnavailable || res.StatusCode == http.StatusGatewayTimeout) { + return cli.doRetry(req, fmt.Errorf("HTTP %d", res.StatusCode), retries, backoff, responseJSON) + } + + contents, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, HTTPError{ + Request: req, + Response: res, + + Message: "failed to read response body", + WrappedError: err, + } + } + + if res.StatusCode < 200 || res.StatusCode >= 300 { + respErr := &RespError{} + if _ = json.Unmarshal(contents, respErr); respErr.ErrCode == "" { + respErr = nil + } + + return contents, HTTPError{ + Request: req, + Response: res, + RespError: respErr, + } + } + + if responseJSON == nil { + return contents, nil + } + + if err = json.Unmarshal(contents, &responseJSON); err != nil { + return nil, HTTPError{ + Request: req, + Response: res, + + Message: "failed to unmarshal response body", + ResponseBody: string(contents), + WrappedError: err, + } + } + + return contents, nil +} + +// Whoami gets the user ID of the current user. See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-join +func (cli *Client) Whoami() (resp *RespWhoami, err error) { + urlPath := cli.BuildURL("account", "whoami") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter +func (cli *Client) CreateFilter(filter *Filter) (resp *RespCreateFilter, err error) { + urlPath := cli.BuildURL("user", cli.UserID, "filter") + _, err = cli.MakeRequest("POST", urlPath, filter, &resp) + return +} + +// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync +func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence event.Presence, ctx context.Context) (resp *RespSync, err error) { + query := map[string]string{ + "timeout": strconv.Itoa(timeout), + } + if since != "" { + query["since"] = since + } + if filterID != "" { + query["filter"] = filterID + } + if setPresence != "" { + query["set_presence"] = string(setPresence) + } + if fullState { + query["full_state"] = "true" + } + urlPath := cli.BuildURLWithQuery(URLPath{"sync"}, query) + _, err = cli.MakeFullRequest(FullRequest{ + Method: http.MethodGet, + URL: urlPath, + ResponseJSON: &resp, + Context: ctx, + // We don't want automatic retries for SyncRequest, the Sync() wrapper handles those. + MaxAttempts: 1, + }) + return +} + +func (cli *Client) register(url string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { + var bodyBytes []byte + bodyBytes, err = cli.MakeRequest("POST", url, req, nil) + if err != nil { + httpErr, ok := err.(HTTPError) + // if response has a 401 status, but doesn't have the errcode field, it's probably a UIA response. + if ok && httpErr.IsStatus(http.StatusUnauthorized) && httpErr.RespError == nil { + err = json.Unmarshal(bodyBytes, &uiaResp) + } + } else { + // body should be RespRegister + err = json.Unmarshal(bodyBytes, &resp) + } + return +} + +// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// +// Registers with kind=user. For kind=guest, see RegisterGuest. +func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { + u := cli.BuildURL("register") + return cli.register(u, req) +} + +// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// with kind=guest. +// +// For kind=user, see Register. +func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { + query := map[string]string{ + "kind": "guest", + } + u := cli.BuildURLWithQuery(URLPath{"register"}, query) + return cli.register(u, req) +} + +// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth +// +// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration +// this way. If the homeserver does not, an error is returned. +// +// This does not set credentials on the client instance. See SetCredentials() instead. +// +// res, err := cli.RegisterDummy(&mautrix.ReqRegister{ +// Username: "alice", +// Password: "wonderland", +// }) +// if err != nil { +// panic(err) +// } +// token := res.AccessToken +func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { + res, uia, err := cli.Register(req) + if err != nil && uia == nil { + return nil, err + } else if uia == nil { + return nil, errors.New("server did not return user-interactive auth flows") + } else if !uia.HasSingleStageFlow(AuthTypeDummy) { + return nil, errors.New("server does not support m.login.dummy") + } + req.Auth = BaseAuthData{Type: AuthTypeDummy, Session: uia.Session} + res, _, err = cli.Register(req) + if err != nil { + return nil, err + } + return res, nil +} + +func (cli *Client) GetLoginFlows() (resp *RespLoginFlows, err error) { + urlPath := cli.BuildURL("login") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) { + urlPath := cli.BuildURL("login") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + if req.StoreCredentials && err == nil { + cli.DeviceID = resp.DeviceID + cli.AccessToken = resp.AccessToken + cli.UserID = resp.UserID + // TODO update cli.HomeserverURL based on the .well-known data in the login response + } + return +} + +// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout +// This does not clear the credentials from the client instance. See ClearCredentials() instead. +func (cli *Client) Logout() (resp *RespLogout, err error) { + urlPath := cli.BuildURL("logout") + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +// LogoutAll logs out all the devices of the current user. See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-logout-all +// This does not clear the credentials from the client instance. See ClearCredentials() instead. +func (cli *Client) LogoutAll() (resp *RespLogout, err error) { + urlPath := cli.BuildURL("logout", "all") + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-versions +func (cli *Client) Versions() (resp *RespVersions, err error) { + urlPath := cli.BuildBaseURL("_matrix", "client", "versions") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.6.1.html#post-matrix-client-r0-join-roomidoralias +// +// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will +// be JSON encoded and used as the request body. +func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) { + var urlPath string + if serverName != "" { + urlPath = cli.BuildURLWithQuery(URLPath{"join", roomIDorAlias}, map[string]string{ + "server_name": serverName, + }) + } else { + urlPath = cli.BuildURL("join", roomIDorAlias) + } + _, err = cli.MakeRequest("POST", urlPath, content, &resp) + return +} + +// JoinRoomByID joins the client to a room ID. See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-join +// +// Unlike JoinRoom, this method can only be used to join rooms that the server already knows about. +// It's mostly intended for bridges and other things where it's already certain that the server is in the room. +func (cli *Client) JoinRoomByID(roomID id.RoomID) (resp *RespJoinRoom, err error) { + _, err = cli.MakeRequest("POST", cli.BuildURL("rooms", roomID, "join"), nil, &resp) + return +} + +// GetDisplayName returns the display name of the user with the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-displayname +func (cli *Client) GetDisplayName(mxid id.UserID) (resp *RespUserDisplayName, err error) { + urlPath := cli.BuildURL("profile", mxid, "displayname") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-displayname +func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) { + return cli.GetDisplayName(cli.UserID) +} + +// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.6.1.html#put-matrix-client-r0-profile-userid-displayname +func (cli *Client) SetDisplayName(displayName string) (err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "displayname") + s := struct { + DisplayName string `json:"displayname"` + }{displayName} + _, err = cli.MakeRequest("PUT", urlPath, &s, nil) + return +} + +// GetAvatarURL gets the avatar URL of the user with the specified MXID. See http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) GetAvatarURL(mxid id.UserID) (url id.ContentURI, err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") + s := struct { + AvatarURL id.ContentURI `json:"avatar_url"` + }{} + + _, err = cli.MakeRequest("GET", urlPath, nil, &s) + if err != nil { + return + } + url = s.AvatarURL + return +} + +// GetOwnAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) GetOwnAvatarURL() (url id.ContentURI, err error) { + return cli.GetAvatarURL(cli.UserID) +} + +// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.6.1.html#put-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) SetAvatarURL(url id.ContentURI) (err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") + s := struct { + AvatarURL string `json:"avatar_url"` + }{url.String()} + _, err = cli.MakeRequest("PUT", urlPath, &s, nil) + if err != nil { + return err + } + + return nil +} + +// GetAccountData gets the user's account data of this type. See https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-account-data-type +func (cli *Client) GetAccountData(name string, output interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "account_data", name) + _, err = cli.MakeRequest("GET", urlPath, nil, output) + return +} + +// SetAccountData sets the user's account data of this type. See https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-account-data-type +func (cli *Client) SetAccountData(name string, data interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "account_data", name) + _, err = cli.MakeRequest("PUT", urlPath, &data, nil) + if err != nil { + return err + } + + return nil +} + +func (cli *Client) GetRoomAccountData(roomID id.RoomID, name string, output interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "account_data", name) + _, err = cli.MakeRequest("GET", urlPath, nil, output) + return +} + +func (cli *Client) SetRoomAccountData(roomID id.RoomID, name string, data interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "account_data", name) + _, err = cli.MakeRequest("PUT", urlPath, &data, nil) + if err != nil { + return err + } + + return nil +} + +type ReqSendEvent struct { + Timestamp int64 + TransactionID string + + ParentID string + RelType event.RelationType +} + +// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid +// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. +func (cli *Client) SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...ReqSendEvent) (resp *RespSendEvent, err error) { + var req ReqSendEvent + if len(extra) > 0 { + req = extra[0] + } + + var txnID string + if len(req.TransactionID) > 0 { + txnID = req.TransactionID + } else { + txnID = cli.TxnID() + } + + queryParams := map[string]string{} + if req.Timestamp > 0 { + queryParams["ts"] = strconv.FormatInt(req.Timestamp, 10) + } + + urlData := URLPath{"rooms", roomID, "send", eventType.String(), txnID} + if len(req.ParentID) > 0 { + urlData = URLPath{"rooms", roomID, "send_relation", req.ParentID, req.RelType, eventType.String(), txnID} + } + + urlPath := cli.BuildURLWithQuery(urlData, queryParams) + _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) + return +} + +// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey +// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. +func (cli *Client) SendStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) { + urlPath := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey) + _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) + return +} + +// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey +// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. +func (cli *Client) SendMassagedStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) { + urlPath := cli.BuildURLWithQuery(URLPath{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{ + "ts": strconv.FormatInt(ts, 10), + }) + _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) + return +} + +// SendText sends an m.room.message event into the given room with a msgtype of m.text +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text +func (cli *Client) SendText(roomID id.RoomID, text string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgText, + Body: text, + }) +} + +// SendImage sends an m.room.message event into the given room with a msgtype of m.image +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image +func (cli *Client) SendImage(roomID id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgImage, + Body: body, + URL: url.CUString(), + }) +} + +// SendVideo sends an m.room.message event into the given room with a msgtype of m.video +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video +func (cli *Client) SendVideo(roomID id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgVideo, + Body: body, + URL: url.CUString(), + }) +} + +// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice +func (cli *Client) SendNotice(roomID id.RoomID, text string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: text, + }) +} + +func (cli *Client) SendReaction(roomID id.RoomID, eventID id.EventID, reaction string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventReaction, &event.ReactionEventContent{ + RelatesTo: event.RelatesTo{ + EventID: eventID, + Type: event.RelAnnotation, + Key: reaction, + }, + }) +} + +// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid +func (cli *Client) RedactEvent(roomID id.RoomID, eventID id.EventID, extra ...ReqRedact) (resp *RespSendEvent, err error) { + req := ReqRedact{} + if len(extra) > 0 { + req = extra[0] + } + var txnID string + if len(req.TxnID) > 0 { + txnID = req.TxnID + } else { + txnID = cli.TxnID() + } + urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID) + _, err = cli.MakeRequest("PUT", urlPath, req, &resp) + return +} + +// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +// resp, err := cli.CreateRoom(&mautrix.ReqCreateRoom{ +// Preset: "public_chat", +// }) +// fmt.Println("Room:", resp.RoomID) +func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) { + urlPath := cli.BuildURL("createRoom") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave +func (cli *Client) LeaveRoom(roomID id.RoomID) (resp *RespLeaveRoom, err error) { + u := cli.BuildURL("rooms", roomID, "leave") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget +func (cli *Client) ForgetRoom(roomID id.RoomID) (resp *RespForgetRoom, err error) { + u := cli.BuildURL("rooms", roomID, "forget") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +func (cli *Client) InviteUser(roomID id.RoomID, req *ReqInviteUser) (resp *RespInviteUser, err error) { + u := cli.BuildURL("rooms", roomID, "invite") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint +func (cli *Client) InviteUserByThirdParty(roomID id.RoomID, req *ReqInvite3PID) (resp *RespInviteUser, err error) { + u := cli.BuildURL("rooms", roomID, "invite") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +func (cli *Client) KickUser(roomID id.RoomID, req *ReqKickUser) (resp *RespKickUser, err error) { + u := cli.BuildURL("rooms", roomID, "kick") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +func (cli *Client) BanUser(roomID id.RoomID, req *ReqBanUser) (resp *RespBanUser, err error) { + u := cli.BuildURL("rooms", roomID, "ban") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +func (cli *Client) UnbanUser(roomID id.RoomID, req *ReqUnbanUser) (resp *RespUnbanUser, err error) { + u := cli.BuildURL("rooms", roomID, "unban") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +func (cli *Client) UserTyping(roomID id.RoomID, typing bool, timeout int64) (resp *RespTyping, err error) { + req := ReqTyping{Typing: typing, Timeout: timeout} + u := cli.BuildURL("rooms", roomID, "typing", cli.UserID) + _, err = cli.MakeRequest("PUT", u, req, &resp) + return +} + +// GetPresence gets the presence of the user with the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status +func (cli *Client) GetPresence(userID id.UserID) (resp *RespPresence, err error) { + resp = new(RespPresence) + u := cli.BuildURL("presence", userID, "status") + _, err = cli.MakeRequest("GET", u, nil, resp) + return +} + +// GetOwnPresence gets the user's presence. See https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status +func (cli *Client) GetOwnPresence() (resp *RespPresence, err error) { + return cli.GetPresence(cli.UserID) +} + +func (cli *Client) SetPresence(status event.Presence) (err error) { + req := ReqPresence{Presence: status} + u := cli.BuildURL("presence", cli.UserID, "status") + _, err = cli.MakeRequest("PUT", u, req, nil) + return +} + +// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with +// the HTTP response body, or return an error. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey +func (cli *Client) StateEvent(roomID id.RoomID, eventType event.Type, stateKey string, outContent interface{}) (err error) { + u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey) + _, err = cli.MakeRequest("GET", u, nil, outContent) + return +} + +// UploadLink uploads an HTTP URL and then returns an MXC URI. +func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) { + res, err := cli.Client.Get(link) + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength) +} + +func (cli *Client) GetDownloadURL(mxcURL id.ContentURI) string { + return cli.BuildBaseURL("_matrix", "media", "r0", "download", mxcURL.Homeserver, mxcURL.FileID) +} + +func (cli *Client) Download(mxcURL id.ContentURI) (io.ReadCloser, error) { + resp, err := cli.Client.Get(cli.GetDownloadURL(mxcURL)) + if err != nil { + return nil, err + } + return resp.Body, nil +} + +func (cli *Client) DownloadBytes(mxcURL id.ContentURI) ([]byte, error) { + resp, err := cli.Download(mxcURL) + if err != nil { + return nil, err + } + defer resp.Close() + return ioutil.ReadAll(resp) +} + +func (cli *Client) UploadBytes(data []byte, contentType string) (*RespMediaUpload, error) { + return cli.UploadBytesWithName(data, contentType, "") +} + +func (cli *Client) UploadBytesWithName(data []byte, contentType, fileName string) (*RespMediaUpload, error) { + return cli.UploadMedia(ReqUploadMedia{ + Content: bytes.NewReader(data), + ContentLength: int64(len(data)), + ContentType: contentType, + FileName: fileName, + }) +} + +// Upload uploads the given data to the content repository and returns an MXC URI. +// +// Deprecated: UploadMedia should be used instead. +func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) { + return cli.UploadMedia(ReqUploadMedia{ + Content: content, + ContentLength: contentLength, + ContentType: contentType, + }) +} + +type ReqUploadMedia struct { + Content io.Reader + ContentLength int64 + ContentType string + FileName string +} + +// UploadMedia uploads the given data to the content repository and returns an MXC URI. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload +func (cli *Client) UploadMedia(data ReqUploadMedia) (*RespMediaUpload, error) { + u, _ := url.Parse(cli.BuildBaseURL("_matrix", "media", "r0", "upload")) + if len(data.FileName) > 0 { + q := u.Query() + q.Set("filename", data.FileName) + u.RawQuery = q.Encode() + } + + var headers http.Header + if len(data.ContentType) > 0 { + headers = http.Header{"Content-Type": []string{data.ContentType}} + } + + var m RespMediaUpload + _, err := cli.MakeFullRequest(FullRequest{ + Method: http.MethodPost, + URL: u.String(), + Headers: headers, + RequestBody: data.Content, + RequestLength: data.ContentLength, + ResponseJSON: &m, + }) + return &m, err +} + +// JoinedMembers returns a map of joined room members. See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +// +// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. +// This API is primarily designed for application services which may want to efficiently look up joined members in a room. +func (cli *Client) JoinedMembers(roomID id.RoomID) (resp *RespJoinedMembers, err error) { + u := cli.BuildURL("rooms", roomID, "joined_members") + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +func (cli *Client) Members(roomID id.RoomID, req ...ReqMembers) (resp *RespMembers, err error) { + var extra ReqMembers + if len(req) > 0 { + extra = req[0] + } + query := map[string]string{} + if len(extra.At) > 0 { + query["at"] = extra.At + } + if len(extra.Membership) > 0 { + query["membership"] = string(extra.Membership) + } + if len(extra.NotMembership) > 0 { + query["not_membership"] = string(extra.NotMembership) + } + u := cli.BuildURLWithQuery(URLPath{"rooms", roomID, "members"}, query) + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +// JoinedRooms returns a list of rooms which the client is joined to. See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +// +// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. +// This API is primarily designed for application services which may want to efficiently look up joined rooms. +func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) { + u := cli.BuildURL("joined_rooms") + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +// Messages returns a list of message and state events for a room. It uses +// pagination query parameters to paginate history in the room. +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages +func (cli *Client) Messages(roomID id.RoomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) { + filter := cli.Syncer.GetFilterJSON(cli.UserID) + filterJSON, err := json.Marshal(filter) + if err != nil { + return nil, err + } + query := map[string]string{ + "from": from, + "dir": string(dir), + "filter": string(filterJSON), + } + if to != "" { + query["to"] = to + } + if limit != 0 { + query["limit"] = strconv.Itoa(limit) + } + + urlPath := cli.BuildURLWithQuery(URLPath{"rooms", roomID, "messages"}, query) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) GetEvent(roomID id.RoomID, eventID id.EventID) (resp *event.Event, err error) { + urlPath := cli.BuildURL("rooms", roomID, "event", eventID) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) MarkRead(roomID id.RoomID, eventID id.EventID) (err error) { + return cli.MarkReadWithContent(roomID, eventID, struct{}{}) +} + +// MarkReadWithContent sends a read receipt including custom data. +// N.B. This is not (yet) a part of the spec, normal servers will drop any extra content. +func (cli *Client) MarkReadWithContent(roomID id.RoomID, eventID id.EventID, content interface{}) (err error) { + urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID) + _, err = cli.MakeRequest("POST", urlPath, &content, nil) + return +} + +func (cli *Client) AddTag(roomID id.RoomID, tag string, order float64) error { + var tagData event.Tag + if order == order { + tagData.Order = json.Number(strconv.FormatFloat(order, 'e', -1, 64)) + } + return cli.AddTagWithCustomData(roomID, tag, tagData) +} + +func (cli *Client) AddTagWithCustomData(roomID id.RoomID, tag string, data interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags", tag) + _, err = cli.MakeRequest("PUT", urlPath, data, nil) + return +} + +func (cli *Client) GetTags(roomID id.RoomID) (tags event.TagEventContent, err error) { + err = cli.GetTagsWithCustomData(roomID, &tags) + return +} + +func (cli *Client) GetTagsWithCustomData(roomID id.RoomID, resp interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) RemoveTag(roomID id.RoomID, tag string) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags", tag) + _, err = cli.MakeRequest("DELETE", urlPath, nil, nil) + return +} + +// Deprecated: Synapse may not handle setting m.tag directly properly, so you should use the Add/RemoveTag methods instead. +func (cli *Client) SetTags(roomID id.RoomID, tags event.Tags) (err error) { + return cli.SetRoomAccountData(roomID, "m.tag", map[string]event.Tags{ + "tags": tags, + }) +} + +// TurnServer returns turn server details and credentials for the client to use when initiating calls. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver +func (cli *Client) TurnServer() (resp *RespTurnServer, err error) { + urlPath := cli.BuildURL("voip", "turnServer") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) CreateAlias(alias id.RoomAlias, roomID id.RoomID) (resp *RespAliasCreate, err error) { + urlPath := cli.BuildURL("directory", "room", alias) + _, err = cli.MakeRequest("PUT", urlPath, &ReqAliasCreate{RoomID: roomID}, &resp) + return +} + +func (cli *Client) ResolveAlias(alias id.RoomAlias) (resp *RespAliasResolve, err error) { + urlPath := cli.BuildURL("directory", "room", alias) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) DeleteAlias(alias id.RoomAlias) (resp *RespAliasDelete, err error) { + urlPath := cli.BuildURL("directory", "room", alias) + _, err = cli.MakeRequest("DELETE", urlPath, nil, &resp) + return +} + +func (cli *Client) UploadKeys(req *ReqUploadKeys) (resp *RespUploadKeys, err error) { + urlPath := cli.BuildURL("keys", "upload") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +func (cli *Client) QueryKeys(req *ReqQueryKeys) (resp *RespQueryKeys, err error) { + urlPath := cli.BuildURL("keys", "query") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +func (cli *Client) ClaimKeys(req *ReqClaimKeys) (resp *RespClaimKeys, err error) { + urlPath := cli.BuildURL("keys", "claim") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +func (cli *Client) GetKeyChanges(from, to string) (resp *RespKeyChanges, err error) { + urlPath := cli.BuildURLWithQuery(URLPath{"keys", "changes"}, map[string]string{ + "from": from, + "to": to, + }) + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +func (cli *Client) SendToDevice(eventType event.Type, req *ReqSendToDevice) (resp *RespSendToDevice, err error) { + urlPath := cli.BuildURL("sendToDevice", eventType.String(), cli.TxnID()) + _, err = cli.MakeRequest("PUT", urlPath, req, &resp) + return +} + +func (cli *Client) GetDevicesInfo() (resp *RespDevicesInfo, err error) { + urlPath := cli.BuildURL("devices") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) GetDeviceInfo(deviceID id.DeviceID) (resp *RespDeviceInfo, err error) { + urlPath := cli.BuildURL("devices", deviceID) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) SetDeviceInfo(deviceID id.DeviceID, req *ReqDeviceInfo) error { + urlPath := cli.BuildURL("devices", deviceID) + _, err := cli.MakeRequest("PUT", urlPath, req, nil) + return err +} + +func (cli *Client) DeleteDevice(deviceID id.DeviceID, req *ReqDeleteDevice) error { + urlPath := cli.BuildURL("devices", deviceID) + _, err := cli.MakeRequest("DELETE", urlPath, req, nil) + return err +} + +func (cli *Client) DeleteDevices(req *ReqDeleteDevices) error { + urlPath := cli.BuildURL("delete_devices") + _, err := cli.MakeRequest("DELETE", urlPath, req, nil) + return err +} + +type UIACallback = func(*RespUserInteractive) interface{} + +// UploadCrossSigningKeys uploads the given cross-signing keys to the server. +// Because the endpoint requires user-interactive authentication a callback must be provided that, +// given the UI auth parameters, produces the required result (or nil to end the flow). +func (cli *Client) UploadCrossSigningKeys(keys *UploadCrossSigningKeysReq, uiaCallback UIACallback) error { + urlPath := cli.BuildBaseURL("_matrix", "client", "unstable", "keys", "device_signing", "upload") + content, err := cli.MakeRequest("POST", urlPath, keys, nil) + if respErr, ok := err.(HTTPError); ok && respErr.IsStatus(http.StatusUnauthorized) { + // try again with UI auth + var uiAuthResp RespUserInteractive + if err := json.Unmarshal(content, &uiAuthResp); err != nil { + return fmt.Errorf("failed to decode UIA response: %w", err) + } + auth := uiaCallback(&uiAuthResp) + if auth != nil { + keys.Auth = auth + return cli.UploadCrossSigningKeys(keys, uiaCallback) + } + } + return err +} + +func (cli *Client) UploadSignatures(req *ReqUploadSignatures) (resp *RespUploadSignatures, err error) { + urlPath := cli.BuildBaseURL("_matrix", "client", "unstable", "keys", "signatures", "upload") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +// GetPushRules returns the push notification rules for the global scope. +func (cli *Client) GetPushRules() (*pushrules.PushRuleset, error) { + return cli.GetScopedPushRules("global") +} + +// GetScopedPushRules returns the push notification rules for the given scope. +func (cli *Client) GetScopedPushRules(scope string) (resp *pushrules.PushRuleset, err error) { + u, _ := url.Parse(cli.BuildURL("pushrules", scope)) + // client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash. + u.Path += "/" + _, err = cli.MakeRequest("GET", u.String(), nil, &resp) + return +} + +func (cli *Client) GetPushRule(scope string, kind pushrules.PushRuleType, ruleID string) (resp *pushrules.PushRule, err error) { + urlPath := cli.BuildURL("pushrules", scope, kind, ruleID) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + if resp != nil { + resp.Type = kind + } + return +} + +func (cli *Client) DeletePushRule(scope string, kind pushrules.PushRuleType, ruleID string) error { + urlPath := cli.BuildURL("pushrules", scope, kind, ruleID) + _, err := cli.MakeRequest("DELETE", urlPath, nil, nil) + return err +} + +func (cli *Client) PutPushRule(scope string, kind pushrules.PushRuleType, ruleID string, req *ReqPutPushRule) error { + query := make(map[string]string) + if len(req.After) > 0 { + query["after"] = req.After + } + if len(req.Before) > 0 { + query["before"] = req.Before + } + urlPath := cli.BuildURLWithQuery(URLPath{"pushrules", scope, kind, ruleID}, query) + _, err := cli.MakeRequest("PUT", urlPath, req, nil) + return err +} + +// TxnID returns the next transaction ID. +func (cli *Client) TxnID() string { + txnID := atomic.AddInt32(&cli.txnID, 1) + return fmt.Sprintf("mautrix-go_%d_%d", time.Now().UnixNano(), txnID) +} + +// NewClient creates a new Matrix Client ready for syncing +func NewClient(homeserverURL string, userID id.UserID, accessToken string) (*Client, error) { + hsURL, err := url.Parse(homeserverURL) + if err != nil { + return nil, err + } + if hsURL.Scheme == "" { + hsURL.Scheme = "https" + } + return &Client{ + AccessToken: accessToken, + UserAgent: DefaultUserAgent, + HomeserverURL: hsURL, + UserID: userID, + Client: &http.Client{Timeout: 180 * time.Second}, + Prefix: URLPath{"_matrix", "client", "r0"}, + Syncer: NewDefaultSyncer(), + // By default, use an in-memory store which will never save filter ids / next batch tokens to disk. + // The client will work with this storer: it just won't remember across restarts. + // In practice, a database backend should be used. + Store: NewInMemoryStore(), + }, nil +} diff --git a/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go new file mode 100644 index 0000000000..170c55c92c --- /dev/null +++ b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go @@ -0,0 +1,171 @@ +// 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 attachment + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "errors" + "hash" + "io" + + "maunium.net/go/mautrix/crypto/utils" +) + +var ( + HashMismatch = errors.New("mismatching SHA-256 digest") + UnsupportedVersion = errors.New("unsupported Matrix file encryption version") + UnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm") + InvalidKey = errors.New("failed to decode key") + InvalidInitVector = errors.New("failed to decode initialization vector") + ReaderClosed = errors.New("encrypting reader was already closed") +) + +var ( + keyBase64Length = base64.RawURLEncoding.EncodedLen(utils.AESCTRKeyLength) + ivBase64Length = base64.RawStdEncoding.EncodedLen(utils.AESCTRIVLength) + hashBase64Length = base64.RawStdEncoding.EncodedLen(utils.SHAHashLength) +) + +type JSONWebKey struct { + Key string `json:"k"` + Algorithm string `json:"alg"` + Extractable bool `json:"ext"` + KeyType string `json:"kty"` + KeyOps []string `json:"key_ops"` +} + +type EncryptedFileHashes struct { + SHA256 string `json:"sha256"` +} + +type decodedKeys struct { + key [utils.AESCTRKeyLength]byte + iv [utils.AESCTRIVLength]byte +} + +type EncryptedFile struct { + Key JSONWebKey `json:"key"` + InitVector string `json:"iv"` + Hashes EncryptedFileHashes `json:"hashes"` + Version string `json:"v"` + + decoded *decodedKeys `json:"-"` +} + +func NewEncryptedFile() *EncryptedFile { + key, iv := utils.GenAttachmentA256CTR() + return &EncryptedFile{ + Key: JSONWebKey{ + Key: base64.RawURLEncoding.EncodeToString(key[:]), + Algorithm: "A256CTR", + Extractable: true, + KeyType: "oct", + KeyOps: []string{"encrypt", "decrypt"}, + }, + InitVector: base64.RawStdEncoding.EncodeToString(iv[:]), + Version: "v2", + + decoded: &decodedKeys{key, iv}, + } +} + +func (ef *EncryptedFile) decodeKeys() error { + if ef.decoded != nil { + return nil + } else if len(ef.Key.Key) != keyBase64Length { + return InvalidKey + } else if len(ef.InitVector) != ivBase64Length { + return InvalidInitVector + } + ef.decoded = &decodedKeys{} + _, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key)) + if err != nil { + return InvalidKey + } + _, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector)) + if err != nil { + return InvalidInitVector + } + return nil +} + +func (ef *EncryptedFile) Encrypt(plaintext []byte) []byte { + ef.decodeKeys() + ciphertext := utils.XorA256CTR(plaintext, ef.decoded.key, ef.decoded.iv) + checksum := sha256.Sum256(ciphertext) + ef.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(checksum[:]) + return ciphertext +} + +// encryptingReader is a variation of cipher.StreamReader that also hashes the content. +type encryptingReader struct { + stream cipher.Stream + hash hash.Hash + source io.Reader + file *EncryptedFile + closed bool +} + +func (r *encryptingReader) Read(dst []byte) (n int, err error) { + if r.closed { + return 0, ReaderClosed + } + n, err = r.source.Read(dst) + r.stream.XORKeyStream(dst[:n], dst[:n]) + r.hash.Write(dst[:n]) + return +} + +func (r *encryptingReader) Close() (err error) { + closer, ok := r.source.(io.ReadCloser) + if ok { + err = closer.Close() + } + r.file.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(r.hash.Sum(nil)) + r.closed = true + return +} + +func (ef *EncryptedFile) EncryptStream(reader io.Reader) io.ReadCloser { + ef.decodeKeys() + block, _ := aes.NewCipher(ef.decoded.key[:]) + return &encryptingReader{ + stream: cipher.NewCTR(block, ef.decoded.iv[:]), + hash: sha256.New(), + source: reader, + file: ef, + } +} + +func (ef *EncryptedFile) checkHash(ciphertext []byte) bool { + if len(ef.Hashes.SHA256) != hashBase64Length { + return false + } + var checksum [utils.SHAHashLength]byte + _, err := base64.RawStdEncoding.Decode(checksum[:], []byte(ef.Hashes.SHA256)) + if err != nil { + return false + } + return checksum == sha256.Sum256(ciphertext) +} + +func (ef *EncryptedFile) Decrypt(ciphertext []byte) ([]byte, error) { + if ef.Version != "v2" { + return nil, UnsupportedVersion + } else if ef.Key.Algorithm != "A256CTR" { + return nil, UnsupportedAlgorithm + } else if !ef.checkHash(ciphertext) { + return nil, HashMismatch + } else if err := ef.decodeKeys(); err != nil { + return nil, err + } else { + return utils.XorA256CTR(ciphertext, ef.decoded.key, ef.decoded.iv), nil + } +} diff --git a/vendor/maunium.net/go/mautrix/crypto/utils/utils.go b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go new file mode 100644 index 0000000000..652b2d3394 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go @@ -0,0 +1,133 @@ +// Copyright (c) 2020 Nikos Filippakis +// +// 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 utils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "math/rand" + "strings" + + "github.com/btcsuite/btcutil/base58" + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/pbkdf2" +) + +const ( + // AESCTRKeyLength is the length of the AES256-CTR key used. + AESCTRKeyLength = 32 + // AESCTRIVLength is the length of the AES256-CTR IV used. + AESCTRIVLength = 16 + // HMACKeyLength is the length of the HMAC key used. + HMACKeyLength = 32 + // SHAHashLength is the length of the SHA hash used. + SHAHashLength = 32 +) + +// XorA256CTR encrypts the input with the keystream generated by the AES256-CTR algorithm with the given arguments. +func XorA256CTR(source []byte, key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) []byte { + block, _ := aes.NewCipher(key[:]) + result := make([]byte, len(source)) + cipher.NewCTR(block, iv[:]).XORKeyStream(result, source) + return result +} + +// GenAttachmentA256CTR generates a new random AES256-CTR key and IV suitable for encrypting attachments. +func GenAttachmentA256CTR() (key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) { + _, err := rand.Read(key[:]) + if err != nil { + panic(err) + } + + // The last 8 bytes of the IV act as the counter in AES-CTR, which means they're left empty here + _, err = rand.Read(iv[:8]) + if err != nil { + panic(err) + } + return +} + +// GenA256CTRIV generates a random IV for AES256-CTR with the last bit set to zero. +func GenA256CTRIV() (iv [AESCTRIVLength]byte) { + _, err := rand.Read(iv[:]) + if err != nil { + panic(err) + } + iv[8] &= 0x7F + return +} + +// DeriveKeysSHA256 derives an AES and a HMAC key from the given recovery key. +func DeriveKeysSHA256(key []byte, name string) ([AESCTRKeyLength]byte, [HMACKeyLength]byte) { + var zeroBytes [32]byte + + derivedHkdf := hkdf.New(sha256.New, key[:], zeroBytes[:], []byte(name)) + + var aesKey [AESCTRKeyLength]byte + var hmacKey [HMACKeyLength]byte + derivedHkdf.Read(aesKey[:]) + derivedHkdf.Read(hmacKey[:]) + + return aesKey, hmacKey +} + +// PBKDF2SHA512 generates a key of the given bit-length using the given passphrase, salt and iteration count. +func PBKDF2SHA512(password []byte, salt []byte, iters int, keyLenBits int) []byte { + return pbkdf2.Key(password, salt, iters, keyLenBits/8, sha512.New) +} + +// DecodeBase58RecoveryKey recovers the secret storage from a recovery key. +func DecodeBase58RecoveryKey(recoveryKey string) []byte { + noSpaces := strings.ReplaceAll(recoveryKey, " ", "") + decoded := base58.Decode(noSpaces) + if len(decoded) != AESCTRKeyLength+3 { // AESCTRKeyLength bytes key and 3 bytes prefix / parity + return nil + } + var parity byte + for _, b := range decoded[:34] { + parity ^= b + } + if parity != decoded[34] || decoded[0] != 0x8B || decoded[1] != 1 { + return nil + } + return decoded[2:34] +} + +// EncodeBase58RecoveryKey recovers the secret storage from a recovery key. +func EncodeBase58RecoveryKey(key []byte) string { + var inputBytes [35]byte + copy(inputBytes[2:34], key[:]) + inputBytes[0] = 0x8B + inputBytes[1] = 1 + + var parity byte + for _, b := range inputBytes[:34] { + parity ^= b + } + inputBytes[34] = parity + recoveryKey := base58.Encode(inputBytes[:]) + + var spacedKey string + for i, c := range recoveryKey { + if i > 0 && i%4 == 0 { + spacedKey += " " + } + spacedKey += string(c) + } + return spacedKey +} + +// HMACSHA256B64 calculates the base64 of the SHA256 hmac of the input with the given key. +func HMACSHA256B64(input []byte, hmacKey [HMACKeyLength]byte) string { + h := hmac.New(sha256.New, hmacKey[:]) + h.Write(input) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/vendor/maunium.net/go/mautrix/error.go b/vendor/maunium.net/go/mautrix/error.go new file mode 100644 index 0000000000..52234d02f0 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/error.go @@ -0,0 +1,144 @@ +// 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 mautrix + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" +) + +// Common error codes from https://matrix.org/docs/spec/client_server/latest#api-standards +// +// Can be used with errors.Is() to check the response code without casting the error: +// err := client.Sync() +// if errors.Is(err, MUnknownToken) { +// // logout +// } +var ( + // Forbidden access, e.g. joining a room without permission, failed login. + MForbidden = RespError{ErrCode: "M_FORBIDDEN"} + // The access token specified was not recognised. + MUnknownToken = RespError{ErrCode: "M_UNKNOWN_TOKEN"} + // No access token was specified for the request. + MMissingToken = RespError{ErrCode: "M_MISSING_TOKEN"} + // Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. + MBadJSON = RespError{ErrCode: "M_BAD_JSON"} + // Request did not contain valid JSON. + MNotJSON = RespError{ErrCode: "M_NOT_JSON"} + // No resource was found for this request. + MNotFound = RespError{ErrCode: "M_NOT_FOUND"} + // Too many requests have been sent in a short period of time. Wait a while then try again. + MLimitExceeded = RespError{ErrCode: "M_LIMIT_EXCEEDED"} + // The user ID associated with the request has been deactivated. + // Typically for endpoints that prove authentication, such as /login. + MUserDeactivated = RespError{ErrCode: "M_USER_DEACTIVATED"} + // Encountered when trying to register a user ID which has been taken. + MUserInUse = RespError{ErrCode: "M_USER_IN_USE"} + // Encountered when trying to register a user ID which is not valid. + MInvalidUsername = RespError{ErrCode: "M_INVALID_USERNAME"} + // Sent when the room alias given to the createRoom API is already in use. + MRoomInUse = RespError{ErrCode: "M_ROOM_IN_USE"} + // The state change requested cannot be performed, such as attempting to unban a user who is not banned. + MBadState = RespError{ErrCode: "M_BAD_STATE"} + // The request or entity was too large. + MTooLarge = RespError{ErrCode: "M_TOO_LARGE"} + // The resource being requested is reserved by an application service, or the application service making the request has not created the resource. + MExclusive = RespError{ErrCode: "M_EXCLUSIVE"} + // The client's request to create a room used a room version that the server does not support. + MUnsupportedRoomVersion = RespError{ErrCode: "M_UNSUPPORTED_ROOM_VERSION"} + // The client attempted to join a room that has a version the server does not support. + // Inspect the room_version property of the error response for the room's version. + MIncompatibleRoomVersion = RespError{ErrCode: "M_INCOMPATIBLE_ROOM_VERSION"} +) + +// HTTPError An HTTP Error response, which may wrap an underlying native Go Error. +type HTTPError struct { + Request *http.Request + Response *http.Response + ResponseBody string + + WrappedError error + RespError *RespError + Message string +} + +func (e HTTPError) Is(err error) bool { + return (e.RespError != nil && errors.Is(e.RespError, err)) || (e.WrappedError != nil && errors.Is(e.WrappedError, err)) +} + +func (e HTTPError) IsStatus(code int) bool { + return e.Response != nil && e.Response.StatusCode == code +} + +func (e HTTPError) Error() string { + if e.WrappedError != nil { + return fmt.Sprintf("%s: %v", e.Message, e.WrappedError) + } else if e.RespError != nil { + return fmt.Sprintf("failed to %s %s: %s (HTTP %d): %s", e.Request.Method, e.Request.URL.Path, + e.RespError.ErrCode, e.Response.StatusCode, e.RespError.Err) + } else { + msg := fmt.Sprintf("failed to %s %s: %s", e.Request.Method, e.Request.URL.Path, e.Response.Status) + if len(e.ResponseBody) > 0 { + msg = fmt.Sprintf("%s\n%s", msg, e.ResponseBody) + } + return msg + } +} + +func (e HTTPError) Unwrap() error { + if e.WrappedError != nil { + return e.WrappedError + } else if e.RespError != nil { + return *e.RespError + } + return nil +} + +// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface. +// See http://matrix.org/docs/spec/client_server/r0.6.1.html#api-standards +type RespError struct { + ErrCode string + Err string + ExtraData map[string]interface{} +} + +func (e *RespError) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &e.ExtraData) + if err != nil { + return err + } + e.ErrCode, _ = e.ExtraData["errcode"].(string) + e.Err, _ = e.ExtraData["error"].(string) + return nil +} + +func (e *RespError) MarshalJSON() ([]byte, error) { + if e.ExtraData == nil { + e.ExtraData = make(map[string]interface{}) + } + e.ExtraData["errcode"] = e.ErrCode + e.ExtraData["error"] = e.Err + return json.Marshal(&e.ExtraData) +} + +// Error returns the errcode and error message. +func (e RespError) Error() string { + return e.ErrCode + ": " + e.Err +} + +func (e RespError) Is(err error) bool { + e2, ok := err.(RespError) + if !ok { + return false + } + if e.ErrCode == "M_UNKNOWN" && e2.ErrCode == "M_UNKNOWN" { + return e.Err == e2.Err + } + return e2.ErrCode == e.ErrCode +} diff --git a/vendor/maunium.net/go/mautrix/event/accountdata.go b/vendor/maunium.net/go/mautrix/event/accountdata.go new file mode 100644 index 0000000000..32932337fc --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/accountdata.go @@ -0,0 +1,45 @@ +// 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 event + +import ( + "encoding/json" + + "maunium.net/go/mautrix/id" +) + +// TagEventContent represents the content of a m.tag room account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-tag +type TagEventContent struct { + Tags Tags `json:"tags"` +} + +type Tags map[string]Tag + +type Tag struct { + Order json.Number `json:"order,omitempty"` +} + +// DirectChatsEventContent represents the content of a m.direct account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-direct +type DirectChatsEventContent map[id.UserID][]id.RoomID + +// FullyReadEventContent represents the content of a m.fully_read account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-fully-read +type FullyReadEventContent struct { + EventID id.EventID `json:"event_id"` +} + +// IgnoredUserListEventContent represents the content of a m.ignored_user_list account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-ignored-user-list +type IgnoredUserListEventContent struct { + IgnoredUsers map[id.UserID]IgnoredUser `json:"ignored_users"` +} + +type IgnoredUser struct { + // This is an empty object +} \ No newline at end of file diff --git a/vendor/maunium.net/go/mautrix/event/content.go b/vendor/maunium.net/go/mautrix/event/content.go new file mode 100644 index 0000000000..42579ff16b --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/content.go @@ -0,0 +1,457 @@ +// 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 event + +import ( + "encoding/gob" + "encoding/json" + "errors" + "fmt" + "reflect" +) + +// TypeMap is a mapping from event type to the content struct type. +// This is used by Content.ParseRaw() for creating the correct type of struct. +var TypeMap = map[Type]reflect.Type{ + StateMember: reflect.TypeOf(MemberEventContent{}), + StatePowerLevels: reflect.TypeOf(PowerLevelsEventContent{}), + StateCanonicalAlias: reflect.TypeOf(CanonicalAliasEventContent{}), + StateRoomName: reflect.TypeOf(RoomNameEventContent{}), + StateRoomAvatar: reflect.TypeOf(RoomAvatarEventContent{}), + StateTopic: reflect.TypeOf(TopicEventContent{}), + StateTombstone: reflect.TypeOf(TombstoneEventContent{}), + StateCreate: reflect.TypeOf(CreateEventContent{}), + StateJoinRules: reflect.TypeOf(JoinRulesEventContent{}), + StateHistoryVisibility: reflect.TypeOf(HistoryVisibilityEventContent{}), + StateGuestAccess: reflect.TypeOf(GuestAccessEventContent{}), + StatePinnedEvents: reflect.TypeOf(PinnedEventsEventContent{}), + StateEncryption: reflect.TypeOf(EncryptionEventContent{}), + + EventMessage: reflect.TypeOf(MessageEventContent{}), + EventSticker: reflect.TypeOf(MessageEventContent{}), + EventEncrypted: reflect.TypeOf(EncryptedEventContent{}), + EventRedaction: reflect.TypeOf(RedactionEventContent{}), + EventReaction: reflect.TypeOf(ReactionEventContent{}), + + AccountDataRoomTags: reflect.TypeOf(TagEventContent{}), + AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}), + AccountDataFullyRead: reflect.TypeOf(FullyReadEventContent{}), + AccountDataIgnoredUserList: reflect.TypeOf(IgnoredUserListEventContent{}), + + EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}), + EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}), + EphemeralEventPresence: reflect.TypeOf(PresenceEventContent{}), + + InRoomVerificationStart: reflect.TypeOf(VerificationStartEventContent{}), + InRoomVerificationReady: reflect.TypeOf(VerificationReadyEventContent{}), + InRoomVerificationAccept: reflect.TypeOf(VerificationAcceptEventContent{}), + InRoomVerificationKey: reflect.TypeOf(VerificationKeyEventContent{}), + InRoomVerificationMAC: reflect.TypeOf(VerificationMacEventContent{}), + InRoomVerificationCancel: reflect.TypeOf(VerificationCancelEventContent{}), + + ToDeviceRoomKey: reflect.TypeOf(RoomKeyEventContent{}), + ToDeviceForwardedRoomKey: reflect.TypeOf(ForwardedRoomKeyEventContent{}), + ToDeviceRoomKeyRequest: reflect.TypeOf(RoomKeyRequestEventContent{}), + ToDeviceEncrypted: reflect.TypeOf(EncryptedEventContent{}), + ToDeviceRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}), + + ToDeviceVerificationStart: reflect.TypeOf(VerificationStartEventContent{}), + ToDeviceVerificationAccept: reflect.TypeOf(VerificationAcceptEventContent{}), + ToDeviceVerificationKey: reflect.TypeOf(VerificationKeyEventContent{}), + ToDeviceVerificationMAC: reflect.TypeOf(VerificationMacEventContent{}), + ToDeviceVerificationCancel: reflect.TypeOf(VerificationCancelEventContent{}), + ToDeviceVerificationRequest: reflect.TypeOf(VerificationRequestEventContent{}), + + ToDeviceOrgMatrixRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}), + + CallInvite: reflect.TypeOf(CallInviteEventContent{}), + CallCandidates: reflect.TypeOf(CallCandidatesEventContent{}), + CallAnswer: reflect.TypeOf(CallAnswerEventContent{}), + CallReject: reflect.TypeOf(CallRejectEventContent{}), + CallSelectAnswer: reflect.TypeOf(CallSelectAnswerEventContent{}), + CallNegotiate: reflect.TypeOf(CallNegotiateEventContent{}), + CallHangup: reflect.TypeOf(CallHangupEventContent{}), +} + +// Content stores the content of a Matrix event. +// +// By default, the content is only parsed into a map[string]interface{}. However, you can call ParseRaw with the +// correct event type to parse the content into a nicer struct, which you can then access from Parsed or via the +// helper functions. +type Content struct { + VeryRaw json.RawMessage + Raw map[string]interface{} + Parsed interface{} +} + +type Relatable interface { + GetRelatesTo() *RelatesTo + OptionalGetRelatesTo() *RelatesTo + SetRelatesTo(rel *RelatesTo) +} + +func (content *Content) UnmarshalJSON(data []byte) error { + content.VeryRaw = data + err := json.Unmarshal(data, &content.Raw) + return err +} + +func (content *Content) MarshalJSON() ([]byte, error) { + if content.Raw == nil { + if content.Parsed == nil { + if content.VeryRaw == nil { + return []byte("{}"), nil + } + return content.VeryRaw, nil + } + return json.Marshal(content.Parsed) + } else if content.Parsed != nil { + // TODO this whole thing is incredibly hacky + // It needs to produce JSON, where: + // * content.Parsed is applied after content.Raw + // * MarshalJSON() is respected inside content.Parsed + // * Custom field inside nested objects of content.Raw are preserved, + // even if content.Parsed contains the higher-level objects. + // * content.Raw is not modified + + unparsed, err := json.Marshal(content.Parsed) + if err != nil { + return nil, err + } + + var rawParsed map[string]interface{} + err = json.Unmarshal(unparsed, &rawParsed) + if err != nil { + return nil, err + } + + output := make(map[string]interface{}) + for key, value := range content.Raw { + output[key] = value + } + + mergeMaps(output, rawParsed) + return json.Marshal(output) + } + return json.Marshal(content.Raw) +} + +func IsUnsupportedContentType(err error) bool { + return errors.Is(err, UnsupportedContentType) +} + +var ContentAlreadyParsed = errors.New("content is already parsed") +var UnsupportedContentType = errors.New("unsupported event type") + +func (content *Content) ParseRaw(evtType Type) error { + if content.Parsed != nil { + return ContentAlreadyParsed + } + structType, ok := TypeMap[evtType] + if !ok { + return fmt.Errorf("%w %s", UnsupportedContentType, evtType.Repr()) + } + content.Parsed = reflect.New(structType).Interface() + return json.Unmarshal(content.VeryRaw, &content.Parsed) +} + +func mergeMaps(into, from map[string]interface{}) { + for key, newValue := range from { + existingValue, ok := into[key] + if !ok { + into[key] = newValue + continue + } + existingValueMap, okEx := existingValue.(map[string]interface{}) + newValueMap, okNew := newValue.(map[string]interface{}) + if okEx && okNew { + mergeMaps(existingValueMap, newValueMap) + } else { + into[key] = newValue + } + } +} + +func init() { + gob.Register(&MemberEventContent{}) + gob.Register(&PowerLevelsEventContent{}) + gob.Register(&CanonicalAliasEventContent{}) + gob.Register(&EncryptionEventContent{}) + gob.Register(&RoomNameEventContent{}) + gob.Register(&RoomAvatarEventContent{}) + gob.Register(&TopicEventContent{}) + gob.Register(&TombstoneEventContent{}) + gob.Register(&CreateEventContent{}) + gob.Register(&JoinRulesEventContent{}) + gob.Register(&HistoryVisibilityEventContent{}) + gob.Register(&GuestAccessEventContent{}) + gob.Register(&PinnedEventsEventContent{}) + gob.Register(&MessageEventContent{}) + gob.Register(&MessageEventContent{}) + gob.Register(&EncryptedEventContent{}) + gob.Register(&RedactionEventContent{}) + gob.Register(&ReactionEventContent{}) + gob.Register(&TagEventContent{}) + gob.Register(&DirectChatsEventContent{}) + gob.Register(&FullyReadEventContent{}) + gob.Register(&IgnoredUserListEventContent{}) + gob.Register(&TypingEventContent{}) + gob.Register(&ReceiptEventContent{}) + gob.Register(&PresenceEventContent{}) + gob.Register(&RoomKeyEventContent{}) + gob.Register(&ForwardedRoomKeyEventContent{}) + gob.Register(&RoomKeyRequestEventContent{}) + gob.Register(&RoomKeyWithheldEventContent{}) +} + +// Helper cast functions below + +func (content *Content) AsMember() *MemberEventContent { + casted, ok := content.Parsed.(*MemberEventContent) + if !ok { + return &MemberEventContent{} + } + return casted +} +func (content *Content) AsPowerLevels() *PowerLevelsEventContent { + casted, ok := content.Parsed.(*PowerLevelsEventContent) + if !ok { + return &PowerLevelsEventContent{} + } + return casted +} +func (content *Content) AsCanonicalAlias() *CanonicalAliasEventContent { + casted, ok := content.Parsed.(*CanonicalAliasEventContent) + if !ok { + return &CanonicalAliasEventContent{} + } + return casted +} +func (content *Content) AsRoomName() *RoomNameEventContent { + casted, ok := content.Parsed.(*RoomNameEventContent) + if !ok { + return &RoomNameEventContent{} + } + return casted +} +func (content *Content) AsRoomAvatar() *RoomAvatarEventContent { + casted, ok := content.Parsed.(*RoomAvatarEventContent) + if !ok { + return &RoomAvatarEventContent{} + } + return casted +} +func (content *Content) AsTopic() *TopicEventContent { + casted, ok := content.Parsed.(*TopicEventContent) + if !ok { + return &TopicEventContent{} + } + return casted +} +func (content *Content) AsTombstone() *TombstoneEventContent { + casted, ok := content.Parsed.(*TombstoneEventContent) + if !ok { + return &TombstoneEventContent{} + } + return casted +} +func (content *Content) AsCreate() *CreateEventContent { + casted, ok := content.Parsed.(*CreateEventContent) + if !ok { + return &CreateEventContent{} + } + return casted +} +func (content *Content) AsJoinRules() *JoinRulesEventContent { + casted, ok := content.Parsed.(*JoinRulesEventContent) + if !ok { + return &JoinRulesEventContent{} + } + return casted +} +func (content *Content) AsHistoryVisibility() *HistoryVisibilityEventContent { + casted, ok := content.Parsed.(*HistoryVisibilityEventContent) + if !ok { + return &HistoryVisibilityEventContent{} + } + return casted +} +func (content *Content) AsGuestAccess() *GuestAccessEventContent { + casted, ok := content.Parsed.(*GuestAccessEventContent) + if !ok { + return &GuestAccessEventContent{} + } + return casted +} +func (content *Content) AsPinnedEvents() *PinnedEventsEventContent { + casted, ok := content.Parsed.(*PinnedEventsEventContent) + if !ok { + return &PinnedEventsEventContent{} + } + return casted +} +func (content *Content) AsEncryption() *EncryptionEventContent { + casted, ok := content.Parsed.(*EncryptionEventContent) + if !ok { + return &EncryptionEventContent{} + } + return casted +} +func (content *Content) AsMessage() *MessageEventContent { + casted, ok := content.Parsed.(*MessageEventContent) + if !ok { + return &MessageEventContent{} + } + return casted +} +func (content *Content) AsEncrypted() *EncryptedEventContent { + casted, ok := content.Parsed.(*EncryptedEventContent) + if !ok { + return &EncryptedEventContent{} + } + return casted +} +func (content *Content) AsRedaction() *RedactionEventContent { + casted, ok := content.Parsed.(*RedactionEventContent) + if !ok { + return &RedactionEventContent{} + } + return casted +} +func (content *Content) AsReaction() *ReactionEventContent { + casted, ok := content.Parsed.(*ReactionEventContent) + if !ok { + return &ReactionEventContent{} + } + return casted +} +func (content *Content) AsTag() *TagEventContent { + casted, ok := content.Parsed.(*TagEventContent) + if !ok { + return &TagEventContent{} + } + return casted +} +func (content *Content) AsDirectChats() *DirectChatsEventContent { + casted, ok := content.Parsed.(*DirectChatsEventContent) + if !ok { + return &DirectChatsEventContent{} + } + return casted +} +func (content *Content) AsFullyRead() *FullyReadEventContent { + casted, ok := content.Parsed.(*FullyReadEventContent) + if !ok { + return &FullyReadEventContent{} + } + return casted +} +func (content *Content) AsIgnoredUserList() *IgnoredUserListEventContent { + casted, ok := content.Parsed.(*IgnoredUserListEventContent) + if !ok { + return &IgnoredUserListEventContent{} + } + return casted +} +func (content *Content) AsTyping() *TypingEventContent { + casted, ok := content.Parsed.(*TypingEventContent) + if !ok { + return &TypingEventContent{} + } + return casted +} +func (content *Content) AsReceipt() *ReceiptEventContent { + casted, ok := content.Parsed.(*ReceiptEventContent) + if !ok { + return &ReceiptEventContent{} + } + return casted +} +func (content *Content) AsPresence() *PresenceEventContent { + casted, ok := content.Parsed.(*PresenceEventContent) + if !ok { + return &PresenceEventContent{} + } + return casted +} +func (content *Content) AsRoomKey() *RoomKeyEventContent { + casted, ok := content.Parsed.(*RoomKeyEventContent) + if !ok { + return &RoomKeyEventContent{} + } + return casted +} +func (content *Content) AsForwardedRoomKey() *ForwardedRoomKeyEventContent { + casted, ok := content.Parsed.(*ForwardedRoomKeyEventContent) + if !ok { + return &ForwardedRoomKeyEventContent{} + } + return casted +} +func (content *Content) AsRoomKeyRequest() *RoomKeyRequestEventContent { + casted, ok := content.Parsed.(*RoomKeyRequestEventContent) + if !ok { + return &RoomKeyRequestEventContent{} + } + return casted +} +func (content *Content) AsRoomKeyWithheld() *RoomKeyWithheldEventContent { + casted, ok := content.Parsed.(*RoomKeyWithheldEventContent) + if !ok { + return &RoomKeyWithheldEventContent{} + } + return casted +} +func (content *Content) AsCallInvite() *CallInviteEventContent { + casted, ok := content.Parsed.(*CallInviteEventContent) + if !ok { + return &CallInviteEventContent{} + } + return casted +} +func (content *Content) AsCallCandidates() *CallCandidatesEventContent { + casted, ok := content.Parsed.(*CallCandidatesEventContent) + if !ok { + return &CallCandidatesEventContent{} + } + return casted +} +func (content *Content) AsCallAnswer() *CallAnswerEventContent { + casted, ok := content.Parsed.(*CallAnswerEventContent) + if !ok { + return &CallAnswerEventContent{} + } + return casted +} +func (content *Content) AsCallReject() *CallRejectEventContent { + casted, ok := content.Parsed.(*CallRejectEventContent) + if !ok { + return &CallRejectEventContent{} + } + return casted +} +func (content *Content) AsCallSelectAnswer() *CallSelectAnswerEventContent { + casted, ok := content.Parsed.(*CallSelectAnswerEventContent) + if !ok { + return &CallSelectAnswerEventContent{} + } + return casted +} +func (content *Content) AsCallNegotiate() *CallNegotiateEventContent { + casted, ok := content.Parsed.(*CallNegotiateEventContent) + if !ok { + return &CallNegotiateEventContent{} + } + return casted +} +func (content *Content) AsCallHangup() *CallHangupEventContent { + casted, ok := content.Parsed.(*CallHangupEventContent) + if !ok { + return &CallHangupEventContent{} + } + return casted +} diff --git a/vendor/maunium.net/go/mautrix/event/encryption.go b/vendor/maunium.net/go/mautrix/event/encryption.go new file mode 100644 index 0000000000..4c9bdac39a --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/encryption.go @@ -0,0 +1,141 @@ +// 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 event + +import ( + "encoding/json" + + "maunium.net/go/mautrix/id" +) + +// EncryptionEventContent represents the content of a m.room.encryption state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-encryption +type EncryptionEventContent struct { + // The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'. + Algorithm id.Algorithm `json:"algorithm"` + // How long the session should be used before changing it. 604800000 (a week) is the recommended default. + RotationPeriodMillis int64 `json:"rotation_period_ms,omitempty"` + // How many messages should be sent before changing the session. 100 is the recommended default. + RotationPeriodMessages int `json:"rotation_period_msgs,omitempty"` +} + +// EncryptedEventContent represents the content of a m.room.encrypted message event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-encrypted +type EncryptedEventContent struct { + Algorithm id.Algorithm `json:"algorithm"` + SenderKey id.SenderKey `json:"sender_key"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + SessionID id.SessionID `json:"session_id,omitempty"` + Ciphertext json.RawMessage `json:"ciphertext"` + + MegolmCiphertext []byte `json:"-"` + OlmCiphertext OlmCiphertexts `json:"-"` + + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +type OlmCiphertexts map[id.Curve25519]struct { + Body string `json:"body"` + Type id.OlmMsgType `json:"type"` +} + +type serializableEncryptedEventContent EncryptedEventContent + +func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*serializableEncryptedEventContent)(content)) + if err != nil { + return err + } + switch content.Algorithm { + case id.AlgorithmOlmV1: + content.OlmCiphertext = make(OlmCiphertexts) + return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext) + case id.AlgorithmMegolmV1: + if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' { + return id.InputNotJSONString + } + content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1] + } + return nil +} + +func (content *EncryptedEventContent) MarshalJSON() ([]byte, error) { + var err error + switch content.Algorithm { + case id.AlgorithmOlmV1: + content.Ciphertext, err = json.Marshal(content.OlmCiphertext) + case id.AlgorithmMegolmV1: + content.Ciphertext = make([]byte, len(content.MegolmCiphertext)+2) + content.Ciphertext[0] = '"' + content.Ciphertext[len(content.Ciphertext)-1] = '"' + copy(content.Ciphertext[1:len(content.Ciphertext)-1], content.MegolmCiphertext) + } + if err != nil { + return nil, err + } + return json.Marshal((*serializableEncryptedEventContent)(content)) +} + +// RoomKeyEventContent represents the content of a m.room_key to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-key +type RoomKeyEventContent struct { + Algorithm id.Algorithm `json:"algorithm"` + RoomID id.RoomID `json:"room_id"` + SessionID id.SessionID `json:"session_id"` + SessionKey string `json:"session_key"` +} + +// ForwardedRoomKeyEventContent represents the content of a m.forwarded_room_key to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-forwarded-room-key +type ForwardedRoomKeyEventContent struct { + RoomKeyEventContent + SenderKey id.SenderKey `json:"sender_key"` + SenderClaimedKey id.Ed25519 `json:"sender_claimed_ed25519_key"` + ForwardingKeyChain []string `json:"forwarding_curve25519_key_chain"` +} + +type KeyRequestAction string + +const ( + KeyRequestActionRequest = "request" + KeyRequestActionCancel = "request_cancellation" +) + +// RoomKeyRequestEventContent represents the content of a m.room_key_request to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-key-request +type RoomKeyRequestEventContent struct { + Body RequestedKeyInfo `json:"body"` + Action KeyRequestAction `json:"action"` + RequestingDeviceID id.DeviceID `json:"requesting_device_id"` + RequestID string `json:"request_id"` +} + +type RequestedKeyInfo struct { + Algorithm id.Algorithm `json:"algorithm"` + RoomID id.RoomID `json:"room_id"` + SenderKey id.SenderKey `json:"sender_key"` + SessionID id.SessionID `json:"session_id"` +} + +type RoomKeyWithheldCode string + +const ( + RoomKeyWithheldBlacklisted RoomKeyWithheldCode = "m.blacklisted" + RoomKeyWithheldUnverified RoomKeyWithheldCode = "m.unverified" + RoomKeyWithheldUnauthorized RoomKeyWithheldCode = "m.unauthorized" + RoomKeyWithheldUnavailable RoomKeyWithheldCode = "m.unavailable" + RoomKeyWithheldNoOlmSession RoomKeyWithheldCode = "m.no_olm" +) + +type RoomKeyWithheldEventContent struct { + RoomID id.RoomID `json:"room_id,omitempty"` + Algorithm id.Algorithm `json:"algorithm"` + SessionID id.SessionID `json:"session_id,omitempty"` + SenderKey id.SenderKey `json:"sender_key"` + Code RoomKeyWithheldCode `json:"code"` + Reason string `json:"reason,omitempty"` +} diff --git a/vendor/maunium.net/go/mautrix/event/ephemeral.go b/vendor/maunium.net/go/mautrix/event/ephemeral.go new file mode 100644 index 0000000000..186cb16419 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/ephemeral.go @@ -0,0 +1,80 @@ +// 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 event + +import ( + "encoding/json" + + "maunium.net/go/mautrix/id" +) + +// TagEventContent represents the content of a m.typing ephemeral event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-typing +type TypingEventContent struct { + UserIDs []id.UserID `json:"user_ids"` +} + +// ReceiptEventContent represents the content of a m.receipt ephemeral event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-receipt +type ReceiptEventContent map[id.EventID]Receipts + +type Receipts struct { + Read map[id.UserID]ReadReceipt `json:"m.read"` +} + +type ReadReceipt struct { + Timestamp int64 `json:"ts"` + + // Extra contains any unknown fields in the read receipt event. + // Most servers don't allow clients to set them, so this will be empty in most cases. + Extra map[string]interface{} `json:"-"` +} + +func (rr *ReadReceipt) UnmarshalJSON(data []byte) error { + // Hacky compatibility hack against crappy clients that send double-encoded read receipts. + // TODO is this actually needed? clients can't currently set custom content in receipts 🤔 + if data[0] == '"' && data[len(data)-1] == '"' { + var strData string + err := json.Unmarshal(data, &strData) + if err != nil { + return err + } + data = []byte(strData) + } + + var parsed map[string]interface{} + err := json.Unmarshal(data, &parsed) + if err != nil { + return err + } + ts, _ := parsed["ts"].(float64) + delete(parsed, "ts") + *rr = ReadReceipt{ + Timestamp: int64(ts), + Extra: parsed, + } + return nil +} + +type Presence string + +const ( + PresenceOnline Presence = "online" + PresenceOffline Presence = "offline" + PresenceUnavailable Presence = "unavailable" +) + +// PresenceEventContent represents the content of a m.presence ephemeral event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-presence +type PresenceEventContent struct { + Presence Presence `json:"presence"` + Displayname string `json:"displayname,omitempty"` + AvatarURL id.ContentURIString `json:"avatar_url,omitempty"` + LastActiveAgo int64 `json:"last_active_ago,omitempty"` + CurrentlyActive bool `json:"currently_active,omitempty"` + StatusMessage string `json:"status_msg,omitempty"` +} diff --git a/vendor/maunium.net/go/mautrix/event/events.go b/vendor/maunium.net/go/mautrix/event/events.go new file mode 100644 index 0000000000..7f80aeac8c --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/events.go @@ -0,0 +1,54 @@ +// 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 event + +import ( + "maunium.net/go/mautrix/id" +) + +// Event represents a single Matrix event. +type Event struct { + StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events. + Sender id.UserID `json:"sender"` // The user ID of the sender of the event + Type Type `json:"type"` // The event type + Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server + ID id.EventID `json:"event_id"` // The unique ID of this event + RoomID id.RoomID `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence) + Content Content `json:"content"` // The JSON content of the event. + Redacts id.EventID `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event + Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver. + + Mautrix MautrixInfo `json:"-"` +} + +type MautrixInfo struct { + Verified bool +} + +func (evt *Event) GetStateKey() string { + if evt.StateKey != nil { + return *evt.StateKey + } + return "" +} + +type StrippedState struct { + Content Content `json:"content"` + Type Type `json:"type"` + StateKey string `json:"state_key"` +} + +type Unsigned struct { + PrevContent *Content `json:"prev_content,omitempty"` + PrevSender id.UserID `json:"prev_sender,omitempty"` + ReplacesState id.EventID `json:"replaces_state,omitempty"` + Age int64 `json:"age,omitempty"` + TransactionID string `json:"transaction_id,omitempty"` + Relations Relations `json:"m.relations,omitempty"` + RedactedBecause *Event `json:"redacted_because,omitempty"` + InviteRoomState []StrippedState `json:"invite_room_state"` +} diff --git a/vendor/maunium.net/go/mautrix/event/member.go b/vendor/maunium.net/go/mautrix/event/member.go new file mode 100644 index 0000000000..9e4e525075 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/member.go @@ -0,0 +1,53 @@ +// 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 event + +import ( + "encoding/json" + + "maunium.net/go/mautrix/id" +) + +// Membership is an enum specifying the membership state of a room member. +type Membership string + +func (ms Membership) IsInviteOrJoin() bool { + return ms == MembershipJoin || ms == MembershipInvite +} + +func (ms Membership) IsLeaveOrBan() bool { + return ms == MembershipLeave || ms == MembershipBan +} + +// The allowed membership states as specified in spec section 10.5.5. +const ( + MembershipJoin Membership = "join" + MembershipLeave Membership = "leave" + MembershipInvite Membership = "invite" + MembershipBan Membership = "ban" + MembershipKnock Membership = "knock" +) + +// MemberEventContent represents the content of a m.room.member state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-member +type MemberEventContent struct { + Membership Membership `json:"membership"` + AvatarURL id.ContentURIString `json:"avatar_url,omitempty"` + Displayname string `json:"displayname,omitempty"` + IsDirect bool `json:"is_direct,omitempty"` + ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"` + Reason string `json:"reason,omitempty"` +} + +type ThirdPartyInvite struct { + DisplayName string `json:"display_name"` + Signed struct { + Token string `json:"token"` + Signatures json.RawMessage `json:"signatures"` + MXID string `json:"mxid"` + } +} diff --git a/vendor/maunium.net/go/mautrix/event/message.go b/vendor/maunium.net/go/mautrix/event/message.go new file mode 100644 index 0000000000..25d1b6a2c0 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/message.go @@ -0,0 +1,231 @@ +// 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 event + +import ( + "encoding/json" + "strconv" + + "maunium.net/go/mautrix/crypto/attachment" + "maunium.net/go/mautrix/id" +) + +// MessageType is the sub-type of a m.room.message event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message-msgtypes +type MessageType string + +// Msgtypes +const ( + MsgText MessageType = "m.text" + MsgEmote MessageType = "m.emote" + MsgNotice MessageType = "m.notice" + MsgImage MessageType = "m.image" + MsgLocation MessageType = "m.location" + MsgVideo MessageType = "m.video" + MsgAudio MessageType = "m.audio" + MsgFile MessageType = "m.file" + + MsgVerificationRequest MessageType = "m.key.verification.request" +) + +// Format specifies the format of the formatted_body in m.room.message events. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message-msgtypes +type Format string + +// Message formats +const ( + FormatHTML Format = "org.matrix.custom.html" +) + +// RedactionEventContent represents the content of a m.room.redaction message event. +// +// The redacted event ID is still at the top level, but will move in a future room version. +// See https://github.com/matrix-org/matrix-doc/pull/2244 and https://github.com/matrix-org/matrix-doc/pull/2174 +// +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-redaction +type RedactionEventContent struct { + Reason string `json:"reason,omitempty"` +} + +// ReactionEventContent represents the content of a m.reaction message event. +// This is not yet in a spec release, see https://github.com/matrix-org/matrix-doc/pull/1849 +type ReactionEventContent struct { + RelatesTo RelatesTo `json:"m.relates_to"` +} + +func (content *ReactionEventContent) GetRelatesTo() *RelatesTo { + return &content.RelatesTo +} + +func (content *ReactionEventContent) OptionalGetRelatesTo() *RelatesTo { + return &content.RelatesTo +} + +func (content *ReactionEventContent) SetRelatesTo(rel *RelatesTo) { + content.RelatesTo = *rel +} + +// MssageEventContent represents the content of a m.room.message event. +// +// It is also used to represent m.sticker events, as they are equivalent to m.room.message +// with the exception of the msgtype field. +// +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message +type MessageEventContent struct { + // Base m.room.message fields + MsgType MessageType `json:"msgtype"` + Body string `json:"body"` + + // Extra fields for text types + Format Format `json:"format,omitempty"` + FormattedBody string `json:"formatted_body,omitempty"` + + // Extra field for m.location + GeoURI string `json:"geo_uri,omitempty"` + + // Extra fields for media types + URL id.ContentURIString `json:"url,omitempty"` + Info *FileInfo `json:"info,omitempty"` + File *EncryptedFileInfo `json:"file,omitempty"` + + // Edits and relations + NewContent *MessageEventContent `json:"m.new_content,omitempty"` + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` + + // In-room verification + To id.UserID `json:"to,omitempty"` + FromDevice id.DeviceID `json:"from_device,omitempty"` + Methods []VerificationMethod `json:"methods,omitempty"` + + replyFallbackRemoved bool +} + +func (content *MessageEventContent) GetRelatesTo() *RelatesTo { + if content.RelatesTo == nil { + content.RelatesTo = &RelatesTo{} + } + return content.RelatesTo +} + +func (content *MessageEventContent) OptionalGetRelatesTo() *RelatesTo { + return content.RelatesTo +} + +func (content *MessageEventContent) SetRelatesTo(rel *RelatesTo) { + content.RelatesTo = rel +} + +func (content *MessageEventContent) GetFile() *EncryptedFileInfo { + if content.File == nil { + content.File = &EncryptedFileInfo{} + } + return content.File +} + +func (content *MessageEventContent) GetInfo() *FileInfo { + if content.Info == nil { + content.Info = &FileInfo{} + } + return content.Info +} + +type EncryptedFileInfo struct { + attachment.EncryptedFile + URL id.ContentURIString `json:"url"` +} + +type FileInfo struct { + MimeType string `json:"mimetype,omitempty"` + ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"` + ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"` + ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"` + Width int `json:"-"` + Height int `json:"-"` + Duration int `json:"-"` + Size int `json:"-"` +} + +type serializableFileInfo struct { + MimeType string `json:"mimetype,omitempty"` + ThumbnailInfo *serializableFileInfo `json:"thumbnail_info,omitempty"` + ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"` + ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"` + + Width json.Number `json:"w,omitempty"` + Height json.Number `json:"h,omitempty"` + Duration json.Number `json:"duration,omitempty"` + Size json.Number `json:"size,omitempty"` +} + +func (sfi *serializableFileInfo) CopyFrom(fileInfo *FileInfo) *serializableFileInfo { + if fileInfo == nil { + return nil + } + *sfi = serializableFileInfo{ + MimeType: fileInfo.MimeType, + ThumbnailURL: fileInfo.ThumbnailURL, + ThumbnailInfo: (&serializableFileInfo{}).CopyFrom(fileInfo.ThumbnailInfo), + ThumbnailFile: fileInfo.ThumbnailFile, + } + if fileInfo.Width > 0 { + sfi.Width = json.Number(strconv.Itoa(fileInfo.Width)) + } + if fileInfo.Height > 0 { + sfi.Height = json.Number(strconv.Itoa(fileInfo.Height)) + } + if fileInfo.Size > 0 { + sfi.Size = json.Number(strconv.Itoa(fileInfo.Size)) + + } + if fileInfo.Duration > 0 { + sfi.Duration = json.Number(strconv.Itoa(int(fileInfo.Duration))) + } + return sfi +} + +func (sfi *serializableFileInfo) CopyTo(fileInfo *FileInfo) { + *fileInfo = FileInfo{ + Width: numberToInt(sfi.Width), + Height: numberToInt(sfi.Height), + Size: numberToInt(sfi.Size), + Duration: numberToInt(sfi.Duration), + MimeType: sfi.MimeType, + ThumbnailURL: sfi.ThumbnailURL, + } + if sfi.ThumbnailInfo != nil { + fileInfo.ThumbnailInfo = &FileInfo{} + sfi.ThumbnailInfo.CopyTo(fileInfo.ThumbnailInfo) + } +} + +func (fileInfo *FileInfo) UnmarshalJSON(data []byte) error { + sfi := &serializableFileInfo{} + if err := json.Unmarshal(data, sfi); err != nil { + return err + } + sfi.CopyTo(fileInfo) + return nil +} + +func (fileInfo *FileInfo) MarshalJSON() ([]byte, error) { + return json.Marshal((&serializableFileInfo{}).CopyFrom(fileInfo)) +} + +func numberToInt(val json.Number) int { + f64, _ := val.Float64() + if f64 > 0 { + return int(f64) + } + return 0 +} + +func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo { + if fileInfo.ThumbnailInfo == nil { + fileInfo.ThumbnailInfo = &FileInfo{} + } + return fileInfo.ThumbnailInfo +} diff --git a/vendor/maunium.net/go/mautrix/event/powerlevels.go b/vendor/maunium.net/go/mautrix/event/powerlevels.go new file mode 100644 index 0000000000..3cac9cd7ac --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/powerlevels.go @@ -0,0 +1,128 @@ +// 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 event + +import ( + "sync" + + "maunium.net/go/mautrix/id" +) + +// PowerLevelsEventContent represents the content of a m.room.power_levels state event content. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-power-levels +type PowerLevelsEventContent struct { + usersLock sync.RWMutex `json:"-"` + Users map[id.UserID]int `json:"users"` + UsersDefault int `json:"users_default"` + + eventsLock sync.RWMutex `json:"-"` + Events map[string]int `json:"events"` + EventsDefault int `json:"events_default"` + + StateDefaultPtr *int `json:"state_default,omitempty"` + + InvitePtr *int `json:"invite,omitempty"` + KickPtr *int `json:"kick,omitempty"` + BanPtr *int `json:"ban,omitempty"` + RedactPtr *int `json:"redact,omitempty"` +} + +func (pl *PowerLevelsEventContent) Invite() int { + if pl.InvitePtr != nil { + return *pl.InvitePtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) Kick() int { + if pl.KickPtr != nil { + return *pl.KickPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) Ban() int { + if pl.BanPtr != nil { + return *pl.BanPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) Redact() int { + if pl.RedactPtr != nil { + return *pl.RedactPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) StateDefault() int { + if pl.StateDefaultPtr != nil { + return *pl.StateDefaultPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) GetUserLevel(userID id.UserID) int { + pl.usersLock.RLock() + defer pl.usersLock.RUnlock() + level, ok := pl.Users[userID] + if !ok { + return pl.UsersDefault + } + return level +} + +func (pl *PowerLevelsEventContent) SetUserLevel(userID id.UserID, level int) { + pl.usersLock.Lock() + defer pl.usersLock.Unlock() + if level == pl.UsersDefault { + delete(pl.Users, userID) + } else { + pl.Users[userID] = level + } +} + +func (pl *PowerLevelsEventContent) EnsureUserLevel(userID id.UserID, level int) bool { + existingLevel := pl.GetUserLevel(userID) + if existingLevel != level { + pl.SetUserLevel(userID, level) + return true + } + return false +} + +func (pl *PowerLevelsEventContent) GetEventLevel(eventType Type) int { + pl.eventsLock.RLock() + defer pl.eventsLock.RUnlock() + level, ok := pl.Events[eventType.String()] + if !ok { + if eventType.IsState() { + return pl.StateDefault() + } + return pl.EventsDefault + } + return level +} + +func (pl *PowerLevelsEventContent) SetEventLevel(eventType Type, level int) { + pl.eventsLock.Lock() + defer pl.eventsLock.Unlock() + if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) { + delete(pl.Events, eventType.String()) + } else { + pl.Events[eventType.String()] = level + } +} + +func (pl *PowerLevelsEventContent) EnsureEventLevel(eventType Type, level int) bool { + existingLevel := pl.GetEventLevel(eventType) + if existingLevel != level { + pl.SetEventLevel(eventType, level) + return true + } + return false +} diff --git a/vendor/maunium.net/go/mautrix/event/relations.go b/vendor/maunium.net/go/mautrix/event/relations.go new file mode 100644 index 0000000000..e8b5c23130 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/relations.go @@ -0,0 +1,200 @@ +// 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 event + +import ( + "encoding/json" + + "maunium.net/go/mautrix/id" +) + +type RelationType string + +const ( + RelReplace RelationType = "m.replace" + RelReference RelationType = "m.reference" + RelAnnotation RelationType = "m.annotation" + RelReply RelationType = "net.maunium.reply" +) + +type RelatesTo struct { + Type RelationType + EventID id.EventID + Key string +} + +type serializableInReplyTo struct { + EventID id.EventID `json:"event_id,omitempty"` +} + +type serializableRelatesTo struct { + InReplyTo *serializableInReplyTo `json:"m.in_reply_to,omitempty"` + + Type RelationType `json:"rel_type,omitempty"` + EventID id.EventID `json:"event_id,omitempty"` + Key string `json:"key,omitempty"` +} + +func (rel *RelatesTo) GetReplaceID() id.EventID { + if rel.Type == RelReplace { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetReferenceID() id.EventID { + if rel.Type == RelReference { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetReplyID() id.EventID { + if rel.Type == RelReply { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetAnnotationID() id.EventID { + if rel.Type == RelAnnotation { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetAnnotationKey() string { + if rel.Type == RelAnnotation { + return rel.Key + } + return "" +} + +func (rel *RelatesTo) UnmarshalJSON(data []byte) error { + var srel serializableRelatesTo + if err := json.Unmarshal(data, &srel); err != nil { + return err + } + if len(srel.Type) > 0 { + rel.Type = srel.Type + rel.EventID = srel.EventID + rel.Key = srel.Key + } else if srel.InReplyTo != nil && len(srel.InReplyTo.EventID) > 0 { + rel.Type = RelReply + rel.EventID = srel.InReplyTo.EventID + rel.Key = "" + } + return nil +} + +func (rel *RelatesTo) MarshalJSON() ([]byte, error) { + srel := serializableRelatesTo{Type: rel.Type, EventID: rel.EventID, Key: rel.Key} + if rel.Type == RelReply { + srel.InReplyTo = &serializableInReplyTo{rel.EventID} + } + return json.Marshal(&srel) +} + +type RelationChunkItem struct { + Type RelationType `json:"type"` + EventID string `json:"event_id,omitempty"` + Key string `json:"key,omitempty"` + Count int `json:"count,omitempty"` +} + +type RelationChunk struct { + Chunk []RelationChunkItem `json:"chunk"` + + Limited bool `json:"limited"` + Count int `json:"count"` +} + +type AnnotationChunk struct { + RelationChunk + Map map[string]int `json:"-"` +} + +type serializableAnnotationChunk AnnotationChunk + +func (ac *AnnotationChunk) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, (*serializableAnnotationChunk)(ac)); err != nil { + return err + } + ac.Map = make(map[string]int) + for _, item := range ac.Chunk { + ac.Map[item.Key] += item.Count + } + return nil +} + +func (ac *AnnotationChunk) Serialize() RelationChunk { + ac.Chunk = make([]RelationChunkItem, len(ac.Map)) + i := 0 + for key, count := range ac.Map { + ac.Chunk[i] = RelationChunkItem{ + Type: RelAnnotation, + Key: key, + Count: count, + } + } + return ac.RelationChunk +} + +type EventIDChunk struct { + RelationChunk + List []string `json:"-"` +} + +type serializableEventIDChunk EventIDChunk + +func (ec *EventIDChunk) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, (*serializableEventIDChunk)(ec)); err != nil { + return err + } + for _, item := range ec.Chunk { + ec.List = append(ec.List, item.EventID) + } + return nil +} + +func (ec *EventIDChunk) Serialize(typ RelationType) RelationChunk { + ec.Chunk = make([]RelationChunkItem, len(ec.List)) + for i, eventID := range ec.List { + ec.Chunk[i] = RelationChunkItem{ + Type: typ, + EventID: eventID, + } + } + return ec.RelationChunk +} + +type Relations struct { + Raw map[RelationType]RelationChunk `json:"-"` + + Annotations AnnotationChunk `json:"m.annotation,omitempty"` + References EventIDChunk `json:"m.reference,omitempty"` + Replaces EventIDChunk `json:"m.replace,omitempty"` +} + +type serializableRelations Relations + +func (relations *Relations) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &relations.Raw); err != nil { + return err + } + return json.Unmarshal(data, (*serializableRelations)(relations)) +} + +func (relations *Relations) MarshalJSON() ([]byte, error) { + if relations.Raw == nil { + relations.Raw = make(map[RelationType]RelationChunk) + } + relations.Raw[RelAnnotation] = relations.Annotations.Serialize() + relations.Raw[RelReference] = relations.References.Serialize(RelReference) + relations.Raw[RelReplace] = relations.Replaces.Serialize(RelReplace) + return json.Marshal(relations.Raw) +} diff --git a/vendor/maunium.net/go/mautrix/event/reply.go b/vendor/maunium.net/go/mautrix/event/reply.go new file mode 100644 index 0000000000..975d010625 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/reply.go @@ -0,0 +1,108 @@ +// 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 event + +import ( + "fmt" + "regexp" + "strings" + + "golang.org/x/net/html" + + "maunium.net/go/mautrix/id" +) + +var HTMLReplyFallbackRegex = regexp.MustCompile(`^[\s\S]+?`) + +func TrimReplyFallbackHTML(html string) string { + return HTMLReplyFallbackRegex.ReplaceAllString(html, "") +} + +func TrimReplyFallbackText(text string) string { + if !strings.HasPrefix(text, "> ") || !strings.Contains(text, "\n") { + return text + } + + lines := strings.Split(text, "\n") + for len(lines) > 0 && strings.HasPrefix(lines[0], "> ") { + lines = lines[1:] + } + return strings.TrimSpace(strings.Join(lines, "\n")) +} + +func (content *MessageEventContent) RemoveReplyFallback() { + if len(content.GetReplyTo()) > 0 && !content.replyFallbackRemoved { + if content.Format == FormatHTML { + content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody) + } + content.Body = TrimReplyFallbackText(content.Body) + content.replyFallbackRemoved = true + } +} + +func (content *MessageEventContent) GetReplyTo() id.EventID { + if content.RelatesTo != nil && content.RelatesTo.Type == RelReply { + return content.RelatesTo.EventID + } + return "" +} + +const ReplyFormat = `
In reply to %s
%s
` + +func (evt *Event) GenerateReplyFallbackHTML() string { + parsedContent, ok := evt.Content.Parsed.(*MessageEventContent) + if !ok { + return "" + } + parsedContent.RemoveReplyFallback() + body := parsedContent.FormattedBody + if len(body) == 0 { + body = html.EscapeString(parsedContent.Body) + } + + senderDisplayName := evt.Sender + + return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body) +} + +func (evt *Event) GenerateReplyFallbackText() string { + parsedContent, ok := evt.Content.Parsed.(*MessageEventContent) + if !ok { + return "" + } + parsedContent.RemoveReplyFallback() + body := parsedContent.Body + lines := strings.Split(strings.TrimSpace(body), "\n") + firstLine, lines := lines[0], lines[1:] + + senderDisplayName := evt.Sender + + var fallbackText strings.Builder + _, _ = fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine) + for _, line := range lines { + _, _ = fmt.Fprintf(&fallbackText, "\n> %s", line) + } + fallbackText.WriteString("\n\n") + return fallbackText.String() +} + +func (content *MessageEventContent) SetReply(inReplyTo *Event) { + content.RelatesTo = &RelatesTo{ + EventID: inReplyTo.ID, + Type: RelReply, + } + + if content.MsgType == MsgText || content.MsgType == MsgNotice { + if len(content.FormattedBody) == 0 || content.Format != FormatHTML { + content.FormattedBody = html.EscapeString(content.Body) + content.Format = FormatHTML + } + content.FormattedBody = inReplyTo.GenerateReplyFallbackHTML() + content.FormattedBody + content.Body = inReplyTo.GenerateReplyFallbackText() + content.Body + content.replyFallbackRemoved = false + } +} diff --git a/vendor/maunium.net/go/mautrix/event/state.go b/vendor/maunium.net/go/mautrix/event/state.go new file mode 100644 index 0000000000..7184d608ab --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/state.go @@ -0,0 +1,110 @@ +// 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 event + +import ( + "maunium.net/go/mautrix/id" +) + +// CanonicalAliasEventContent represents the content of a m.room.canonical_alias state event. +// https://matrix.org/docs/spec/client_server/r0.6.1#m-room-canonical-alias +type CanonicalAliasEventContent struct { + Alias id.RoomAlias `json:"alias"` + AltAliases []id.RoomAlias `json:"alt_aliases,omitempty"` +} + +// RoomNameEventContent represents the content of a m.room.name state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-name +type RoomNameEventContent struct { + Name string `json:"name"` +} + +// RoomAvatarEventContent represents the content of a m.room.avatar state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-avatar +type RoomAvatarEventContent struct { + URL id.ContentURI `json:"url"` +} + +// TopicEventContent represents the content of a m.room.topic state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-topic +type TopicEventContent struct { + Topic string `json:"topic"` +} + +// TombstoneEventContent represents the content of a m.room.tombstone state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-tombstone +type TombstoneEventContent struct { + Body string `json:"body"` + ReplacementRoom id.RoomID `json:"replacement_room"` +} + +// CreateEventContent represents the content of a m.room.create state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-create +type CreateEventContent struct { + Creator id.UserID `json:"creator"` + Federate bool `json:"m.federate,omitempty"` + RoomVersion string `json:"version,omitempty"` + Predecessor struct { + RoomID id.RoomID `json:"room_id"` + EventID id.EventID `json:"event_id"` + } `json:"predecessor"` +} + +// JoinRule specifies how open a room is to new members. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-join-rules +type JoinRule string + +const ( + JoinRulePublic JoinRule = "public" + JoinRuleKnock JoinRule = "knock" + JoinRuleInvite JoinRule = "invite" + JoinRulePrivate JoinRule = "private" +) + +// JoinRulesEventContent represents the content of a m.room.join_rules state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-join-rules +type JoinRulesEventContent struct { + JoinRule JoinRule `json:"join_rule"` +} + +// PinnedEventsEventContent represents the content of a m.room.pinned_events state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-pinned-events +type PinnedEventsEventContent struct { + Pinned []id.EventID `json:"pinned"` +} + +// HistoryVisibility specifies who can see new messages. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-history-visibility +type HistoryVisibility string + +const ( + HistoryVisibilityInvited HistoryVisibility = "invited" + HistoryVisibilityJoined HistoryVisibility = "joined" + HistoryVisibilityShared HistoryVisibility = "shared" + HistoryVisibilityWorldReadable HistoryVisibility = "world_readable" +) + +// HistoryVisibilityEventContent represents the content of a m.room.history_visibility state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-history-visibility +type HistoryVisibilityEventContent struct { + HistoryVisibility HistoryVisibility `json:"history_visibility"` +} + +// GuestAccess specifies whether or not guest accounts can join. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-guest-access +type GuestAccess string + +const ( + GuestAccessCanJoin GuestAccess = "can_join" + GuestAccessForbidden GuestAccess = "forbidden" +) + +// GuestAccessEventContent represents the content of a m.room.guest_access state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-guest-access +type GuestAccessEventContent struct { + GuestAccess GuestAccess `json:"guest_access"` +} diff --git a/vendor/maunium.net/go/mautrix/event/type.go b/vendor/maunium.net/go/mautrix/event/type.go new file mode 100644 index 0000000000..c6cf85456d --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/type.go @@ -0,0 +1,235 @@ +// 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 event + +import ( + "encoding/json" + "fmt" + "strings" +) + +type TypeClass int + +func (tc TypeClass) Name() string { + switch tc { + case MessageEventType: + return "message" + case StateEventType: + return "state" + case EphemeralEventType: + return "ephemeral" + case AccountDataEventType: + return "account data" + case ToDeviceEventType: + return "to-device" + default: + return "unknown" + } +} + +const ( + // Unknown events + UnknownEventType TypeClass = iota + // Normal message events + MessageEventType + // State events + StateEventType + // Ephemeral events + EphemeralEventType + // Account data events + AccountDataEventType + // Device-to-device events + ToDeviceEventType +) + +type Type struct { + Type string + Class TypeClass +} + +func NewEventType(name string) Type { + evtType := Type{Type: name} + evtType.Class = evtType.GuessClass() + return evtType +} + +func (et *Type) IsState() bool { + return et.Class == StateEventType +} + +func (et *Type) IsEphemeral() bool { + return et.Class == EphemeralEventType +} + +func (et *Type) IsAccountData() bool { + return et.Class == AccountDataEventType +} + +func (et *Type) IsToDevice() bool { + return et.Class == ToDeviceEventType +} + +func (et *Type) IsInRoomVerification() bool { + switch et.Type { + case InRoomVerificationStart.Type, InRoomVerificationReady.Type, InRoomVerificationAccept.Type, + InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type: + return true + default: + return false + } +} + +func (et *Type) IsCall() bool { + switch et.Type { + case CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type, + CallNegotiate.Type, CallHangup.Type: + return true + default: + return false + } +} + +func (et *Type) IsCustom() bool { + return !strings.HasPrefix(et.Type, "m.") +} + +func (et *Type) GuessClass() TypeClass { + switch et.Type { + case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type, + StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type, + StateTombstone.Type, StateEncryption.Type: + return StateEventType + case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type: + return EphemeralEventType + case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type, + AccountDataSecretStorageKey.Type, AccountDataSecretStorageDefaultKey.Type, + AccountDataCrossSigningMaster.Type, AccountDataCrossSigningSelf.Type, AccountDataCrossSigningUser.Type: + return AccountDataEventType + case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type, + InRoomVerificationStart.Type, InRoomVerificationReady.Type, InRoomVerificationAccept.Type, + InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type, + CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type, + CallNegotiate.Type, CallHangup.Type: + return MessageEventType + case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type: + return ToDeviceEventType + default: + return UnknownEventType + } +} + +func (et *Type) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &et.Type) + if err != nil { + return err + } + et.Class = et.GuessClass() + return nil +} + +func (et *Type) MarshalJSON() ([]byte, error) { + return json.Marshal(&et.Type) +} + +func (et Type) UnmarshalText(data []byte) error { + et.Type = string(data) + et.Class = et.GuessClass() + return nil +} + +func (et Type) MarshalText() ([]byte, error) { + return []byte(et.Type), nil +} + +func (et *Type) String() string { + return et.Type +} + +func (et *Type) Repr() string { + return fmt.Sprintf("%s (%s)", et.Type, et.Class.Name()) +} + +// State events +var ( + StateAliases = Type{"m.room.aliases", StateEventType} + StateCanonicalAlias = Type{"m.room.canonical_alias", StateEventType} + StateCreate = Type{"m.room.create", StateEventType} + StateJoinRules = Type{"m.room.join_rules", StateEventType} + StateHistoryVisibility = Type{"m.room.history_visibility", StateEventType} + StateGuestAccess = Type{"m.room.guest_access", StateEventType} + StateMember = Type{"m.room.member", StateEventType} + StatePowerLevels = Type{"m.room.power_levels", StateEventType} + StateRoomName = Type{"m.room.name", StateEventType} + StateTopic = Type{"m.room.topic", StateEventType} + StateRoomAvatar = Type{"m.room.avatar", StateEventType} + StatePinnedEvents = Type{"m.room.pinned_events", StateEventType} + StateTombstone = Type{"m.room.tombstone", StateEventType} + StateEncryption = Type{"m.room.encryption", StateEventType} +) + +// Message events +var ( + EventRedaction = Type{"m.room.redaction", MessageEventType} + EventMessage = Type{"m.room.message", MessageEventType} + EventEncrypted = Type{"m.room.encrypted", MessageEventType} + EventReaction = Type{"m.reaction", MessageEventType} + EventSticker = Type{"m.sticker", MessageEventType} + + InRoomVerificationStart = Type{"m.key.verification.start", MessageEventType} + InRoomVerificationReady = Type{"m.key.verification.ready", MessageEventType} + InRoomVerificationAccept = Type{"m.key.verification.accept", MessageEventType} + InRoomVerificationKey = Type{"m.key.verification.key", MessageEventType} + InRoomVerificationMAC = Type{"m.key.verification.mac", MessageEventType} + InRoomVerificationCancel = Type{"m.key.verification.cancel", MessageEventType} + + CallInvite = Type{"m.call.invite", MessageEventType} + CallCandidates = Type{"m.call.candidates", MessageEventType} + CallAnswer = Type{"m.call.answer", MessageEventType} + CallReject = Type{"m.call.reject", MessageEventType} + CallSelectAnswer = Type{"m.call.select_answer", MessageEventType} + CallNegotiate = Type{"m.call.negotiate", MessageEventType} + CallHangup = Type{"m.call.hangup", MessageEventType} +) + +// Ephemeral events +var ( + EphemeralEventReceipt = Type{"m.receipt", EphemeralEventType} + EphemeralEventTyping = Type{"m.typing", EphemeralEventType} + EphemeralEventPresence = Type{"m.presence", EphemeralEventType} +) + +// Account data events +var ( + AccountDataDirectChats = Type{"m.direct", AccountDataEventType} + AccountDataPushRules = Type{"m.push_rules", AccountDataEventType} + AccountDataRoomTags = Type{"m.tag", AccountDataEventType} + AccountDataFullyRead = Type{"m.fully_read", AccountDataEventType} + AccountDataIgnoredUserList = Type{"m.ignored_user_list", AccountDataEventType} + + AccountDataSecretStorageDefaultKey = Type{"m.secret_storage.default_key", AccountDataEventType} + AccountDataSecretStorageKey = Type{"m.secret_storage.key", AccountDataEventType} + AccountDataCrossSigningMaster = Type{"m.cross_signing.master", AccountDataEventType} + AccountDataCrossSigningUser = Type{"m.cross_signing.user_signing", AccountDataEventType} + AccountDataCrossSigningSelf = Type{"m.cross_signing.self_signing", AccountDataEventType} +) + +// Device-to-device events +var ( + ToDeviceRoomKey = Type{"m.room_key", ToDeviceEventType} + ToDeviceRoomKeyRequest = Type{"m.room_key_request", ToDeviceEventType} + ToDeviceForwardedRoomKey = Type{"m.forwarded_room_key", ToDeviceEventType} + ToDeviceEncrypted = Type{"m.room.encrypted", ToDeviceEventType} + ToDeviceRoomKeyWithheld = Type{"m.room_key.withheld", ToDeviceEventType} + ToDeviceVerificationRequest = Type{"m.key.verification.request", ToDeviceEventType} + ToDeviceVerificationStart = Type{"m.key.verification.start", ToDeviceEventType} + ToDeviceVerificationAccept = Type{"m.key.verification.accept", ToDeviceEventType} + ToDeviceVerificationKey = Type{"m.key.verification.key", ToDeviceEventType} + ToDeviceVerificationMAC = Type{"m.key.verification.mac", ToDeviceEventType} + ToDeviceVerificationCancel = Type{"m.key.verification.cancel", ToDeviceEventType} + + ToDeviceOrgMatrixRoomKeyWithheld = Type{"org.matrix.room_key.withheld", ToDeviceEventType} +) diff --git a/vendor/maunium.net/go/mautrix/event/verification.go b/vendor/maunium.net/go/mautrix/event/verification.go new file mode 100644 index 0000000000..800e7b124e --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/verification.go @@ -0,0 +1,306 @@ +// Copyright (c) 2020 Nikos Filippakis +// +// 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 event + +import ( + "maunium.net/go/mautrix/id" +) + +type VerificationMethod string + +const VerificationMethodSAS VerificationMethod = "m.sas.v1" + +// VerificationRequestEventContent represents the content of a m.key.verification.request to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-request +type VerificationRequestEventContent struct { + // The device ID which is initiating the request. + FromDevice id.DeviceID `json:"from_device"` + // An opaque identifier for the verification request. Must be unique with respect to the devices involved. + TransactionID string `json:"transaction_id,omitempty"` + // The verification methods supported by the sender. + Methods []VerificationMethod `json:"methods"` + // The POSIX timestamp in milliseconds for when the request was made. + Timestamp int64 `json:"timestamp,omitempty"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vrec *VerificationRequestEventContent) SupportsVerificationMethod(meth VerificationMethod) bool { + for _, supportedMeth := range vrec.Methods { + if supportedMeth == meth { + return true + } + } + return false +} + +type KeyAgreementProtocol string + +const ( + KeyAgreementCurve25519 KeyAgreementProtocol = "curve25519" + KeyAgreementCurve25519HKDFSHA256 KeyAgreementProtocol = "curve25519-hkdf-sha256" +) + +type VerificationHashMethod string + +const VerificationHashSHA256 VerificationHashMethod = "sha256" + +type MACMethod string + +const HKDFHMACSHA256 MACMethod = "hkdf-hmac-sha256" + +type SASMethod string + +const ( + SASDecimal SASMethod = "decimal" + SASEmoji SASMethod = "emoji" +) + +// VerificationStartEventContent represents the content of a m.key.verification.start to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-start +type VerificationStartEventContent struct { + // The device ID which is initiating the process. + FromDevice id.DeviceID `json:"from_device"` + // An opaque identifier for the verification process. Must be unique with respect to the devices involved. + TransactionID string `json:"transaction_id,omitempty"` + // The verification method to use. + Method VerificationMethod `json:"method"` + // The key agreement protocols the sending device understands. + KeyAgreementProtocols []KeyAgreementProtocol `json:"key_agreement_protocols"` + // The hash methods the sending device understands. + Hashes []VerificationHashMethod `json:"hashes"` + // The message authentication codes that the sending device understands. + MessageAuthenticationCodes []MACMethod `json:"message_authentication_codes"` + // The SAS methods the sending device (and the sending device's user) understands. + ShortAuthenticationString []SASMethod `json:"short_authentication_string"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vsec *VerificationStartEventContent) SupportsKeyAgreementProtocol(proto KeyAgreementProtocol) bool { + for _, supportedProto := range vsec.KeyAgreementProtocols { + if supportedProto == proto { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) SupportsHashMethod(alg VerificationHashMethod) bool { + for _, supportedAlg := range vsec.Hashes { + if supportedAlg == alg { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) SupportsMACMethod(meth MACMethod) bool { + for _, supportedMeth := range vsec.MessageAuthenticationCodes { + if supportedMeth == meth { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) SupportsSASMethod(meth SASMethod) bool { + for _, supportedMeth := range vsec.ShortAuthenticationString { + if supportedMeth == meth { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) GetRelatesTo() *RelatesTo { + if vsec.RelatesTo == nil { + vsec.RelatesTo = &RelatesTo{} + } + return vsec.RelatesTo +} + +func (vsec *VerificationStartEventContent) OptionalGetRelatesTo() *RelatesTo { + return vsec.RelatesTo +} + +func (vsec *VerificationStartEventContent) SetRelatesTo(rel *RelatesTo) { + vsec.RelatesTo = rel +} + +// VerificationReadyEventContent represents the content of a m.key.verification.ready event. +type VerificationReadyEventContent struct { + // The device ID which accepted the process. + FromDevice id.DeviceID `json:"from_device"` + // The verification methods supported by the sender. + Methods []VerificationMethod `json:"methods"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +var _ Relatable = (*VerificationReadyEventContent)(nil) + +func (vrec *VerificationReadyEventContent) GetRelatesTo() *RelatesTo { + if vrec.RelatesTo == nil { + vrec.RelatesTo = &RelatesTo{} + } + return vrec.RelatesTo +} + +func (vrec *VerificationReadyEventContent) OptionalGetRelatesTo() *RelatesTo { + return vrec.RelatesTo +} + +func (vrec *VerificationReadyEventContent) SetRelatesTo(rel *RelatesTo) { + vrec.RelatesTo = rel +} + +// VerificationAcceptEventContent represents the content of a m.key.verification.accept to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-accept +type VerificationAcceptEventContent struct { + // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. + TransactionID string `json:"transaction_id,omitempty"` + // The verification method to use. + Method VerificationMethod `json:"method"` + // The key agreement protocol the device is choosing to use, out of the options in the m.key.verification.start message. + KeyAgreementProtocol KeyAgreementProtocol `json:"key_agreement_protocol"` + // The hash method the device is choosing to use, out of the options in the m.key.verification.start message. + Hash VerificationHashMethod `json:"hash"` + // The message authentication code the device is choosing to use, out of the options in the m.key.verification.start message. + MessageAuthenticationCode MACMethod `json:"message_authentication_code"` + // The SAS methods both devices involved in the verification process understand. Must be a subset of the options in the m.key.verification.start message. + ShortAuthenticationString []SASMethod `json:"short_authentication_string"` + // The hash (encoded as unpadded base64) of the concatenation of the device's ephemeral public key (encoded as unpadded base64) and the canonical JSON representation of the m.key.verification.start message. + Commitment string `json:"commitment"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vaec *VerificationAcceptEventContent) GetRelatesTo() *RelatesTo { + if vaec.RelatesTo == nil { + vaec.RelatesTo = &RelatesTo{} + } + return vaec.RelatesTo +} + +func (vaec *VerificationAcceptEventContent) OptionalGetRelatesTo() *RelatesTo { + return vaec.RelatesTo +} + +func (vaec *VerificationAcceptEventContent) SetRelatesTo(rel *RelatesTo) { + vaec.RelatesTo = rel +} + +// VerificationKeyEventContent represents the content of a m.key.verification.key to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-key +type VerificationKeyEventContent struct { + // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. + TransactionID string `json:"transaction_id,omitempty"` + // The device's ephemeral public key, encoded as unpadded base64. + Key string `json:"key"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vkec *VerificationKeyEventContent) GetRelatesTo() *RelatesTo { + if vkec.RelatesTo == nil { + vkec.RelatesTo = &RelatesTo{} + } + return vkec.RelatesTo +} + +func (vkec *VerificationKeyEventContent) OptionalGetRelatesTo() *RelatesTo { + return vkec.RelatesTo +} + +func (vkec *VerificationKeyEventContent) SetRelatesTo(rel *RelatesTo) { + vkec.RelatesTo = rel +} + +// VerificationMacEventContent represents the content of a m.key.verification.mac to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-mac +type VerificationMacEventContent struct { + // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. + TransactionID string `json:"transaction_id,omitempty"` + // A map of the key ID to the MAC of the key, using the algorithm in the verification process. The MAC is encoded as unpadded base64. + Mac map[id.KeyID]string `json:"mac"` + // The MAC of the comma-separated, sorted, list of key IDs given in the mac property, encoded as unpadded base64. + Keys string `json:"keys"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vmec *VerificationMacEventContent) GetRelatesTo() *RelatesTo { + if vmec.RelatesTo == nil { + vmec.RelatesTo = &RelatesTo{} + } + return vmec.RelatesTo +} + +func (vmec *VerificationMacEventContent) OptionalGetRelatesTo() *RelatesTo { + return vmec.RelatesTo +} + +func (vmec *VerificationMacEventContent) SetRelatesTo(rel *RelatesTo) { + vmec.RelatesTo = rel +} + +type VerificationCancelCode string + +const ( + VerificationCancelByUser VerificationCancelCode = "m.user" + VerificationCancelByTimeout VerificationCancelCode = "m.timeout" + VerificationCancelUnknownTransaction VerificationCancelCode = "m.unknown_transaction" + VerificationCancelUnknownMethod VerificationCancelCode = "m.unknown_method" + VerificationCancelUnexpectedMessage VerificationCancelCode = "m.unexpected_message" + VerificationCancelKeyMismatch VerificationCancelCode = "m.key_mismatch" + VerificationCancelUserMismatch VerificationCancelCode = "m.user_mismatch" + VerificationCancelInvalidMessage VerificationCancelCode = "m.invalid_message" + VerificationCancelAccepted VerificationCancelCode = "m.accepted" + VerificationCancelSASMismatch VerificationCancelCode = "m.mismatched_sas" + VerificationCancelCommitmentMismatch VerificationCancelCode = "m.mismatched_commitment" +) + +// VerificationCancelEventContent represents the content of a m.key.verification.cancel to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-cancel +type VerificationCancelEventContent struct { + // The opaque identifier for the verification process/request. + TransactionID string `json:"transaction_id,omitempty"` + // A human readable description of the code. The client should only rely on this string if it does not understand the code. + Reason string `json:"reason"` + // The error code for why the process/request was cancelled by the user. + Code VerificationCancelCode `json:"code"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vcec *VerificationCancelEventContent) GetRelatesTo() *RelatesTo { + if vcec.RelatesTo == nil { + vcec.RelatesTo = &RelatesTo{} + } + return vcec.RelatesTo +} + +func (vcec *VerificationCancelEventContent) OptionalGetRelatesTo() *RelatesTo { + return vcec.RelatesTo +} + +func (vcec *VerificationCancelEventContent) SetRelatesTo(rel *RelatesTo) { + vcec.RelatesTo = rel +} diff --git a/vendor/maunium.net/go/mautrix/event/voip.go b/vendor/maunium.net/go/mautrix/event/voip.go new file mode 100644 index 0000000000..28f56c9533 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/voip.go @@ -0,0 +1,116 @@ +// 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 event + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type CallHangupReason string + +const ( + CallHangupICEFailed CallHangupReason = "ice_failed" + CallHangupInviteTimeout CallHangupReason = "invite_timeout" + CallHangupUserHangup CallHangupReason = "user_hangup" + CallHangupUserMediaFailed CallHangupReason = "user_media_failed" + CallHangupUnknownError CallHangupReason = "unknown_error" +) + +type CallDataType string + +const ( + CallDataTypeOffer CallDataType = "offer" + CallDataTypeAnswer CallDataType = "answer" +) + +type CallData struct { + SDP string `json:"sdp"` + Type CallDataType `json:"type"` +} + +type CallCandidate struct { + Candidate string `json:"candidate"` + SDPMLineIndex int `json:"sdpMLineIndex"` + SDPMID string `json:"sdpMid"` +} + +type CallVersion string + +func (cv *CallVersion) UnmarshalJSON(raw []byte) error { + var numberVersion int + err := json.Unmarshal(raw, &numberVersion) + if err != nil { + var stringVersion string + err = json.Unmarshal(raw, &stringVersion) + if err != nil { + return fmt.Errorf("failed to parse CallVersion: %w", err) + } + *cv = CallVersion(stringVersion) + } else { + *cv = CallVersion(strconv.Itoa(numberVersion)) + } + return nil +} + +func (cv *CallVersion) MarshalJSON() ([]byte, error) { + for _, char := range *cv { + if char < '0' || char > '9' { + // The version contains weird characters, return as string. + return json.Marshal(string(*cv)) + } + } + // The version consists of only ASCII digits, return as an integer. + return []byte(*cv), nil +} + +func (cv *CallVersion) Int() (int, error) { + return strconv.Atoi(string(*cv)) +} + +type BaseCallEventContent struct { + CallID string `json:"call_id"` + PartyID string `json:"party_id"` + Version CallVersion `json:"version"` +} + +type CallInviteEventContent struct { + BaseCallEventContent + Lifetime int `json:"lifetime"` + Offer CallData `json:"offer"` +} + +type CallCandidatesEventContent struct { + BaseCallEventContent + Candidates []CallCandidate `json:"candidates"` +} + +type CallRejectEventContent struct { + BaseCallEventContent +} + +type CallAnswerEventContent struct { + BaseCallEventContent + Answer CallData `json:"answer"` +} + +type CallSelectAnswerEventContent struct { + BaseCallEventContent + SelectedPartyID string `json:"selected_party_id"` +} + +type CallNegotiateEventContent struct { + BaseCallEventContent + Lifetime int `json:"lifetime"` + Description CallData `json:"description"` +} + +type CallHangupEventContent struct { + BaseCallEventContent + Reason CallHangupReason `json:"reason"` +} diff --git a/vendor/maunium.net/go/mautrix/filter.go b/vendor/maunium.net/go/mautrix/filter.go new file mode 100644 index 0000000000..71235f2dde --- /dev/null +++ b/vendor/maunium.net/go/mautrix/filter.go @@ -0,0 +1,93 @@ +// Copyright 2017 Jan Christian Grünhage + +package mautrix + +import ( + "errors" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type EventFormat string + +const ( + EventFormatClient EventFormat = "client" + EventFormatFederation EventFormat = "federation" +) + +//Filter is used by clients to specify how the server should filter responses to e.g. sync requests +//Specified by: https://matrix.org/docs/spec/client_server/r0.6.0.html#filtering +type Filter struct { + AccountData FilterPart `json:"account_data,omitempty"` + EventFields []string `json:"event_fields,omitempty"` + EventFormat EventFormat `json:"event_format,omitempty"` + Presence FilterPart `json:"presence,omitempty"` + Room RoomFilter `json:"room,omitempty"` +} + +// RoomFilter is used to define filtering rules for room events +type RoomFilter struct { + AccountData FilterPart `json:"account_data,omitempty"` + Ephemeral FilterPart `json:"ephemeral,omitempty"` + IncludeLeave bool `json:"include_leave,omitempty"` + NotRooms []id.RoomID `json:"not_rooms,omitempty"` + Rooms []id.RoomID `json:"rooms,omitempty"` + State FilterPart `json:"state,omitempty"` + Timeline FilterPart `json:"timeline,omitempty"` +} + +// FilterPart is used to define filtering rules for specific categories of events +type FilterPart struct { + NotRooms []id.RoomID `json:"not_rooms,omitempty"` + Rooms []id.RoomID `json:"rooms,omitempty"` + Limit int `json:"limit,omitempty"` + NotSenders []id.UserID `json:"not_senders,omitempty"` + NotTypes []event.Type `json:"not_types,omitempty"` + Senders []id.UserID `json:"senders,omitempty"` + Types []event.Type `json:"types,omitempty"` + ContainsURL *bool `json:"contains_url,omitempty"` + + LazyLoadMembers bool `json:"lazy_load_members,omitempty"` + IncludeRedundantMembers bool `json:"include_redundant_members,omitempty"` +} + +// Validate checks if the filter contains valid property values +func (filter *Filter) Validate() error { + if filter.EventFormat != EventFormatClient && filter.EventFormat != EventFormatFederation { + return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]") + } + return nil +} + +// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request +func DefaultFilter() Filter { + return Filter{ + AccountData: DefaultFilterPart(), + EventFields: nil, + EventFormat: "client", + Presence: DefaultFilterPart(), + Room: RoomFilter{ + AccountData: DefaultFilterPart(), + Ephemeral: DefaultFilterPart(), + IncludeLeave: false, + NotRooms: nil, + Rooms: nil, + State: DefaultFilterPart(), + Timeline: DefaultFilterPart(), + }, + } +} + +// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request +func DefaultFilterPart() FilterPart { + return FilterPart{ + NotRooms: nil, + Rooms: nil, + Limit: 20, + NotSenders: nil, + NotTypes: nil, + Senders: nil, + Types: nil, + } +} diff --git a/vendor/maunium.net/go/mautrix/go.mod b/vendor/maunium.net/go/mautrix/go.mod new file mode 100644 index 0000000000..999cde5fd2 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/go.mod @@ -0,0 +1,19 @@ +module maunium.net/go/mautrix + +go 1.14 + +require ( + github.com/btcsuite/btcutil v1.0.2 + github.com/gorilla/mux v1.8.0 + github.com/gorilla/websocket v1.4.2 + github.com/lib/pq v1.9.0 + github.com/mattn/go-sqlite3 v1.14.6 + github.com/russross/blackfriday/v2 v2.1.0 + github.com/stretchr/testify v1.6.1 + github.com/tidwall/gjson v1.6.8 + github.com/tidwall/sjson v1.1.5 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d + gopkg.in/yaml.v2 v2.3.0 + maunium.net/go/maulogger/v2 v2.2.4 +) diff --git a/vendor/maunium.net/go/mautrix/go.sum b/vendor/maunium.net/go/mautrix/go.sum new file mode 100644 index 0000000000..ba7aa381d1 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/go.sum @@ -0,0 +1,77 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +maunium.net/go/maulogger/v2 v2.2.4 h1:oV2GDeM4fx1uRysdpDC0FcrPg+thFicSd9XzPcYMbVY= +maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= 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 0000000000..967bba9a57 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/contenturi.go @@ -0,0 +1,133 @@ +// 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" + "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://matrix.org/docs/spec/client_server/r0.6.0#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.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) { + if uri.IsEmpty() { + return []byte(""), nil + } + return []byte(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 0000000000..5683cc4746 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/crypto.go @@ -0,0 +1,114 @@ +// 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://matrix.org/docs/spec/client_server/r0.6.0#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) +} + +// 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 : 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 : 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:]) +} 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 0000000000..1742d45723 --- /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], + uri.MXID1, + } + if uri.Sigil2 != 0 { + parts = append(parts, SigilToPathSegment[uri.Sigil2], 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.QueryEscape(uri.PrimaryIdentifier())) + if uri.Sigil2 != 0 { + fragment = fmt.Sprintf("%s/%s", fragment, url.QueryEscape(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 0000000000..0f632f11ed --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/opaque.go @@ -0,0 +1,41 @@ +// 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 + +func (roomID RoomID) String() string { + return string(roomID) +} + +func (roomAlias RoomAlias) String() string { + return string(roomAlias) +} + +func (eventID EventID) String() string { + return string(eventID) +} diff --git a/vendor/github.com/matrix-org/gomatrix/userids.go b/vendor/maunium.net/go/mautrix/id/userid.go similarity index 57% rename from vendor/github.com/matrix-org/gomatrix/userids.go rename to vendor/maunium.net/go/mautrix/id/userid.go index 70002c5b05..67199419bf 100644 --- a/vendor/github.com/matrix-org/gomatrix/userids.go +++ b/vendor/maunium.net/go/mautrix/id/userid.go @@ -1,12 +1,95 @@ -package gomatrix +// 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 +} + +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") @@ -116,15 +199,3 @@ func DecodeUserLocalpart(str string) (string, error) { } return outputBuffer.String(), nil } - -// ExtractUserLocalpart extracts the localpart portion of a user ID. -// See http://matrix.org/docs/spec/intro.html#user-identifiers -func ExtractUserLocalpart(userID string) (string, error) { - if len(userID) == 0 || userID[0] != '@' { - return "", fmt.Errorf("%s is not a valid user id", userID) - } - return strings.TrimPrefix( - strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ] - "@", // remove "@" prefix - ), nil -} diff --git a/vendor/maunium.net/go/mautrix/pushrules/action.go b/vendor/maunium.net/go/mautrix/pushrules/action.go new file mode 100644 index 0000000000..1f885e0d02 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/action.go @@ -0,0 +1,124 @@ +// 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 pushrules + +import "encoding/json" + +// PushActionType is the type of a PushAction +type PushActionType string + +// The allowed push action types as specified in spec section 11.12.1.4.1. +const ( + ActionNotify PushActionType = "notify" + ActionDontNotify PushActionType = "dont_notify" + ActionCoalesce PushActionType = "coalesce" + ActionSetTweak PushActionType = "set_tweak" +) + +// PushActionTweak is the type of the tweak in SetTweak push actions. +type PushActionTweak string + +// The allowed tweak types as specified in spec section 11.12.1.4.1.1. +const ( + TweakSound PushActionTweak = "sound" + TweakHighlight PushActionTweak = "highlight" +) + +// PushActionArray is an array of PushActions. +type PushActionArray []*PushAction + +// PushActionArrayShould contains the important information parsed from a PushActionArray. +type PushActionArrayShould struct { + // Whether or not the array contained a Notify, DontNotify or Coalesce action type. + NotifySpecified bool + // Whether or not the event in question should trigger a notification. + Notify bool + // Whether or not the event in question should be highlighted. + Highlight bool + + // Whether or not the event in question should trigger a sound alert. + PlaySound bool + // The name of the sound to play if PlaySound is true. + SoundName string +} + +// Should parses this push action array and returns the relevant details wrapped in a PushActionArrayShould struct. +func (actions PushActionArray) Should() (should PushActionArrayShould) { + for _, action := range actions { + switch action.Action { + case ActionNotify, ActionCoalesce: + should.Notify = true + should.NotifySpecified = true + case ActionDontNotify: + should.Notify = false + should.NotifySpecified = true + case ActionSetTweak: + switch action.Tweak { + case TweakHighlight: + var ok bool + should.Highlight, ok = action.Value.(bool) + if !ok { + // Highlight value not specified, so assume true since the tweak is set. + should.Highlight = true + } + case TweakSound: + should.SoundName = action.Value.(string) + should.PlaySound = len(should.SoundName) > 0 + } + } + } + return +} + +// PushAction is a single action that should be triggered when receiving a message. +type PushAction struct { + Action PushActionType + Tweak PushActionTweak + Value interface{} +} + +// UnmarshalJSON parses JSON into this PushAction. +// +// * If the JSON is a single string, the value is stored in the Action field. +// * If the JSON is an object with the set_tweak field, Action will be set to +// "set_tweak", Tweak will be set to the value of the set_tweak field and +// and Value will be set to the value of the value field. +// * In any other case, the function does nothing. +func (action *PushAction) UnmarshalJSON(raw []byte) error { + var data interface{} + + err := json.Unmarshal(raw, &data) + if err != nil { + return err + } + + switch val := data.(type) { + case string: + action.Action = PushActionType(val) + case map[string]interface{}: + tweak, ok := val["set_tweak"].(string) + if ok { + action.Action = ActionSetTweak + action.Tweak = PushActionTweak(tweak) + action.Value, _ = val["value"] + } + } + return nil +} + +// MarshalJSON is the reverse of UnmarshalJSON() +func (action *PushAction) MarshalJSON() (raw []byte, err error) { + if action.Action == ActionSetTweak { + data := map[string]interface{}{ + "set_tweak": action.Tweak, + "value": action.Value, + } + return json.Marshal(&data) + } + data := string(action.Action) + return json.Marshal(&data) +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/condition.go b/vendor/maunium.net/go/mautrix/pushrules/condition.go new file mode 100644 index 0000000000..f11d2b8c35 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/condition.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 pushrules + +import ( + "regexp" + "strconv" + "strings" + "unicode" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/pushrules/glob" +) + +// Room is an interface with the functions that are needed for processing room-specific push conditions +type Room interface { + GetOwnDisplayname() string + GetMemberCount() int +} + +// PushCondKind is the type of a push condition. +type PushCondKind string + +// The allowed push condition kinds as specified in section 11.12.1.4.3 of r0.3.0 of the Client-Server API. +const ( + KindEventMatch PushCondKind = "event_match" + KindContainsDisplayName PushCondKind = "contains_display_name" + KindRoomMemberCount PushCondKind = "room_member_count" +) + +// PushCondition wraps a condition that is required for a specific PushRule to be used. +type PushCondition struct { + // The type of the condition. + Kind PushCondKind `json:"kind"` + // The dot-separated field of the event to match. Only applicable if kind is EventMatch. + Key string `json:"key,omitempty"` + // The glob-style pattern to match the field against. Only applicable if kind is EventMatch. + Pattern string `json:"pattern,omitempty"` + // The condition that needs to be fulfilled for RoomMemberCount-type conditions. + // A decimal integer optionally prefixed by ==, <, >, >= or <=. Prefix "==" is assumed if no prefix found. + MemberCountCondition string `json:"is,omitempty"` +} + +// MemberCountFilterRegex is the regular expression to parse the MemberCountCondition of PushConditions. +var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$") + +// Match checks if this condition is fulfilled for the given event in the given room. +func (cond *PushCondition) Match(room Room, evt *event.Event) bool { + switch cond.Kind { + case KindEventMatch: + return cond.matchValue(room, evt) + case KindContainsDisplayName: + return cond.matchDisplayName(room, evt) + case KindRoomMemberCount: + return cond.matchMemberCount(room) + default: + return false + } +} + +func (cond *PushCondition) matchValue(room Room, evt *event.Event) bool { + index := strings.IndexRune(cond.Key, '.') + key := cond.Key + subkey := "" + if index > 0 { + subkey = key[index+1:] + key = key[0:index] + } + + pattern, err := glob.Compile(cond.Pattern) + if err != nil { + return false + } + + switch key { + case "type": + return pattern.MatchString(evt.Type.String()) + case "sender": + return pattern.MatchString(string(evt.Sender)) + case "room_id": + return pattern.MatchString(string(evt.RoomID)) + case "state_key": + if evt.StateKey == nil { + return cond.Pattern == "" + } + return pattern.MatchString(*evt.StateKey) + case "content": + val, _ := evt.Content.Raw[subkey].(string) + return pattern.MatchString(val) + default: + return false + } +} + +func (cond *PushCondition) matchDisplayName(room Room, evt *event.Event) bool { + displayname := room.GetOwnDisplayname() + if len(displayname) == 0 { + return false + } + + msg, ok := evt.Content.Raw["body"].(string) + if !ok { + return false + } + + isAcceptable := func(r uint8) bool { + return unicode.IsSpace(rune(r)) || unicode.IsPunct(rune(r)) + } + length := len(displayname) + for index := strings.Index(msg, displayname); index != -1; index = strings.Index(msg, displayname) { + if (index <= 0 || isAcceptable(msg[index-1])) && (index+length >= len(msg) || isAcceptable(msg[index+length])) { + return true + } + msg = msg[index+len(displayname):] + } + return false +} + +func (cond *PushCondition) matchMemberCount(room Room) bool { + group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition) + if len(group) != 3 { + return false + } + + operator := group[1] + wantedMemberCount, _ := strconv.Atoi(group[2]) + + memberCount := room.GetMemberCount() + + switch operator { + case "==", "": + return memberCount == wantedMemberCount + case ">": + return memberCount > wantedMemberCount + case ">=": + return memberCount >= wantedMemberCount + case "<": + return memberCount < wantedMemberCount + case "<=": + return memberCount <= wantedMemberCount + default: + // Should be impossible due to regex. + return false + } +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/doc.go b/vendor/maunium.net/go/mautrix/pushrules/doc.go new file mode 100644 index 0000000000..19cd7745f2 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/doc.go @@ -0,0 +1,2 @@ +// Package pushrules contains utilities to parse push notification rules. +package pushrules diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE b/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE new file mode 100644 index 0000000000..cb00d95243 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE @@ -0,0 +1,22 @@ +Glob is licensed under the MIT "Expat" License: + +Copyright (c) 2016: Zachary Yedidia. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/README.md b/vendor/maunium.net/go/mautrix/pushrules/glob/README.md new file mode 100644 index 0000000000..e2e6c6498b --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/glob/README.md @@ -0,0 +1,28 @@ +# String globbing in Go + +[![GoDoc](https://godoc.org/github.com/zyedidia/glob?status.svg)](http://godoc.org/github.com/zyedidia/glob) + +This package adds support for globs in Go. + +It simply converts glob expressions to regexps. I try to follow the standard defined [here](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13). + +# Example + +```go +package main + +import "github.com/zyedidia/glob" + +func main() { + glob, err := glob.Compile("{*.go,*.c}") + if err != nil { + // Error + } + + glob.Match([]byte("test.c")) // true + glob.Match([]byte("hello.go")) // true + glob.Match([]byte("test.d")) // false +} +``` + +You can call all the same functions on a glob that you can call on a regexp. diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go b/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go new file mode 100644 index 0000000000..c270dbc5d1 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go @@ -0,0 +1,108 @@ +// Package glob provides objects for matching strings with globs +package glob + +import "regexp" + +// Glob is a wrapper of *regexp.Regexp. +// It should contain a glob expression compiled into a regular expression. +type Glob struct { + *regexp.Regexp +} + +// Compile a takes a glob expression as a string and transforms it +// into a *Glob object (which is really just a regular expression) +// Compile also returns a possible error. +func Compile(pattern string) (*Glob, error) { + r, err := globToRegex(pattern) + return &Glob{r}, err +} + +func globToRegex(glob string) (*regexp.Regexp, error) { + regex := "" + inGroup := 0 + inClass := 0 + firstIndexInClass := -1 + arr := []byte(glob) + + hasGlobCharacters := false + + for i := 0; i < len(arr); i++ { + ch := arr[i] + + switch ch { + case '\\': + i++ + if i >= len(arr) { + regex += "\\" + } else { + next := arr[i] + switch next { + case ',': + // Nothing + case 'Q', 'E': + regex += "\\\\" + default: + regex += "\\" + } + regex += string(next) + } + case '*': + if inClass == 0 { + regex += ".*" + } else { + regex += "*" + } + hasGlobCharacters = true + case '?': + if inClass == 0 { + regex += "." + } else { + regex += "?" + } + hasGlobCharacters = true + case '[': + inClass++ + firstIndexInClass = i + 1 + regex += "[" + hasGlobCharacters = true + case ']': + inClass-- + regex += "]" + case '.', '(', ')', '+', '|', '^', '$', '@', '%': + if inClass == 0 || (firstIndexInClass == i && ch == '^') { + regex += "\\" + } + regex += string(ch) + hasGlobCharacters = true + case '!': + if firstIndexInClass == i { + regex += "^" + } else { + regex += "!" + } + hasGlobCharacters = true + case '{': + inGroup++ + regex += "(" + hasGlobCharacters = true + case '}': + inGroup-- + regex += ")" + case ',': + if inGroup > 0 { + regex += "|" + hasGlobCharacters = true + } else { + regex += "," + } + default: + regex += string(ch) + } + } + + if hasGlobCharacters { + return regexp.Compile("^" + regex + "$") + } else { + return regexp.Compile(regex) + } +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/pushrules.go b/vendor/maunium.net/go/mautrix/pushrules/pushrules.go new file mode 100644 index 0000000000..cb7d2f6c4f --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/pushrules.go @@ -0,0 +1,37 @@ +// 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 pushrules + +import ( + "encoding/gob" + "encoding/json" + "reflect" + + "maunium.net/go/mautrix/event" +) + +// EventContent represents the content of a m.push_rules account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-push-rules +type EventContent struct { + Ruleset *PushRuleset `json:"global"` +} + +func init() { + event.TypeMap[event.AccountDataPushRules] = reflect.TypeOf(EventContent{}) + gob.Register(&EventContent{}) +} + +// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON. +func EventToPushRules(evt *event.Event) (*PushRuleset, error) { + content := &EventContent{} + err := json.Unmarshal(evt.Content.VeryRaw, content) + if err != nil { + return nil, err + } + + return content.Ruleset, nil +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/rule.go b/vendor/maunium.net/go/mautrix/pushrules/rule.go new file mode 100644 index 0000000000..8ce2da77e1 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/rule.go @@ -0,0 +1,154 @@ +// 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 pushrules + +import ( + "encoding/gob" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "maunium.net/go/mautrix/pushrules/glob" +) + +func init() { + gob.Register(PushRuleArray{}) + gob.Register(PushRuleMap{}) +} + +type PushRuleCollection interface { + GetActions(room Room, evt *event.Event) PushActionArray +} + +type PushRuleArray []*PushRule + +func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray { + for _, rule := range rules { + rule.Type = typ + } + return rules +} + +func (rules PushRuleArray) GetActions(room Room, evt *event.Event) PushActionArray { + for _, rule := range rules { + if !rule.Match(room, evt) { + continue + } + return rule.Actions + } + return nil +} + +type PushRuleMap struct { + Map map[string]*PushRule + Type PushRuleType +} + +func (rules PushRuleArray) SetTypeAndMap(typ PushRuleType) PushRuleMap { + data := PushRuleMap{ + Map: make(map[string]*PushRule), + Type: typ, + } + for _, rule := range rules { + rule.Type = typ + data.Map[rule.RuleID] = rule + } + return data +} + +func (ruleMap PushRuleMap) GetActions(room Room, evt *event.Event) PushActionArray { + var rule *PushRule + var found bool + switch ruleMap.Type { + case RoomRule: + rule, found = ruleMap.Map[string(evt.RoomID)] + case SenderRule: + rule, found = ruleMap.Map[string(evt.Sender)] + } + if found && rule.Match(room, evt) { + return rule.Actions + } + return nil +} + +func (ruleMap PushRuleMap) Unmap() PushRuleArray { + array := make(PushRuleArray, len(ruleMap.Map)) + index := 0 + for _, rule := range ruleMap.Map { + array[index] = rule + index++ + } + return array +} + +type PushRuleType string + +const ( + OverrideRule PushRuleType = "override" + ContentRule PushRuleType = "content" + RoomRule PushRuleType = "room" + SenderRule PushRuleType = "sender" + UnderrideRule PushRuleType = "underride" +) + +type PushRule struct { + // The type of this rule. + Type PushRuleType `json:"-"` + // The ID of this rule. + // For room-specific rules and user-specific rules, this is the room or user ID (respectively) + // For other types of rules, this doesn't affect anything. + RuleID string `json:"rule_id"` + // The actions this rule should trigger when matched. + Actions PushActionArray `json:"actions"` + // Whether this is a default rule, or has been set explicitly. + Default bool `json:"default"` + // Whether or not this push rule is enabled. + Enabled bool `json:"enabled"` + // The conditions to match in order to trigger this rule. + // Only applicable to generic underride/override rules. + Conditions []*PushCondition `json:"conditions,omitempty"` + // Pattern for content-specific push rules + Pattern string `json:"pattern,omitempty"` +} + +func (rule *PushRule) Match(room Room, evt *event.Event) bool { + if !rule.Enabled { + return false + } + switch rule.Type { + case OverrideRule, UnderrideRule: + return rule.matchConditions(room, evt) + case ContentRule: + return rule.matchPattern(room, evt) + case RoomRule: + return id.RoomID(rule.RuleID) == evt.RoomID + case SenderRule: + return id.UserID(rule.RuleID) == evt.Sender + default: + return false + } +} + +func (rule *PushRule) matchConditions(room Room, evt *event.Event) bool { + for _, cond := range rule.Conditions { + if !cond.Match(room, evt) { + return false + } + } + return true +} + +func (rule *PushRule) matchPattern(room Room, evt *event.Event) bool { + pattern, err := glob.Compile(rule.Pattern) + if err != nil { + return false + } + msg, ok := evt.Content.Raw["body"].(string) + if !ok { + return false + } + return pattern.MatchString(msg) +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/ruleset.go b/vendor/maunium.net/go/mautrix/pushrules/ruleset.go new file mode 100644 index 0000000000..48ae1e3513 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/ruleset.go @@ -0,0 +1,88 @@ +// 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 pushrules + +import ( + "encoding/json" + + "maunium.net/go/mautrix/event" +) + +type PushRuleset struct { + Override PushRuleArray + Content PushRuleArray + Room PushRuleMap + Sender PushRuleMap + Underride PushRuleArray +} + +type rawPushRuleset struct { + Override PushRuleArray `json:"override"` + Content PushRuleArray `json:"content"` + Room PushRuleArray `json:"room"` + Sender PushRuleArray `json:"sender"` + Underride PushRuleArray `json:"underride"` +} + +// UnmarshalJSON parses JSON into this PushRuleset. +// +// For override, sender and underride push rule arrays, the type is added +// to each PushRule and the array is used as-is. +// +// For room and sender push rule arrays, the type is added to each PushRule +// and the array is converted to a map with the rule ID as the key and the +// PushRule as the value. +func (rs *PushRuleset) UnmarshalJSON(raw []byte) (err error) { + data := rawPushRuleset{} + err = json.Unmarshal(raw, &data) + if err != nil { + return + } + + rs.Override = data.Override.SetType(OverrideRule) + rs.Content = data.Content.SetType(ContentRule) + rs.Room = data.Room.SetTypeAndMap(RoomRule) + rs.Sender = data.Sender.SetTypeAndMap(SenderRule) + rs.Underride = data.Underride.SetType(UnderrideRule) + return +} + +// MarshalJSON is the reverse of UnmarshalJSON() +func (rs *PushRuleset) MarshalJSON() ([]byte, error) { + data := rawPushRuleset{ + Override: rs.Override, + Content: rs.Content, + Room: rs.Room.Unmap(), + Sender: rs.Sender.Unmap(), + Underride: rs.Underride, + } + return json.Marshal(&data) +} + +// DefaultPushActions is the value returned if none of the rule +// collections in a Ruleset match the event given to GetActions() +var DefaultPushActions = PushActionArray{&PushAction{Action: ActionDontNotify}} + +// GetActions matches the given event against all of the push rule +// collections in this push ruleset in the order of priority as +// specified in spec section 11.12.1.4. +func (rs *PushRuleset) GetActions(room Room, evt *event.Event) (match PushActionArray) { + // Add push rule collections to array in priority order + arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride} + // Loop until one of the push rule collections matches the room/event combo. + for _, pra := range arrays { + if pra == nil { + continue + } + if match = pra.GetActions(room, evt); match != nil { + // Match found, return it. + return + } + } + // No match found, return default actions. + return DefaultPushActions +} diff --git a/vendor/maunium.net/go/mautrix/requests.go b/vendor/maunium.net/go/mautrix/requests.go new file mode 100644 index 0000000000..72cc8e6b2e --- /dev/null +++ b/vendor/maunium.net/go/mautrix/requests.go @@ -0,0 +1,302 @@ +package mautrix + +import ( + "encoding/json" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "maunium.net/go/mautrix/pushrules" +) + +type AuthType string + +const ( + AuthTypePassword = "m.login.password" + AuthTypeReCAPTCHA = "m.login.recaptcha" + AuthTypeOAuth2 = "m.login.oauth2" + AuthTypeSSO = "m.login.sso" + AuthTypeEmail = "m.login.email.identity" + AuthTypeMSISDN = "m.login.msisdn" + AuthTypeToken = "m.login.token" + AuthTypeDummy = "m.login.dummy" + + AuthTypeAppservice = "m.login.application_service" + AuthTypeHalfyAppservice = "uk.half-shot.msc2778.login.application_service" +) + +type IdentifierType string + +const ( + IdentifierTypeUser = "m.id.user" + IdentifierTypeThirdParty = "m.id.thirdparty" + IdentifierTypePhone = "m.id.phone" +) + +// ReqRegister is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-register +type ReqRegister struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` + InhibitLogin bool `json:"inhibit_login,omitempty"` + Auth interface{} `json:"auth,omitempty"` + + // Type for registration, only used for appservice user registrations + // https://matrix.org/docs/spec/application_service/r0.1.2#server-admin-style-permissions + Type AuthType `json:"type,omitempty"` +} + +type BaseAuthData struct { + Type AuthType `json:"type"` + Session string `json:"session,omitempty"` +} + +type UserIdentifier struct { + Type IdentifierType `json:"type"` + + User string `json:"user,omitempty"` + + Medium string `json:"medium,omitempty"` + Address string `json:"address,omitempty"` + + Country string `json:"country,omitempty"` + Phone string `json:"phone,omitempty"` +} + +// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +type ReqLogin struct { + Type AuthType `json:"type"` + Identifier UserIdentifier `json:"identifier"` + Password string `json:"password,omitempty"` + Token string `json:"token,omitempty"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` + + // Whether or not the returned credentials should be stored in the Client + StoreCredentials bool `json:"-"` +} + +type ReqUIAuthFallback struct { + Session string `json:"session"` + User string `json:"user"` +} + +type ReqUIAuthLogin struct { + BaseAuthData + User string `json:"user"` + Password string `json:"password"` +} + +// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type ReqCreateRoom struct { + Visibility string `json:"visibility,omitempty"` + RoomAliasName string `json:"room_alias_name,omitempty"` + Name string `json:"name,omitempty"` + Topic string `json:"topic,omitempty"` + Invite []id.UserID `json:"invite,omitempty"` + Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"` + CreationContent map[string]interface{} `json:"creation_content,omitempty"` + InitialState []*event.Event `json:"initial_state,omitempty"` + Preset string `json:"preset,omitempty"` + IsDirect bool `json:"is_direct,omitempty"` +} + +// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid +type ReqRedact struct { + Reason string `json:"reason,omitempty"` + TxnID string `json:"-"` +} + +type ReqMembers struct { + At string `json:"at"` + Membership event.Membership `json:"membership,omitempty"` + NotMembership event.Membership `json:"not_membership,omitempty"` +} + +// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57 +// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type ReqInvite3PID struct { + IDServer string `json:"id_server"` + Medium string `json:"medium"` + Address string `json:"address"` +} + +// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +type ReqInviteUser struct { + UserID id.UserID `json:"user_id"` +} + +// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +type ReqKickUser struct { + Reason string `json:"reason,omitempty"` + UserID id.UserID `json:"user_id"` +} + +// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +type ReqBanUser struct { + Reason string `json:"reason,omitempty"` + UserID id.UserID `json:"user_id"` +} + +// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +type ReqUnbanUser struct { + UserID id.UserID `json:"user_id"` +} + +// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +type ReqTyping struct { + Typing bool `json:"typing"` + Timeout int64 `json:"timeout,omitempty"` +} + +type ReqPresence struct { + Presence event.Presence `json:"presence"` +} + +type ReqAliasCreate struct { + RoomID id.RoomID `json:"room_id"` +} + +type OneTimeKey struct { + Key id.Curve25519 `json:"key"` + IsSigned bool `json:"-"` + Signatures Signatures `json:"signatures,omitempty"` + Unsigned map[string]interface{} `json:"unsigned,omitempty"` +} + +type serializableOTK OneTimeKey + +func (otk *OneTimeKey) UnmarshalJSON(data []byte) (err error) { + if len(data) > 0 && data[0] == '"' && data[len(data)-1] == '"' { + err = json.Unmarshal(data, &otk.Key) + otk.Signatures = nil + otk.Unsigned = nil + otk.IsSigned = false + } else { + err = json.Unmarshal(data, (*serializableOTK)(otk)) + otk.IsSigned = true + } + return err +} + +func (otk *OneTimeKey) MarshalJSON() ([]byte, error) { + if !otk.IsSigned { + return json.Marshal(otk.Key) + } else { + return json.Marshal((*serializableOTK)(otk)) + } +} + +type ReqUploadKeys struct { + DeviceKeys *DeviceKeys `json:"device_keys,omitempty"` + OneTimeKeys map[id.KeyID]OneTimeKey `json:"one_time_keys"` +} + +type ReqKeysSignatures struct { + UserID id.UserID `json:"user_id"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + Algorithms []id.Algorithm `json:"algorithms,omitempty"` + Usage []id.CrossSigningUsage `json:"usage,omitempty"` + Keys map[id.KeyID]string `json:"keys"` + Signatures Signatures `json:"signatures"` +} + +type ReqUploadSignatures map[id.UserID]map[string]ReqKeysSignatures + +type DeviceKeys struct { + UserID id.UserID `json:"user_id"` + DeviceID id.DeviceID `json:"device_id"` + Algorithms []id.Algorithm `json:"algorithms"` + Keys KeyMap `json:"keys"` + Signatures Signatures `json:"signatures"` + Unsigned map[string]interface{} `json:"unsigned,omitempty"` +} + +type CrossSigningKeys struct { + UserID id.UserID `json:"user_id"` + Usage []id.CrossSigningUsage `json:"usage"` + Keys map[id.KeyID]id.Ed25519 `json:"keys"` + Signatures map[id.UserID]map[id.KeyID]string `json:"signatures,omitempty"` +} + +func (csk *CrossSigningKeys) FirstKey() id.Ed25519 { + for _, key := range csk.Keys { + return key + } + return "" +} + +type UploadCrossSigningKeysReq struct { + Master CrossSigningKeys `json:"master_key"` + SelfSigning CrossSigningKeys `json:"self_signing_key"` + UserSigning CrossSigningKeys `json:"user_signing_key"` + Auth interface{} `json:"auth,omitempty"` +} + +type KeyMap map[id.DeviceKeyID]string + +func (km KeyMap) GetEd25519(deviceID id.DeviceID) id.Ed25519 { + val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID)] + if !ok { + return "" + } + return id.Ed25519(val) +} + +func (km KeyMap) GetCurve25519(deviceID id.DeviceID) id.Curve25519 { + val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmCurve25519, deviceID)] + if !ok { + return "" + } + return id.Curve25519(val) +} + +type Signatures map[id.UserID]map[id.KeyID]string + +type ReqQueryKeys struct { + DeviceKeys DeviceKeysRequest `json:"device_keys"` + + Timeout int64 `json:"timeout,omitempty"` + Token string `json:"token,omitempty"` +} + +type DeviceKeysRequest map[id.UserID]DeviceIDList + +type DeviceIDList []id.DeviceID + +type ReqClaimKeys struct { + OneTimeKeys OneTimeKeysRequest `json:"one_time_keys"` + + Timeout int64 `json:"timeout,omitempty"` +} + +type OneTimeKeysRequest map[id.UserID]map[id.DeviceID]id.KeyAlgorithm + +type ReqSendToDevice struct { + Messages map[id.UserID]map[id.DeviceID]*event.Content `json:"messages"` +} + +// ReqDeviceInfo is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-devices-deviceid +type ReqDeviceInfo struct { + DisplayName string `json:"display_name,omitempty"` +} + +// ReqDeleteDevice is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-devices-deviceid +type ReqDeleteDevice struct { + Auth interface{} `json:"auth,omitempty"` +} + +// ReqDeleteDevices is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-delete-devices +type ReqDeleteDevices struct { + Devices []id.DeviceID `json:"devices"` + Auth interface{} `json:"auth,omitempty"` +} + +type ReqPutPushRule struct { + Before string `json:"-"` + After string `json:"-"` + + Actions []pushrules.PushActionType `json:"actions"` + Conditions []pushrules.PushCondition `json:"conditions"` + Pattern string `json:"pattern"` +} diff --git a/vendor/maunium.net/go/mautrix/responses.go b/vendor/maunium.net/go/mautrix/responses.go new file mode 100644 index 0000000000..b415d46bba --- /dev/null +++ b/vendor/maunium.net/go/mautrix/responses.go @@ -0,0 +1,291 @@ +package mautrix + +import ( + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// RespWhoami is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-account-whoami +type RespWhoami struct { + UserID id.UserID `json:"user_id"` + // N.B. This field is not in the spec yet, it's expected to land in r0.6.2 or r0.7.0 + DeviceID id.DeviceID `json:"device_id"` +} + +// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter +type RespCreateFilter struct { + FilterID string `json:"filter_id"` +} + +// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-versions +type RespVersions struct { + Versions []string `json:"versions"` + UnstableFeatures map[string]bool `json:"unstable_features"` +} + +// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join +type RespJoinRoom struct { + RoomID id.RoomID `json:"room_id"` +} + +// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave +type RespLeaveRoom struct{} + +// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget +type RespForgetRoom struct{} + +// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +type RespInviteUser struct{} + +// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +type RespKickUser struct{} + +// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +type RespBanUser struct{} + +// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +type RespUnbanUser struct{} + +// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +type RespTyping struct{} + +// RespPresence is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status +type RespPresence struct { + Presence event.Presence `json:"presence"` + LastActiveAgo int `json:"last_active_ago"` + StatusMsg string `json:"status_msg"` + CurrentlyActive bool `json:"currently_active"` +} + +// RespJoinedRooms is the JSON response for https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +type RespJoinedRooms struct { + JoinedRooms []id.RoomID `json:"joined_rooms"` +} + +// RespJoinedMembers is the JSON response for https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +type RespJoinedMembers struct { + Joined map[id.UserID]struct { + DisplayName *string `json:"display_name"` + AvatarURL *string `json:"avatar_url"` + } `json:"joined"` +} + +// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages +type RespMessages struct { + Start string `json:"start"` + Chunk []*event.Event `json:"chunk"` + State []*event.Event `json:"state"` + End string `json:"end"` +} + +// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid +type RespSendEvent struct { + EventID id.EventID `json:"event_id"` +} + +// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload +type RespMediaUpload struct { + ContentURI id.ContentURI `json:"content_uri"` +} + +// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api +type RespUserInteractive struct { + Flows []struct { + Stages []AuthType `json:"stages"` + } `json:"flows"` + Params map[AuthType]interface{} `json:"params"` + Session string `json:"session"` + Completed []string `json:"completed"` + + ErrCode string `json:"errcode"` + Error string `json:"error"` +} + +// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName. +func (r RespUserInteractive) HasSingleStageFlow(stageName AuthType) bool { + for _, f := range r.Flows { + if len(f.Stages) == 1 && f.Stages[0] == stageName { + return true + } + } + return false +} + +// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname +type RespUserDisplayName struct { + DisplayName string `json:"displayname"` +} + +// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +type RespRegister struct { + AccessToken string `json:"access_token"` + DeviceID id.DeviceID `json:"device_id"` + HomeServer string `json:"home_server"` + RefreshToken string `json:"refresh_token"` + UserID id.UserID `json:"user_id"` +} + +type RespLoginFlows struct { + Flows []struct { + Type AuthType `json:"type"` + } `json:"flows"` +} + +func (rlf *RespLoginFlows) HasFlow(flowType AuthType) bool { + for _, flow := range rlf.Flows { + if flow.Type == flowType { + return true + } + } + return false +} + +// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +type RespLogin struct { + AccessToken string `json:"access_token"` + DeviceID id.DeviceID `json:"device_id"` + UserID id.UserID `json:"user_id"` + // TODO add .well-known field here +} + +// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout +type RespLogout struct{} + +// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-createroom +type RespCreateRoom struct { + RoomID id.RoomID `json:"room_id"` +} + +type RespMembers struct { + Chunk []*event.Event `json:"chunk"` +} + +type LazyLoadSummary struct { + Heroes []id.UserID `json:"m.heroes,omitempty"` + JoinedMemberCount *int `json:"m.joined_member_count,omitempty"` + InvitedMemberCount *int `json:"m.invited_member_count,omitempty"` +} + +// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#get-matrix-client-r0-sync +type RespSync struct { + NextBatch string `json:"next_batch"` + + AccountData struct { + Events []*event.Event `json:"events"` + } `json:"account_data"` + Presence struct { + Events []*event.Event `json:"events"` + } `json:"presence"` + ToDevice struct { + Events []*event.Event `json:"events"` + } `json:"to_device"` + + DeviceLists struct { + Changed []id.UserID `json:"changed"` + Left []id.UserID `json:"left"` + } `json:"device_lists"` + DeviceOneTimeKeysCount OneTimeKeysCount `json:"device_one_time_keys_count"` + + Rooms struct { + Leave map[id.RoomID]SyncLeftRoom `json:"leave"` + Join map[id.RoomID]SyncJoinedRoom `json:"join"` + Invite map[id.RoomID]SyncInvitedRoom `json:"invite"` + } `json:"rooms"` +} + +type OneTimeKeysCount struct { + Curve25519 int `json:"curve25519"` + SignedCurve25519 int `json:"signed_curve25519"` +} + +type SyncLeftRoom struct { + Summary LazyLoadSummary `json:"summary"` + State struct { + Events []*event.Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []*event.Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` +} + +type SyncJoinedRoom struct { + Summary LazyLoadSummary `json:"summary"` + State struct { + Events []*event.Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []*event.Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` + Ephemeral struct { + Events []*event.Event `json:"events"` + } `json:"ephemeral"` + AccountData struct { + Events []*event.Event `json:"events"` + } `json:"account_data"` +} + +type SyncInvitedRoom struct { + Summary LazyLoadSummary `json:"summary"` + State struct { + Events []*event.Event `json:"events"` + } `json:"invite_state"` +} + +type RespTurnServer struct { + Username string `json:"username"` + Password string `json:"password"` + TTL int `json:"ttl"` + URIs []string `json:"uris"` +} + +type RespAliasCreate struct{} +type RespAliasDelete struct{} +type RespAliasResolve struct { + RoomID id.RoomID `json:"room_id"` + Servers []string `json:"servers"` +} + +type RespUploadKeys struct { + OneTimeKeyCounts OneTimeKeysCount `json:"one_time_key_counts"` +} + +type RespQueryKeys struct { + Failures map[string]interface{} `json:"failures"` + DeviceKeys map[id.UserID]map[id.DeviceID]DeviceKeys `json:"device_keys"` + MasterKeys map[id.UserID]CrossSigningKeys `json:"master_keys"` + SelfSigningKeys map[id.UserID]CrossSigningKeys `json:"self_signing_keys"` + UserSigningKeys map[id.UserID]CrossSigningKeys `json:"user_signing_keys"` +} + +type RespClaimKeys struct { + Failures map[string]interface{} `json:"failures"` + OneTimeKeys map[id.UserID]map[id.DeviceID]map[id.KeyID]OneTimeKey `json:"one_time_keys"` +} + +type RespUploadSignatures struct { + Failures map[string]interface{} `json:"failures"` +} + +type RespKeyChanges struct { + Changed []id.UserID `json:"changed"` + Left []id.UserID `json:"left"` +} + +type RespSendToDevice struct{} + +// RespDevicesInfo is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices +type RespDevicesInfo struct { + Devices []RespDeviceInfo `json:"devices"` +} + +// RespDeviceInfo is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices-deviceid +type RespDeviceInfo struct { + DeviceID id.DeviceID `json:"device_id"` + DisplayName string `json:"display_name"` + LastSeenIP string `json:"last_seen_ip"` + LastSeenTS int64 `json:"last_seen_ts"` +} diff --git a/vendor/maunium.net/go/mautrix/room.go b/vendor/maunium.net/go/mautrix/room.go new file mode 100644 index 0000000000..588097f61f --- /dev/null +++ b/vendor/maunium.net/go/mautrix/room.go @@ -0,0 +1,52 @@ +package mautrix + +import ( + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// Room represents a single Matrix room. +type Room struct { + ID id.RoomID + State map[event.Type]map[string]*event.Event +} + +// UpdateState updates the room's current state with the given Event. This will clobber events based +// on the type/state_key combination. +func (room Room) UpdateState(evt *event.Event) { + _, exists := room.State[evt.Type] + if !exists { + room.State[evt.Type] = make(map[string]*event.Event) + } + room.State[evt.Type][*evt.StateKey] = evt +} + +// GetStateEvent returns the state event for the given type/state_key combo, or nil. +func (room Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event { + stateEventMap, _ := room.State[eventType] + evt, _ := stateEventMap[stateKey] + return evt +} + +// GetMembershipState returns the membership state of the given user ID in this room. If there is +// no entry for this member, 'leave' is returned for consistency with left users. +func (room Room) GetMembershipState(userID id.UserID) event.Membership { + state := event.MembershipLeave + evt := room.GetStateEvent(event.StateMember, string(userID)) + if evt != nil { + membership, ok := evt.Content.Raw["membership"].(string) + if ok { + state = event.Membership(membership) + } + } + return state +} + +// NewRoom creates a new Room with the given ID +func NewRoom(roomID id.RoomID) *Room { + // Init the State map and return a pointer to the Room + return &Room{ + ID: roomID, + State: make(map[event.Type]map[string]*event.Event), + } +} diff --git a/vendor/maunium.net/go/mautrix/store.go b/vendor/maunium.net/go/mautrix/store.go new file mode 100644 index 0000000000..6fc9622c55 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/store.go @@ -0,0 +1,162 @@ +package mautrix + +import ( + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// Storer is an interface which must be satisfied to store client data. +// +// You can either write a struct which persists this data to disk, or you can use the +// provided "InMemoryStore" which just keeps data around in-memory which is lost on +// restarts. +type Storer interface { + SaveFilterID(userID id.UserID, filterID string) + LoadFilterID(userID id.UserID) string + SaveNextBatch(userID id.UserID, nextBatchToken string) + LoadNextBatch(userID id.UserID) string + SaveRoom(room *Room) + LoadRoom(roomID id.RoomID) *Room +} + +// InMemoryStore implements the Storer interface. +// +// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs +// or next batch tokens on any goroutine other than the syncing goroutine: the one +// which called Client.Sync(). +type InMemoryStore struct { + Filters map[id.UserID]string + NextBatch map[id.UserID]string + Rooms map[id.RoomID]*Room +} + +// SaveFilterID to memory. +func (s *InMemoryStore) SaveFilterID(userID id.UserID, filterID string) { + s.Filters[userID] = filterID +} + +// LoadFilterID from memory. +func (s *InMemoryStore) LoadFilterID(userID id.UserID) string { + return s.Filters[userID] +} + +// SaveNextBatch to memory. +func (s *InMemoryStore) SaveNextBatch(userID id.UserID, nextBatchToken string) { + s.NextBatch[userID] = nextBatchToken +} + +// LoadNextBatch from memory. +func (s *InMemoryStore) LoadNextBatch(userID id.UserID) string { + return s.NextBatch[userID] +} + +// SaveRoom to memory. +func (s *InMemoryStore) SaveRoom(room *Room) { + s.Rooms[room.ID] = room +} + +// LoadRoom from memory. +func (s *InMemoryStore) LoadRoom(roomID id.RoomID) *Room { + return s.Rooms[roomID] +} + +// UpdateState stores a state event. This can be passed to DefaultSyncer.OnEvent to keep all room state cached. +func (s *InMemoryStore) UpdateState(_ EventSource, evt *event.Event) { + if !evt.Type.IsState() { + return + } + room := s.LoadRoom(evt.RoomID) + if room == nil { + room = NewRoom(evt.RoomID) + s.SaveRoom(room) + } + room.UpdateState(evt) +} + +// NewInMemoryStore constructs a new InMemoryStore. +func NewInMemoryStore() *InMemoryStore { + return &InMemoryStore{ + Filters: make(map[id.UserID]string), + NextBatch: make(map[id.UserID]string), + Rooms: make(map[id.RoomID]*Room), + } +} + +// AccountDataStore uses account data to store the next batch token, and +// reuses the InMemoryStore for all other operations. +type AccountDataStore struct { + *InMemoryStore + eventType string + client *Client +} + +type accountData struct { + NextBatch string `json:"next_batch"` +} + +// SaveNextBatch to account data. +func (s *AccountDataStore) SaveNextBatch(userID id.UserID, nextBatchToken string) { + if userID.String() != s.client.UserID.String() { + panic("AccountDataStore must only be used with bots") + } + + data := accountData{ + NextBatch: nextBatchToken, + } + + err := s.client.SetAccountData(s.eventType, data) + if err != nil { + if s.client.Logger != nil { + s.client.Logger.Debugfln("failed to save next batch token to account data: %s", err.Error()) + } + } +} + +// LoadNextBatch from account data. +func (s *AccountDataStore) LoadNextBatch(userID id.UserID) string { + if userID.String() != s.client.UserID.String() { + panic("AccountDataStore must only be used with bots") + } + + data := &accountData{} + + err := s.client.GetAccountData(s.eventType, data) + if err != nil { + if s.client.Logger != nil { + s.client.Logger.Debugfln("failed to load next batch token to account data: %s", err.Error()) + } + return "" + } + + return data.NextBatch +} + +// NewAccountDataStore returns a new AccountDataStore, which stores +// the next_batch token as a custom event in account data in the +// homeserver. +// +// AccountDataStore is only appropriate for bots, not appservices. +// +// eventType should be a reversed DNS name like tld.domain.sub.internal and +// must be unique for a client. The data stored in it is considered internal +// and must not be modified through outside means. You should also add a filter +// for account data changes of this event type, to avoid ending up in a sync +// loop: +// +// mautrix.Filter{ +// AccountData: mautrix.FilterPart{ +// Limit: 20, +// NotTypes: []event.Type{ +// event.NewEventType(eventType), +// }, +// }, +// } +// mautrix.Client.CreateFilter(...) +// +func NewAccountDataStore(eventType string, client *Client) *AccountDataStore { + return &AccountDataStore{ + InMemoryStore: NewInMemoryStore(), + eventType: eventType, + client: client, + } +} diff --git a/vendor/maunium.net/go/mautrix/sync.go b/vendor/maunium.net/go/mautrix/sync.go new file mode 100644 index 0000000000..a7453becb3 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/sync.go @@ -0,0 +1,283 @@ +// 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 mautrix + +import ( + "fmt" + "runtime/debug" + "time" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// EventSource represents the part of the sync response that an event came from. +type EventSource int + +const ( + EventSourcePresence EventSource = 1 << iota + EventSourceJoin + EventSourceInvite + EventSourceLeave + EventSourceAccountData + EventSourceTimeline + EventSourceState + EventSourceEphemeral + EventSourceToDevice +) + +func (es EventSource) String() string { + switch { + case es == EventSourcePresence: + return "presence" + case es == EventSourceAccountData: + return "user account data" + case es == EventSourceToDevice: + return "to-device" + case es&EventSourceJoin != 0: + es -= EventSourceJoin + switch es { + case EventSourceState: + return "joined state" + case EventSourceTimeline: + return "joined timeline" + case EventSourceEphemeral: + return "room ephemeral (joined)" + case EventSourceAccountData: + return "room account data (joined)" + } + case es&EventSourceInvite != 0: + es -= EventSourceInvite + switch es { + case EventSourceState: + return "invited state" + } + case es&EventSourceLeave != 0: + es -= EventSourceLeave + switch es { + case EventSourceState: + return "left state" + case EventSourceTimeline: + return "left timeline" + } + } + return fmt.Sprintf("unknown (%d)", es) +} + +// EventHandler handles a single event from a sync response. +type EventHandler func(source EventSource, evt *event.Event) + +// SyncHandler handles a whole sync response. If the return value is false, handling will be stopped completely. +type SyncHandler func(resp *RespSync, since string) bool + +// Syncer is an interface that must be satisfied in order to do /sync requests on a client. +type Syncer interface { + // Process the /sync response. The since parameter is the since= value that was used to produce the response. + // This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped + // permanently. + ProcessResponse(resp *RespSync, since string) error + // OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently. + OnFailedSync(res *RespSync, err error) (time.Duration, error) + // GetFilterJSON for the given user ID. NOT the filter ID. + GetFilterJSON(userID id.UserID) *Filter +} + +type ExtensibleSyncer interface { + OnSync(callback SyncHandler) + OnEvent(callback EventHandler) + OnEventType(eventType event.Type, callback EventHandler) +} + +// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively +// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer +// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information. +type DefaultSyncer struct { + // syncListeners want the whole sync response, e.g. the crypto machine + syncListeners []SyncHandler + // globalListeners want all events + globalListeners []EventHandler + // listeners want a specific event type + listeners map[event.Type][]EventHandler + // ParseEventContent determines whether or not event content should be parsed before passing to handlers. + ParseEventContent bool + // ParseErrorHandler is called when event.Content.ParseRaw returns an error. + // If it returns false, the event will not be forwarded to listeners. + ParseErrorHandler func(evt *event.Event, err error) bool +} + +var _ Syncer = (*DefaultSyncer)(nil) +var _ ExtensibleSyncer = (*DefaultSyncer)(nil) + +// NewDefaultSyncer returns an instantiated DefaultSyncer +func NewDefaultSyncer() *DefaultSyncer { + return &DefaultSyncer{ + listeners: make(map[event.Type][]EventHandler), + syncListeners: []SyncHandler{}, + globalListeners: []EventHandler{}, + ParseEventContent: true, + ParseErrorHandler: func(evt *event.Event, err error) bool { + return false + }, + } +} + +// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of +// unrepeating events. Returns a fatal error if a listener panics. +func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("ProcessResponse panicked! since=%s panic=%s\n%s", since, r, debug.Stack()) + } + }() + + for _, listener := range s.syncListeners { + if !listener(res, since) { + return + } + } + + s.processSyncEvents("", res.Presence.Events, EventSourcePresence) + s.processSyncEvents("", res.AccountData.Events, EventSourceAccountData) + + for roomID, roomData := range res.Rooms.Join { + s.processSyncEvents(roomID, roomData.State.Events, EventSourceJoin|EventSourceState) + s.processSyncEvents(roomID, roomData.Timeline.Events, EventSourceJoin|EventSourceTimeline) + s.processSyncEvents(roomID, roomData.Ephemeral.Events, EventSourceJoin|EventSourceEphemeral) + s.processSyncEvents(roomID, roomData.AccountData.Events, EventSourceJoin|EventSourceAccountData) + } + for roomID, roomData := range res.Rooms.Invite { + s.processSyncEvents(roomID, roomData.State.Events, EventSourceInvite|EventSourceState) + } + for roomID, roomData := range res.Rooms.Leave { + s.processSyncEvents(roomID, roomData.State.Events, EventSourceLeave|EventSourceState) + s.processSyncEvents(roomID, roomData.Timeline.Events, EventSourceLeave|EventSourceTimeline) + } + return +} + +func (s *DefaultSyncer) processSyncEvents(roomID id.RoomID, events []*event.Event, source EventSource) { + for _, evt := range events { + s.processSyncEvent(roomID, evt, source) + } +} + +func (s *DefaultSyncer) processSyncEvent(roomID id.RoomID, evt *event.Event, source EventSource) { + evt.RoomID = roomID + + // Ensure the type class is correct. It's safe to mutate the class since the event type is not a pointer. + // Listeners are keyed by type structs, which means only the correct class will pass. + switch { + case evt.StateKey != nil: + evt.Type.Class = event.StateEventType + case source == EventSourcePresence, source&EventSourceEphemeral != 0: + evt.Type.Class = event.EphemeralEventType + case source&EventSourceAccountData != 0: + evt.Type.Class = event.AccountDataEventType + case source == EventSourceToDevice: + evt.Type.Class = event.ToDeviceEventType + default: + evt.Type.Class = event.MessageEventType + } + + if s.ParseEventContent { + err := evt.Content.ParseRaw(evt.Type) + if err != nil && !s.ParseErrorHandler(evt, err) { + return + } + } + + s.notifyListeners(source, evt) +} + +func (s *DefaultSyncer) notifyListeners(source EventSource, evt *event.Event) { + for _, fn := range s.globalListeners { + fn(source, evt) + } + listeners, exists := s.listeners[evt.Type] + if exists { + for _, fn := range listeners { + fn(source, evt) + } + } +} + +// OnEventType allows callers to be notified when there are new events for the given event type. +// There are no duplicate checks. +func (s *DefaultSyncer) OnEventType(eventType event.Type, callback EventHandler) { + _, exists := s.listeners[eventType] + if !exists { + s.listeners[eventType] = []EventHandler{} + } + s.listeners[eventType] = append(s.listeners[eventType], callback) +} + +func (s *DefaultSyncer) OnSync(callback SyncHandler) { + s.syncListeners = append(s.syncListeners, callback) +} + +func (s *DefaultSyncer) OnEvent(callback EventHandler) { + s.globalListeners = append(s.globalListeners, callback) +} + +// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error. +func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) { + return 10 * time.Second, nil +} + +// GetFilterJSON returns a filter with a timeline limit of 50. +func (s *DefaultSyncer) GetFilterJSON(userID id.UserID) *Filter { + return &Filter{ + Room: RoomFilter{ + Timeline: FilterPart{ + Limit: 50, + }, + }, + } +} + +// OldEventIgnorer is an utility struct for bots to ignore events from before the bot joined the room. +// Create a struct and call Register with your DefaultSyncer to register the sync handler. +type OldEventIgnorer struct { + UserID id.UserID +} + +func (oei *OldEventIgnorer) Register(syncer ExtensibleSyncer) { + syncer.OnSync(oei.DontProcessOldEvents) +} + +// DontProcessOldEvents returns true if a sync response should be processed. May modify the response to remove +// stuff that shouldn't be processed. +func (oei *OldEventIgnorer) DontProcessOldEvents(resp *RespSync, since string) bool { + if since == "" { + return false + } + // This is a horrible hack because /sync will return the most recent messages for a room + // as soon as you /join it. We do NOT want to process those events in that particular room + // because they may have already been processed (if you toggle the bot in/out of the room). + // + // Work around this by inspecting each room's timeline and seeing if an m.room.member event for us + // exists and is "join" and then discard processing that room entirely if so. + // TODO: We probably want to process messages from after the last join event in the timeline. + for roomID, roomData := range resp.Rooms.Join { + for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- { + evt := roomData.Timeline.Events[i] + if evt.Type == event.StateMember && evt.GetStateKey() == string(oei.UserID) { + membership, _ := evt.Content.Raw["membership"].(string) + if membership == "join" { + _, ok := resp.Rooms.Join[roomID] + if !ok { + continue + } + delete(resp.Rooms.Join, roomID) // don't re-process messages + delete(resp.Rooms.Invite, roomID) // don't re-process invites + break + } + } + } + } + return true +} diff --git a/vendor/maunium.net/go/mautrix/version.go b/vendor/maunium.net/go/mautrix/version.go new file mode 100644 index 0000000000..ac5426a4d7 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/version.go @@ -0,0 +1,5 @@ +package mautrix + +const Version = "v0.9.14" + +var DefaultUserAgent = "mautrix-go/" + Version diff --git a/vendor/modules.txt b/vendor/modules.txt index 4a06f74fd0..65e54decc0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -38,6 +38,8 @@ github.com/SevereCloud/vksdk/v2/longpoll-bot github.com/SevereCloud/vksdk/v2/object # github.com/blang/semver v3.5.1+incompatible github.com/blang/semver +# github.com/btcsuite/btcutil v1.0.2 +github.com/btcsuite/btcutil/base58 # github.com/d5/tengo/v2 v2.7.0 ## explicit github.com/d5/tengo/v2 @@ -137,7 +139,6 @@ github.com/lrstanley/girc github.com/magiconair/properties # github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 ## explicit -github.com/matrix-org/gomatrix # github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d ## explicit github.com/matterbridge/Rocket.Chat.Go.SDK/models @@ -325,6 +326,7 @@ golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/hkdf golang.org/x/crypto/internal/subtle golang.org/x/crypto/nacl/secretbox +golang.org/x/crypto/pbkdf2 golang.org/x/crypto/poly1305 golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/ssh @@ -445,3 +447,12 @@ layeh.com/gumble/gumble layeh.com/gumble/gumble/MumbleProto layeh.com/gumble/gumble/varint layeh.com/gumble/gumbleutil +# maunium.net/go/mautrix v0.9.14 +## explicit +maunium.net/go/mautrix +maunium.net/go/mautrix/crypto/attachment +maunium.net/go/mautrix/crypto/utils +maunium.net/go/mautrix/event +maunium.net/go/mautrix/id +maunium.net/go/mautrix/pushrules +maunium.net/go/mautrix/pushrules/glob From 72b063a79aafde5746882272c5806f548de394df Mon Sep 17 00:00:00 2001 From: Simon THOBY Date: Fri, 9 Jul 2021 21:44:53 +0200 Subject: [PATCH 3/3] matrix: add support for application services --- bridge/config/config.go | 32 +- bridge/matrix/appservice.go | 54 + bridge/matrix/helpers.go | 67 +- bridge/matrix/matrix.go | 263 +- gateway/gateway.go | 6 +- go.mod | 1 - go.sum | 19 +- matterbridge.toml.sample | 12 + vendor/github.com/gorilla/mux/AUTHORS | 8 + vendor/github.com/gorilla/mux/LICENSE | 27 + vendor/github.com/gorilla/mux/README.md | 805 ++ vendor/github.com/gorilla/mux/doc.go | 306 + vendor/github.com/gorilla/mux/go.mod | 3 + vendor/github.com/gorilla/mux/middleware.go | 74 + vendor/github.com/gorilla/mux/mux.go | 606 + vendor/github.com/gorilla/mux/regexp.go | 388 + vendor/github.com/gorilla/mux/route.go | 736 ++ vendor/github.com/gorilla/mux/test_helpers.go | 19 + vendor/golang.org/x/net/publicsuffix/list.go | 181 + vendor/golang.org/x/net/publicsuffix/table.go | 10435 ++++++++++++++++ vendor/maunium.net/go/maulogger/v2/LICENSE | 621 + vendor/maunium.net/go/maulogger/v2/README.md | 6 + .../maunium.net/go/maulogger/v2/defaults.go | 289 + vendor/maunium.net/go/maulogger/v2/go.mod | 3 + vendor/maunium.net/go/maulogger/v2/level.go | 56 + vendor/maunium.net/go/maulogger/v2/logger.go | 212 + .../maunium.net/go/maulogger/v2/sublogger.go | 208 + vendor/maunium.net/go/maulogger/v2/writer.go | 87 + .../go/mautrix/appservice/appservice.go | 374 + .../go/mautrix/appservice/eventprocessor.go | 103 + .../maunium.net/go/mautrix/appservice/http.go | 203 + .../go/mautrix/appservice/intent.go | 307 + .../go/mautrix/appservice/protocol.go | 71 + .../go/mautrix/appservice/random.go | 34 + .../go/mautrix/appservice/registration.go | 106 + .../go/mautrix/appservice/statestore.go | 236 + .../go/mautrix/appservice/websocket.go | 195 + vendor/modules.txt | 8 +- 38 files changed, 17042 insertions(+), 119 deletions(-) create mode 100644 bridge/matrix/appservice.go create mode 100644 vendor/github.com/gorilla/mux/AUTHORS create mode 100644 vendor/github.com/gorilla/mux/LICENSE create mode 100644 vendor/github.com/gorilla/mux/README.md create mode 100644 vendor/github.com/gorilla/mux/doc.go create mode 100644 vendor/github.com/gorilla/mux/go.mod create mode 100644 vendor/github.com/gorilla/mux/middleware.go create mode 100644 vendor/github.com/gorilla/mux/mux.go create mode 100644 vendor/github.com/gorilla/mux/regexp.go create mode 100644 vendor/github.com/gorilla/mux/route.go create mode 100644 vendor/github.com/gorilla/mux/test_helpers.go create mode 100644 vendor/golang.org/x/net/publicsuffix/list.go create mode 100644 vendor/golang.org/x/net/publicsuffix/table.go create mode 100644 vendor/maunium.net/go/maulogger/v2/LICENSE create mode 100644 vendor/maunium.net/go/maulogger/v2/README.md create mode 100644 vendor/maunium.net/go/maulogger/v2/defaults.go create mode 100644 vendor/maunium.net/go/maulogger/v2/go.mod create mode 100644 vendor/maunium.net/go/maulogger/v2/level.go create mode 100644 vendor/maunium.net/go/maulogger/v2/logger.go create mode 100644 vendor/maunium.net/go/maulogger/v2/sublogger.go create mode 100644 vendor/maunium.net/go/maulogger/v2/writer.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/appservice.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/eventprocessor.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/http.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/intent.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/protocol.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/random.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/registration.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/statestore.go create mode 100644 vendor/maunium.net/go/mautrix/appservice/websocket.go diff --git a/bridge/config/config.go b/bridge/config/config.go index a6e3c5464d..a71c8ac7c1 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -31,20 +31,22 @@ const ( const ParentIDNotFound = "msg-parent-not-found" +//nolint: tagliatelle type Message struct { - Text string `json:"text"` - Channel string `json:"channel"` - Username string `json:"username"` - UserID string `json:"userid"` // userid on the bridge - Avatar string `json:"avatar"` - Account string `json:"account"` - Event string `json:"event"` - Protocol string `json:"protocol"` - Gateway string `json:"gateway"` - ParentID string `json:"parent_id"` - Timestamp time.Time `json:"timestamp"` - ID string `json:"id"` - Extra map[string][]interface{} + Text string `json:"text"` + Channel string `json:"channel"` + Username string `json:"username"` + OriginalUsername string `json:"original_username"` + UserID string `json:"userid"` // userid on the bridge + Avatar string `json:"avatar"` + Account string `json:"account"` + Event string `json:"event"` + Protocol string `json:"protocol"` + Gateway string `json:"gateway"` + ParentID string `json:"parent_id"` + Timestamp time.Time `json:"timestamp"` + ID string `json:"id"` + Extra map[string][]interface{} } func (m Message) ParentNotFound() bool { @@ -143,6 +145,10 @@ type Protocol struct { ReplaceNicks [][]string // all protocols RemoteNickFormat string // all protocols RunCommands []string // IRC + UseAppService bool // matrix + AppServiceHost string // matrix + AppServicePort uint16 // matrix + AppServiceConfigPath string // matrix Server string // IRC,mattermost,XMPP,discord,matrix SessionFile string // msteams,whatsapp ShowJoinPart bool // all protocols diff --git a/bridge/matrix/appservice.go b/bridge/matrix/appservice.go new file mode 100644 index 0000000000..58ed3a7d00 --- /dev/null +++ b/bridge/matrix/appservice.go @@ -0,0 +1,54 @@ +package bmatrix + +import ( + "errors" + + "maunium.net/go/mautrix/appservice" + "maunium.net/go/mautrix/event" +) + +//nolint: wrapcheck +func (b *Bmatrix) startAppService() error { + appService := appservice.Create() + + appService.HomeserverURL = b.mc.HomeserverURL.String() + _, homeServerDomain, _ := b.mc.UserID.Parse() + appService.HomeserverDomain = homeServerDomain + //nolint: exhaustivestruct + appService.Host = appservice.HostConfig{ + Hostname: b.GetString("AppServiceHost"), + Port: uint16(b.GetInt("AppServicePort")), + } + appService.RegistrationPath = b.GetString("AppServiceConfigPath") + + initSuccess, err := appService.Init() + if err != nil { + return err + } else if !initSuccess { + return errors.New("couldn't initialise the application service") + } + + b.appService = appService + + // TODO: detect service completion and rerun automatically + go b.appService.Start() + b.Log.Debug("appservice launched") + + processor := appservice.NewEventProcessor(b.appService) + processor.On(event.EventMessage, func(ev *event.Event) { + b.handleEvent(originAppService, ev) + }) + go processor.Start() + b.Log.Debug("appservice even dispatcher launched") + + // handle service stopping/restarting + go func(b *Bmatrix, processor *appservice.EventProcessor) { + <-b.stop + + b.appService.Stop() + processor.Stop() + b.stopAck <- struct{}{} + }(b, processor) + + return nil +} diff --git a/bridge/matrix/helpers.go b/bridge/matrix/helpers.go index e0ed4bae07..3faf6b3aa0 100644 --- a/bridge/matrix/helpers.go +++ b/bridge/matrix/helpers.go @@ -7,7 +7,10 @@ import ( "time" matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" + + "github.com/42wim/matterbridge/bridge/config" ) func newMatrixUsername(username string) *matrixUsername { @@ -27,11 +30,11 @@ func newMatrixUsername(username string) *matrixUsername { } // getRoomID retrieves a matching room ID from the channel name. -func (b *Bmatrix) getRoomID(channel string) id.RoomID { +func (b *Bmatrix) getRoomID(channelName string) id.RoomID { b.RLock() defer b.RUnlock() - for ID, name := range b.RoomMap { - if name == channel { + for ID, channel := range b.RoomMap { + if channelName == channel.name { return ID } } @@ -175,3 +178,61 @@ func (b *Bmatrix) retry(f func() error) error { } } } + +type SendMessageEventWrapper struct { + inner *matrix.Client +} + +//nolint: wrapcheck +func (w SendMessageEventWrapper) SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}) (resp *matrix.RespSendEvent, err error) { + return w.inner.SendMessageEvent(roomID, eventType, contentJSON) +} + +//nolint: wrapcheck +func (b *Bmatrix) sendMessageEventWithRetries(channel id.RoomID, message event.MessageEventContent, sourceMessage config.Message) (string, error) { + var ( + resp *matrix.RespSendEvent + client interface { + SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}) (resp *matrix.RespSendEvent, err error) + } + err error + ) + + b.RLock() + appservice := b.RoomMap[channel].appService + b.RUnlock() + + client = SendMessageEventWrapper{inner: b.mc} + + if appservice { + // when using application services, the sender variable 'sender' is actually not a UserID but the username + username := sourceMessage.OriginalUsername + intent := b.appService.Intent(id.UserID(fmt.Sprintf("@%s%s:%s", "__bridge_", id.EncodeUserLocalpart(username), b.appService.HomeserverDomain))) + // if we can't change the display name it's not great but not the end of the world either, ignore it + // TODO: do not perform this action on every message, with an in-memory cache or something + _ = intent.SetDisplayName(username) + client = intent + } else { + b.applyUsernametoMessage(&message, sourceMessage) + } + + err = b.retry(func() error { + resp, err = client.SendMessageEvent(channel, event.EventMessage, message) + + return err + }) + if err != nil { + return "", err + } + + return string(resp.EventID), err +} + +func (b *Bmatrix) applyUsernametoMessage(newMsg *event.MessageEventContent, origMsg config.Message) { + username := newMatrixUsername(origMsg.Username) + + newMsg.Body = username.plain + newMsg.Body + if newMsg.FormattedBody != "" { + newMsg.FormattedBody = username.formatted + newMsg.FormattedBody + } +} diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go index 4bf9cb507d..0af5a2f2a5 100644 --- a/bridge/matrix/matrix.go +++ b/bridge/matrix/matrix.go @@ -11,6 +11,7 @@ import ( "time" matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" @@ -24,21 +25,34 @@ var ( htmlReplacementTag = regexp.MustCompile("<[^>]*>") ) +type EventOrigin int + +const ( + originClassicSyncer EventOrigin = iota + originAppService +) + type NicknameCacheEntry struct { displayName string lastUpdated time.Time } +type RoomInfo struct { + name string + appService bool +} + type Bmatrix struct { mc *matrix.Client + appService *appservice.AppService UserID id.UserID NicknameMap map[id.RoomID]map[id.UserID]NicknameCacheEntry - RoomMap map[id.RoomID]string + RoomMap map[id.RoomID]RoomInfo rateMutex sync.RWMutex sync.RWMutex *bridge.Config - stop chan int - stopAck chan int + stop chan struct{} + stopAck chan struct{} } type matrixUsername struct { @@ -48,10 +62,10 @@ type matrixUsername struct { func New(cfg *bridge.Config) bridge.Bridger { b := &Bmatrix{Config: cfg} - b.RoomMap = make(map[id.RoomID]string) + b.RoomMap = make(map[id.RoomID]RoomInfo) b.NicknameMap = make(map[id.RoomID]map[id.UserID]NicknameCacheEntry) - b.stop = make(chan int, 1) - b.stopAck = make(chan int, 1) + b.stop = make(chan struct{}, 2) + b.stopAck = make(chan struct{}, 2) return b } @@ -84,17 +98,34 @@ func (b *Bmatrix) Connect() error { b.UserID = resp.UserID b.Log.Info("Connection succeeded") } + go b.handlematrix() + + if b.GetBool("UseAppService") { + err := b.startAppService() + if err != nil { + b.Log.Errorf("couldn't start the application service: %#v", err) + + return err + } + } + return nil } func (b *Bmatrix) Disconnect() error { // tell the Sync() loop to exit - b.stop <- 0 + b.stop <- struct{}{} + if b.appService != nil { + b.stop <- struct{}{} + } b.mc.StopSync() - // wait for the syncer to terminate + // wait for both the syncer and the appservice to terminate <-b.stopAck + if b.appService != nil { + <-b.stopAck + } return nil } @@ -107,7 +138,7 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error { } b.Lock() - b.RoomMap[resp.RoomID] = channel.Name + b.RoomMap[resp.RoomID] = RoomInfo{name: channel.Name, appService: false} b.Unlock() return nil @@ -118,19 +149,20 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { b.Log.Debugf("=> Receiving %#v", msg) channel := b.getRoomID(msg.Channel) + if channel == "" { + return "", fmt.Errorf("got message for unknown channel '%s'", msg.Channel) + } b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel) - username := newMatrixUsername(msg.Username) - // Make a action /me of the message if msg.Event == config.EventUserAction { m := event.MessageEventContent{ MsgType: event.MsgEmote, - Body: username.plain + msg.Text, - FormattedBody: username.formatted + msg.Text, + Body: msg.Text, + FormattedBody: msg.Text, } - return b.sendEventWithRetries(channel, event.EventMessage, m) + return b.sendMessageEventWithRetries(channel, m, msg) } // Delete message @@ -160,10 +192,10 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { for _, rmsg := range helper.HandleExtra(&msg, b.General) { m := event.MessageEventContent{ MsgType: event.MsgText, - Body: rmsg.Username + rmsg.Text, + Body: rmsg.Text, } - _, err := b.sendEventWithRetries(channel, event.EventMessage, m) + _, err := b.sendMessageEventWithRetries(channel, m, msg) if err != nil { b.Log.Errorf("sendText failed: %s", err) } @@ -177,14 +209,14 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Edit message if we have an ID if msg.ID != "" { rmsg := event.MessageEventContent{ - Body: username.plain + msg.Text, MsgType: event.MsgText, + Body: msg.Text, } if b.GetBool("HTMLDisable") { - rmsg.FormattedBody = username.formatted + "* " + msg.Text + rmsg.FormattedBody = "* " + msg.Text } else { rmsg.Format = event.FormatHTML - rmsg.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text) + rmsg.FormattedBody = "* " + helper.ParseMarkdown(msg.Text) } rmsg.NewContent = &event.MessageEventContent{ Body: rmsg.Body, @@ -195,12 +227,12 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { Type: event.RelReplace, } - return b.sendEventWithRetries(channel, event.EventMessage, rmsg) + return b.sendMessageEventWithRetries(channel, rmsg, msg) } m := event.MessageEventContent{ - Body: username.plain + msg.Text, - FormattedBody: username.formatted + msg.Text, + Body: msg.Text, + FormattedBody: msg.Text, } // Use notices to send join/leave events @@ -211,11 +243,11 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { if b.GetBool("HTMLDisable") { m.FormattedBody = "" } else { - m.FormattedBody = username.formatted + helper.ParseMarkdown(msg.Text) + m.FormattedBody = helper.ParseMarkdown(msg.Text) } } - return b.sendEventWithRetries(channel, event.EventMessage, m) + return b.sendMessageEventWithRetries(channel, m, msg) } func (b *Bmatrix) handlematrix() { @@ -226,14 +258,17 @@ func (b *Bmatrix) handlematrix() { return } - syncer.OnEventType(event.EventRedaction, b.handleEvent) - syncer.OnEventType(event.EventMessage, b.handleEvent) - syncer.OnEventType(event.StateMember, b.handleMemberChange) + for _, evType := range []event.Type{event.EventRedaction, event.EventMessage, event.StateMember} { + syncer.OnEventType(evType, func(source matrix.EventSource, ev *event.Event) { + b.handleEvent(originClassicSyncer, ev) + }) + } + go func() { for { select { case <-b.stop: - b.stopAck <- 1 + b.stopAck <- struct{}{} return default: @@ -245,26 +280,100 @@ func (b *Bmatrix) handlematrix() { }() } -//nolint: wrapcheck -func (b *Bmatrix) sendEventWithRetries(channel id.RoomID, eventType event.Type, eventData interface{}) (string, error) { - var ( - resp *matrix.RespSendEvent - err error - ) +// Determines if the event comes from ourselves, in which case we want to ignore it +func (b *Bmatrix) ignoreBridgingEvents(ev *event.Event) bool { + if ev.Sender == b.UserID { + return true + } - err = b.retry(func() error { - resp, err = b.mc.SendMessageEvent(channel, eventType, eventData) + // ignore messages we may have sent via the appservice + if b.appService != nil { + if ev.Sender == b.appService.BotClient().UserID { + return true + } - return err - }) - if err != nil { - return "", err + // ignore virtual users messages (we ignore the 'exclusive' field of Namespace for now) + for _, namespace := range b.appService.Registration.Namespaces.UserIDs { + match, err := regexp.MatchString(namespace.Regex, ev.Sender.String()) + if match && err == nil { + return true + } + } } - return string(resp.EventID), err + return false } -func (b *Bmatrix) handleMemberChange(source matrix.EventSource, ev *event.Event) { +//nolint: funlen +func (b *Bmatrix) handleEvent(origin EventOrigin, ev *event.Event) { + b.Log.Debugf("== Receiving event: %#v", ev) + + if b.ignoreBridgingEvents(ev) { + return + } + + if ev.Type == event.StateMember { + b.handleMemberChange(ev) + + return + } + + b.RLock() + channel, ok := b.RoomMap[ev.RoomID] + b.RUnlock() + if !ok { + // we don't know that room yet, that must be a room returned by an application service, + // but matterbridge doesn't handle those just yet + b.Log.Debugf("Unknown room %s", ev.RoomID) + + return + } + + // if we receive appservice events for this room, there is no need to check them with the classical syncer + if !channel.appService && origin == originAppService { + channel.appService = true + b.Lock() + b.RoomMap[ev.RoomID] = channel + b.Unlock() + } + + // if we receive messages both via the classical matrix syncer and appserver, prefer appservice and throw away this duplicate event + if channel.appService && origin != originAppService { + b.Log.Debugf("Dropping event, should receive it via appservice: %s", ev.ID) + + return + } + + // Create our message + rmsg := config.Message{ + Username: b.getDisplayName(ev.RoomID, ev.Sender), + Channel: channel.name, + Account: b.Account, + UserID: string(ev.Sender), + ID: string(ev.ID), + Avatar: b.getAvatarURL(ev.Sender), + } + + // Remove homeserver suffix if configured + if b.GetBool("NoHomeServerSuffix") { + re := regexp.MustCompile("(.*?):.*") + rmsg.Username = re.ReplaceAllString(rmsg.Username, `$1`) + } + + // Delete event + if ev.Type == event.EventRedaction { + rmsg.Event = config.EventMsgDelete + rmsg.ID = string(ev.Redacts) + rmsg.Text = config.EventMsgDelete + b.Remote <- rmsg + + return + } + + b.handleMessage(rmsg, ev) +} + +func (b *Bmatrix) handleMemberChange(ev *event.Event) { member := ev.Content.AsMember() if member == nil { b.Log.Errorf("Couldn't process a member event:\n%#v", ev) @@ -277,7 +386,16 @@ func (b *Bmatrix) handleMemberChange(source matrix.EventSource, ev *event.Event) } } -func (b *Bmatrix) handleMessage(rmsg config.Message, msg event.MessageEventContent, ev *event.Event) { +//nolint: cyclop +func (b *Bmatrix) handleMessage(rmsg config.Message, ev *event.Event) { + msg := ev.Content.AsMessage() + if msg == nil { + b.Log.Errorf("matterbridge don't support this event type: %s", ev.Type.Type) + b.Log.Debugf("Full event: %#v", ev) + + return + } + rmsg.Text = msg.Body // Do we have a /me action @@ -296,7 +414,7 @@ func (b *Bmatrix) handleMessage(rmsg config.Message, msg event.MessageEventConte // Do we have attachments (we only allow image,video or file msgtypes) if msg.MsgType == event.MsgImage || msg.MsgType == event.MsgVideo || msg.MsgType == event.MsgFile { - err := b.handleDownloadFile(&rmsg, msg) + err := b.handleDownloadFile(&rmsg, *msg) if err != nil { b.Log.Errorf("download failed: %#v", err) } @@ -311,54 +429,6 @@ func (b *Bmatrix) handleMessage(rmsg config.Message, msg event.MessageEventConte } } -func (b *Bmatrix) handleEvent(source matrix.EventSource, ev *event.Event) { - b.Log.Debugf("== Receiving event: %#v", ev) - if ev.Sender != b.UserID { - b.RLock() - channel, ok := b.RoomMap[ev.RoomID] - b.RUnlock() - if !ok { - b.Log.Debugf("Unknown room %s", ev.RoomID) - return - } - - // Create our message - rmsg := config.Message{ - Username: b.getDisplayName(ev.RoomID, ev.Sender), - Channel: channel, - Account: b.Account, - UserID: string(ev.Sender), - ID: string(ev.ID), - Avatar: b.getAvatarURL(ev.Sender), - } - - // Remove homeserver suffix if configured - if b.GetBool("NoHomeServerSuffix") { - re := regexp.MustCompile("(.*?):.*") - rmsg.Username = re.ReplaceAllString(rmsg.Username, `$1`) - } - - // Delete event - if ev.Type == event.EventRedaction { - rmsg.Event = config.EventMsgDelete - rmsg.ID = string(ev.Redacts) - rmsg.Text = config.EventMsgDelete - b.Remote <- rmsg - return - } - - msg := ev.Content.AsMessage() - if msg == nil { - b.Log.Errorf("matterbridge don't support this event type: %s", ev.Type.Type) - b.Log.Debugf("Full event: %#v", ev) - - return - } - - b.handleMessage(rmsg, *msg, ev) - } -} - // handleDownloadFile handles file download func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, msg event.MessageEventContent) error { rmsg.Extra = make(map[string][]interface{}) @@ -411,7 +481,6 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel id.RoomID) (str // handleUploadFile handles native upload of a file. //nolint: funlen func (b *Bmatrix) handleUploadFile(msg *config.Message, channel id.RoomID, fi *config.FileInfo) { - username := newMatrixUsername(msg.Username) content := bytes.NewReader(*fi.Data) sp := strings.Split(fi.Name, ".") mtype := mime.TypeByExtension("." + sp[len(sp)-1]) @@ -419,11 +488,11 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel id.RoomID, fi *c // image and video uploads send no username, we have to do this ourself here #715 m := event.MessageEventContent{ MsgType: event.MsgText, - Body: username.plain + fi.Comment, - FormattedBody: username.formatted + fi.Comment, + Body: fi.Comment, + FormattedBody: fi.Comment, } - _, err := b.sendEventWithRetries(channel, event.EventMessage, m) + _, err := b.sendMessageEventWithRetries(channel, m, *msg) if err != nil { b.Log.Errorf("file comment failed: %#v", err) } @@ -482,7 +551,7 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel id.RoomID, fi *c } } - _, err = b.sendEventWithRetries(channel, event.EventMessage, m) + _, err = b.sendMessageEventWithRetries(channel, m, *msg) if err != nil { b.Log.Errorf("sending the message referencing the uploaded file failed: %#v", err) } diff --git a/gateway/gateway.go b/gateway/gateway.go index 1c6c21c3e2..c83f970351 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -306,11 +306,12 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { return false } -func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string { +func (gw *Gateway) ModifyUsername(msg *config.Message, dest *bridge.Bridge) string { if dest.GetBool("StripNick") { re := regexp.MustCompile("[^a-zA-Z0-9]+") msg.Username = re.ReplaceAllString(msg.Username, "") } + nick := dest.GetString("RemoteNickFormat") // loop to replace nicks @@ -445,7 +446,8 @@ func (gw *Gateway) SendMessage( msg.Channel = channel.Name msg.Avatar = gw.modifyAvatar(rmsg, dest) - msg.Username = gw.modifyUsername(rmsg, dest) + msg.OriginalUsername = msg.Username + msg.Username = gw.ModifyUsername(rmsg, dest) msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel) diff --git a/go.mod b/go.mod index b8b0d372a2..2dced7f2cc 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/kyokomi/emoji/v2 v2.2.8 github.com/labstack/echo/v4 v4.3.0 github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b - github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 // indirect github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050 diff --git a/go.sum b/go.sum index 6cc1278caa..7cdd875531 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= @@ -295,6 +296,7 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81 h1:FdZThbRF0R+2qgyBl3KCVNWWBmKm68E+stT3rnQ02Ww= github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81/go.mod h1:lDm2E64X4OjFdBUA4hlN4mEvbSitvhJdKw7rsA8KHgI= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= @@ -358,6 +360,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -391,11 +394,13 @@ github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbV github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= @@ -499,6 +504,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -533,10 +539,12 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= @@ -565,7 +573,6 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d h1:Csy27nDj00vRGp2+QvwSanaW0RA60SZUHXCk4BBT0AU= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A= github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 h1:4J2YZuY8dIYrxbLMsWGqPZb/B59ygCwSBkyZHez5PSY= @@ -676,6 +683,7 @@ github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9/go.mod h1:A5SRAcpTe github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -691,11 +699,13 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/oov/psd v0.0.0-20201002182931-74231384897f/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -850,8 +860,10 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/slack-go/slack v0.9.1 h1:pekQBs0RmrdAgoqzcMCzUCWSyIkhzUU3F83ExAdZrKo= github.com/slack-go/slack v0.9.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= @@ -953,6 +965,7 @@ github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPy github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= @@ -1328,6 +1341,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomod.garykim.dev/nc-talk v0.3.0 h1:MZxLc/gX2/+bdOw4xt6pi+qQFUQld1woGfw1hEJ0fbM= gomod.garykim.dev/nc-talk v0.3.0/go.mod h1:q/Adot/H7iqi+H4lANopV7/xcMf+sX3AZXUXqiITwok= @@ -1466,6 +1480,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1486,6 +1501,7 @@ gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJc gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M= gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1515,6 +1531,7 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0= layeh.com/gumble v0.0.0-20200818122324-146f9205029b h1:Kne6wkHqbqrygRsqs5XUNhSs84DFG5TYMeCkCbM56sY= layeh.com/gumble v0.0.0-20200818122324-146f9205029b/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc= +maunium.net/go/maulogger/v2 v2.2.4 h1:oV2GDeM4fx1uRysdpDC0FcrPg+thFicSd9XzPcYMbVY= maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= maunium.net/go/mautrix v0.9.14 h1:2MMJ630VM+xfa4Q5AooMAhPG1+wQnQybSr/z8PlRZ8A= maunium.net/go/mautrix v0.9.14/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8= diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index dc2626f710..a3661825ec 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -1347,6 +1347,18 @@ StripNick=false #OPTIONAL (default false) ShowTopicChange=false +#Enable to start an application service. +#Must be paired with an AppServiceConfigPath directive +#The application service must be registered in your matrix homeserver too! +UseAppService=false + +#If the application service is enabled, specify the listening host and port +AppServiceHost="0.0.0.0" +AppServicePort=1234 + +#Path to the application service configuration file (in the format used by synapse) +AppServiceConfigPath="appservice_matterbridge_config.yml" + ################################################################### #steam section ################################################################### diff --git a/vendor/github.com/gorilla/mux/AUTHORS b/vendor/github.com/gorilla/mux/AUTHORS new file mode 100644 index 0000000000..b722392ee5 --- /dev/null +++ b/vendor/github.com/gorilla/mux/AUTHORS @@ -0,0 +1,8 @@ +# This is the official list of gorilla/mux authors for copyright purposes. +# +# Please keep the list sorted. + +Google LLC (https://opensource.google.com/) +Kamil Kisielk +Matt Silverlock +Rodrigo Moraes (https://github.com/moraes) diff --git a/vendor/github.com/gorilla/mux/LICENSE b/vendor/github.com/gorilla/mux/LICENSE new file mode 100644 index 0000000000..6903df6386 --- /dev/null +++ b/vendor/github.com/gorilla/mux/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md new file mode 100644 index 0000000000..35eea9f106 --- /dev/null +++ b/vendor/github.com/gorilla/mux/README.md @@ -0,0 +1,805 @@ +# gorilla/mux + +[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) +[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux) +[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) + +![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png) + +https://www.gorillatoolkit.org/pkg/mux + +Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to +their respective handler. + +The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: + +* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. +* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. +* URL hosts, paths and query values can have variables with an optional regular expression. +* Registered URLs can be built, or "reversed", which helps maintaining references to resources. +* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. + +--- + +* [Install](#install) +* [Examples](#examples) +* [Matching Routes](#matching-routes) +* [Static Files](#static-files) +* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.) +* [Registered URLs](#registered-urls) +* [Walking Routes](#walking-routes) +* [Graceful Shutdown](#graceful-shutdown) +* [Middleware](#middleware) +* [Handling CORS Requests](#handling-cors-requests) +* [Testing Handlers](#testing-handlers) +* [Full Example](#full-example) + +--- + +## Install + +With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain: + +```sh +go get -u github.com/gorilla/mux +``` + +## Examples + +Let's start registering a couple of URL paths and handlers: + +```go +func main() { + r := mux.NewRouter() + r.HandleFunc("/", HomeHandler) + r.HandleFunc("/products", ProductsHandler) + r.HandleFunc("/articles", ArticlesHandler) + http.Handle("/", r) +} +``` + +Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters. + +Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: + +```go +r := mux.NewRouter() +r.HandleFunc("/products/{key}", ProductHandler) +r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) +r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) +``` + +The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: + +```go +func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Category: %v\n", vars["category"]) +} +``` + +And this is all you need to know about the basic usage. More advanced options are explained below. + +### Matching Routes + +Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: + +```go +r := mux.NewRouter() +// Only matches if domain is "www.example.com". +r.Host("www.example.com") +// Matches a dynamic subdomain. +r.Host("{subdomain:[a-z]+}.example.com") +``` + +There are several other matchers that can be added. To match path prefixes: + +```go +r.PathPrefix("/products/") +``` + +...or HTTP methods: + +```go +r.Methods("GET", "POST") +``` + +...or URL schemes: + +```go +r.Schemes("https") +``` + +...or header values: + +```go +r.Headers("X-Requested-With", "XMLHttpRequest") +``` + +...or query values: + +```go +r.Queries("key", "value") +``` + +...or to use a custom matcher function: + +```go +r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { + return r.ProtoMajor == 0 +}) +``` + +...and finally, it is possible to combine several matchers in a single route: + +```go +r.HandleFunc("/products", ProductsHandler). + Host("www.example.com"). + Methods("GET"). + Schemes("http") +``` + +Routes are tested in the order they were added to the router. If two routes match, the first one wins: + +```go +r := mux.NewRouter() +r.HandleFunc("/specific", specificHandler) +r.PathPrefix("/").Handler(catchAllHandler) +``` + +Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". + +For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it: + +```go +r := mux.NewRouter() +s := r.Host("www.example.com").Subrouter() +``` + +Then register routes in the subrouter: + +```go +s.HandleFunc("/products/", ProductsHandler) +s.HandleFunc("/products/{key}", ProductHandler) +s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) +``` + +The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. + +Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. + +There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: + +```go +r := mux.NewRouter() +s := r.PathPrefix("/products").Subrouter() +// "/products/" +s.HandleFunc("/", ProductsHandler) +// "/products/{key}/" +s.HandleFunc("/{key}/", ProductHandler) +// "/products/{key}/details" +s.HandleFunc("/{key}/details", ProductDetailsHandler) +``` + + +### Static Files + +Note that the path provided to `PathPrefix()` represents a "wildcard": calling +`PathPrefix("/static/").Handler(...)` means that the handler will be passed any +request that matches "/static/\*". This makes it easy to serve static files with mux: + +```go +func main() { + var dir string + + flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") + flag.Parse() + r := mux.NewRouter() + + // This will serve files under http://localhost:8000/static/ + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) + + srv := &http.Server{ + Handler: r, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) +} +``` + +### Serving Single Page Applications + +Most of the time it makes sense to serve your SPA on a separate web server from your API, +but sometimes it's desirable to serve them both from one place. It's possible to write a simple +handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage +mux's powerful routing for your API endpoints. + +```go +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/gorilla/mux" +) + +// spaHandler implements the http.Handler interface, so we can use it +// to respond to HTTP requests. The path to the static directory and +// path to the index file within that static directory are used to +// serve the SPA in the given static directory. +type spaHandler struct { + staticPath string + indexPath string +} + +// ServeHTTP inspects the URL path to locate a file within the static dir +// on the SPA handler. If a file is found, it will be served. If not, the +// file located at the index path on the SPA handler will be served. This +// is suitable behavior for serving an SPA (single page application). +func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // get the absolute path to prevent directory traversal + path, err := filepath.Abs(r.URL.Path) + if err != nil { + // if we failed to get the absolute path respond with a 400 bad request + // and stop + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // prepend the path with the path to the static directory + path = filepath.Join(h.staticPath, path) + + // check whether a file exists at the given path + _, err = os.Stat(path) + if os.IsNotExist(err) { + // file does not exist, serve index.html + http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) + return + } else if err != nil { + // if we got an error (that wasn't that the file doesn't exist) stating the + // file, return a 500 internal server error and stop + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // otherwise, use http.FileServer to serve the static dir + http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) +} + +func main() { + router := mux.NewRouter() + + router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { + // an example API handler + json.NewEncoder(w).Encode(map[string]bool{"ok": true}) + }) + + spa := spaHandler{staticPath: "build", indexPath: "index.html"} + router.PathPrefix("/").Handler(spa) + + srv := &http.Server{ + Handler: router, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) +} +``` + +### Registered URLs + +Now let's see how to build registered URLs. + +Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example: + +```go +r := mux.NewRouter() +r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). + Name("article") +``` + +To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: + +```go +url, err := r.Get("article").URL("category", "technology", "id", "42") +``` + +...and the result will be a `url.URL` with the following path: + +``` +"/articles/technology/42" +``` + +This also works for host and query value variables: + +```go +r := mux.NewRouter() +r.Host("{subdomain}.example.com"). + Path("/articles/{category}/{id:[0-9]+}"). + Queries("filter", "{filter}"). + HandlerFunc(ArticleHandler). + Name("article") + +// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla" +url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42", + "filter", "gorilla") +``` + +All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. + +Regex support also exists for matching Headers within a route. For example, we could do: + +```go +r.HeadersRegexp("Content-Type", "application/(text|json)") +``` + +...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` + +There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do: + +```go +// "http://news.example.com/" +host, err := r.Get("article").URLHost("subdomain", "news") + +// "/articles/technology/42" +path, err := r.Get("article").URLPath("category", "technology", "id", "42") +``` + +And if you use subrouters, host and path defined separately can be built as well: + +```go +r := mux.NewRouter() +s := r.Host("{subdomain}.example.com").Subrouter() +s.Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + +// "http://news.example.com/articles/technology/42" +url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") +``` + +### Walking Routes + +The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, +the following prints all of the registered routes: + +```go +package main + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +func handler(w http.ResponseWriter, r *http.Request) { + return +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/", handler) + r.HandleFunc("/products", handler).Methods("POST") + r.HandleFunc("/articles", handler).Methods("GET") + r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT") + r.HandleFunc("/authors", handler).Queries("surname", "{surname}") + err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + pathTemplate, err := route.GetPathTemplate() + if err == nil { + fmt.Println("ROUTE:", pathTemplate) + } + pathRegexp, err := route.GetPathRegexp() + if err == nil { + fmt.Println("Path regexp:", pathRegexp) + } + queriesTemplates, err := route.GetQueriesTemplates() + if err == nil { + fmt.Println("Queries templates:", strings.Join(queriesTemplates, ",")) + } + queriesRegexps, err := route.GetQueriesRegexp() + if err == nil { + fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ",")) + } + methods, err := route.GetMethods() + if err == nil { + fmt.Println("Methods:", strings.Join(methods, ",")) + } + fmt.Println() + return nil + }) + + if err != nil { + fmt.Println(err) + } + + http.Handle("/", r) +} +``` + +### Graceful Shutdown + +Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`: + +```go +package main + +import ( + "context" + "flag" + "log" + "net/http" + "os" + "os/signal" + "time" + + "github.com/gorilla/mux" +) + +func main() { + var wait time.Duration + flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") + flag.Parse() + + r := mux.NewRouter() + // Add your routes as needed + + srv := &http.Server{ + Addr: "0.0.0.0:8080", + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + Handler: r, // Pass our instance of gorilla/mux in. + } + + // Run our server in a goroutine so that it doesn't block. + go func() { + if err := srv.ListenAndServe(); err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to wait for. + ctx, cancel := context.WithTimeout(context.Background(), wait) + defer cancel() + // Doesn't block if no connections, but will otherwise wait + // until the timeout deadline. + srv.Shutdown(ctx) + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should wait for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) +} +``` + +### Middleware + +Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters. +Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking. + +Mux middlewares are defined using the de facto standard type: + +```go +type MiddlewareFunc func(http.Handler) http.Handler +``` + +Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers. + +A very basic middleware which logs the URI of the request being handled could be written as: + +```go +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Do stuff here + log.Println(r.RequestURI) + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + }) +} +``` + +Middlewares can be added to a router using `Router.Use()`: + +```go +r := mux.NewRouter() +r.HandleFunc("/", handler) +r.Use(loggingMiddleware) +``` + +A more complex authentication middleware, which maps session token to users, could be written as: + +```go +// Define our struct +type authenticationMiddleware struct { + tokenUsers map[string]string +} + +// Initialize it somewhere +func (amw *authenticationMiddleware) Populate() { + amw.tokenUsers["00000000"] = "user0" + amw.tokenUsers["aaaaaaaa"] = "userA" + amw.tokenUsers["05f717e5"] = "randomUser" + amw.tokenUsers["deadbeef"] = "user0" +} + +// Middleware function, which will be called for each request +func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("X-Session-Token") + + if user, found := amw.tokenUsers[token]; found { + // We found the token in our map + log.Printf("Authenticated user %s\n", user) + // Pass down the request to the next middleware (or final handler) + next.ServeHTTP(w, r) + } else { + // Write an error and stop the handler chain + http.Error(w, "Forbidden", http.StatusForbidden) + } + }) +} +``` + +```go +r := mux.NewRouter() +r.HandleFunc("/", handler) + +amw := authenticationMiddleware{} +amw.Populate() + +r.Use(amw.Middleware) +``` + +Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. + +### Handling CORS Requests + +[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header. + +* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin` +* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route +* If you do not specify any methods, then: +> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers. + +Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers: + +```go +package main + +import ( + "net/http" + "github.com/gorilla/mux" +) + +func main() { + r := mux.NewRouter() + + // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers + r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions) + r.Use(mux.CORSMethodMiddleware(r)) + + http.ListenAndServe(":8080", r) +} + +func fooHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + if r.Method == http.MethodOptions { + return + } + + w.Write([]byte("foo")) +} +``` + +And an request to `/foo` using something like: + +```bash +curl localhost:8080/foo -v +``` + +Would look like: + +```bash +* Trying ::1... +* TCP_NODELAY set +* Connected to localhost (::1) port 8080 (#0) +> GET /foo HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.59.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS +< Access-Control-Allow-Origin: * +< Date: Fri, 28 Jun 2019 20:13:30 GMT +< Content-Length: 3 +< Content-Type: text/plain; charset=utf-8 +< +* Connection #0 to host localhost left intact +foo +``` + +### Testing Handlers + +Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. + +First, our simple HTTP handler: + +```go +// endpoints.go +package main + +func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { + // A very simple health check. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + // In the future we could report back on the status of our DB, or our cache + // (e.g. Redis) by performing a simple PING, and include them in the response. + io.WriteString(w, `{"alive": true}`) +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/health", HealthCheckHandler) + + log.Fatal(http.ListenAndServe("localhost:8080", r)) +} +``` + +Our test code: + +```go +// endpoints_test.go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthCheckHandler(t *testing.T) { + // Create a request to pass to our handler. We don't have any query parameters for now, so we'll + // pass 'nil' as the third parameter. + req, err := http.NewRequest("GET", "/health", nil) + if err != nil { + t.Fatal(err) + } + + // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. + rr := httptest.NewRecorder() + handler := http.HandlerFunc(HealthCheckHandler) + + // Our handlers satisfy http.Handler, so we can call their ServeHTTP method + // directly and pass in our Request and ResponseRecorder. + handler.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + // Check the response body is what we expect. + expected := `{"alive": true}` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } +} +``` + +In the case that our routes have [variables](#examples), we can pass those in the request. We could write +[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple +possible route variables as needed. + +```go +// endpoints.go +func main() { + r := mux.NewRouter() + // A route with a route variable: + r.HandleFunc("/metrics/{type}", MetricsHandler) + + log.Fatal(http.ListenAndServe("localhost:8080", r)) +} +``` + +Our test file, with a table-driven test of `routeVariables`: + +```go +// endpoints_test.go +func TestMetricsHandler(t *testing.T) { + tt := []struct{ + routeVariable string + shouldPass bool + }{ + {"goroutines", true}, + {"heap", true}, + {"counters", true}, + {"queries", true}, + {"adhadaeqm3k", false}, + } + + for _, tc := range tt { + path := fmt.Sprintf("/metrics/%s", tc.routeVariable) + req, err := http.NewRequest("GET", path, nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + // Need to create a router that we can pass the request through so that the vars will be added to the context + router := mux.NewRouter() + router.HandleFunc("/metrics/{type}", MetricsHandler) + router.ServeHTTP(rr, req) + + // In this case, our MetricsHandler returns a non-200 response + // for a route variable it doesn't know about. + if rr.Code == http.StatusOK && !tc.shouldPass { + t.Errorf("handler should have failed on routeVariable %s: got %v want %v", + tc.routeVariable, rr.Code, http.StatusOK) + } + } +} +``` + +## Full Example + +Here's a complete, runnable example of a small `mux` based server: + +```go +package main + +import ( + "net/http" + "log" + "github.com/gorilla/mux" +) + +func YourHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Gorilla!\n")) +} + +func main() { + r := mux.NewRouter() + // Routes consist of a path and a handler function. + r.HandleFunc("/", YourHandler) + + // Bind to a port and pass our router in + log.Fatal(http.ListenAndServe(":8000", r)) +} +``` + +## License + +BSD licensed. See the LICENSE file for details. diff --git a/vendor/github.com/gorilla/mux/doc.go b/vendor/github.com/gorilla/mux/doc.go new file mode 100644 index 0000000000..bd5a38b55d --- /dev/null +++ b/vendor/github.com/gorilla/mux/doc.go @@ -0,0 +1,306 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package mux implements a request router and dispatcher. + +The name mux stands for "HTTP request multiplexer". Like the standard +http.ServeMux, mux.Router matches incoming requests against a list of +registered routes and calls a handler for the route that matches the URL +or other conditions. The main features are: + + * Requests can be matched based on URL host, path, path prefix, schemes, + header and query values, HTTP methods or using custom matchers. + * URL hosts, paths and query values can have variables with an optional + regular expression. + * Registered URLs can be built, or "reversed", which helps maintaining + references to resources. + * Routes can be used as subrouters: nested routes are only tested if the + parent route matches. This is useful to define groups of routes that + share common conditions like a host, a path prefix or other repeated + attributes. As a bonus, this optimizes request matching. + * It implements the http.Handler interface so it is compatible with the + standard http.ServeMux. + +Let's start registering a couple of URL paths and handlers: + + func main() { + r := mux.NewRouter() + r.HandleFunc("/", HomeHandler) + r.HandleFunc("/products", ProductsHandler) + r.HandleFunc("/articles", ArticlesHandler) + http.Handle("/", r) + } + +Here we register three routes mapping URL paths to handlers. This is +equivalent to how http.HandleFunc() works: if an incoming request URL matches +one of the paths, the corresponding handler is called passing +(http.ResponseWriter, *http.Request) as parameters. + +Paths can have variables. They are defined using the format {name} or +{name:pattern}. If a regular expression pattern is not defined, the matched +variable will be anything until the next slash. For example: + + r := mux.NewRouter() + r.HandleFunc("/products/{key}", ProductHandler) + r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) + r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) + +Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: + + r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) + +The names are used to create a map of route variables which can be retrieved +calling mux.Vars(): + + vars := mux.Vars(request) + category := vars["category"] + +Note that if any capturing groups are present, mux will panic() during parsing. To prevent +this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to +"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably +when capturing groups were present. + +And this is all you need to know about the basic usage. More advanced options +are explained below. + +Routes can also be restricted to a domain or subdomain. Just define a host +pattern to be matched. They can also have variables: + + r := mux.NewRouter() + // Only matches if domain is "www.example.com". + r.Host("www.example.com") + // Matches a dynamic subdomain. + r.Host("{subdomain:[a-z]+}.domain.com") + +There are several other matchers that can be added. To match path prefixes: + + r.PathPrefix("/products/") + +...or HTTP methods: + + r.Methods("GET", "POST") + +...or URL schemes: + + r.Schemes("https") + +...or header values: + + r.Headers("X-Requested-With", "XMLHttpRequest") + +...or query values: + + r.Queries("key", "value") + +...or to use a custom matcher function: + + r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { + return r.ProtoMajor == 0 + }) + +...and finally, it is possible to combine several matchers in a single route: + + r.HandleFunc("/products", ProductsHandler). + Host("www.example.com"). + Methods("GET"). + Schemes("http") + +Setting the same matching conditions again and again can be boring, so we have +a way to group several routes that share the same requirements. +We call it "subrouting". + +For example, let's say we have several URLs that should only match when the +host is "www.example.com". Create a route for that host and get a "subrouter" +from it: + + r := mux.NewRouter() + s := r.Host("www.example.com").Subrouter() + +Then register routes in the subrouter: + + s.HandleFunc("/products/", ProductsHandler) + s.HandleFunc("/products/{key}", ProductHandler) + s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) + +The three URL paths we registered above will only be tested if the domain is +"www.example.com", because the subrouter is tested first. This is not +only convenient, but also optimizes request matching. You can create +subrouters combining any attribute matchers accepted by a route. + +Subrouters can be used to create domain or path "namespaces": you define +subrouters in a central place and then parts of the app can register its +paths relatively to a given subrouter. + +There's one more thing about subroutes. When a subrouter has a path prefix, +the inner routes use it as base for their paths: + + r := mux.NewRouter() + s := r.PathPrefix("/products").Subrouter() + // "/products/" + s.HandleFunc("/", ProductsHandler) + // "/products/{key}/" + s.HandleFunc("/{key}/", ProductHandler) + // "/products/{key}/details" + s.HandleFunc("/{key}/details", ProductDetailsHandler) + +Note that the path provided to PathPrefix() represents a "wildcard": calling +PathPrefix("/static/").Handler(...) means that the handler will be passed any +request that matches "/static/*". This makes it easy to serve static files with mux: + + func main() { + var dir string + + flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") + flag.Parse() + r := mux.NewRouter() + + // This will serve files under http://localhost:8000/static/ + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) + + srv := &http.Server{ + Handler: r, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) + } + +Now let's see how to build registered URLs. + +Routes can be named. All routes that define a name can have their URLs built, +or "reversed". We define a name calling Name() on a route. For example: + + r := mux.NewRouter() + r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). + Name("article") + +To build a URL, get the route and call the URL() method, passing a sequence of +key/value pairs for the route variables. For the previous route, we would do: + + url, err := r.Get("article").URL("category", "technology", "id", "42") + +...and the result will be a url.URL with the following path: + + "/articles/technology/42" + +This also works for host and query value variables: + + r := mux.NewRouter() + r.Host("{subdomain}.domain.com"). + Path("/articles/{category}/{id:[0-9]+}"). + Queries("filter", "{filter}"). + HandlerFunc(ArticleHandler). + Name("article") + + // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" + url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42", + "filter", "gorilla") + +All variables defined in the route are required, and their values must +conform to the corresponding patterns. These requirements guarantee that a +generated URL will always match a registered route -- the only exception is +for explicitly defined "build-only" routes which never match. + +Regex support also exists for matching Headers within a route. For example, we could do: + + r.HeadersRegexp("Content-Type", "application/(text|json)") + +...and the route will match both requests with a Content-Type of `application/json` as well as +`application/text` + +There's also a way to build only the URL host or path for a route: +use the methods URLHost() or URLPath() instead. For the previous route, +we would do: + + // "http://news.domain.com/" + host, err := r.Get("article").URLHost("subdomain", "news") + + // "/articles/technology/42" + path, err := r.Get("article").URLPath("category", "technology", "id", "42") + +And if you use subrouters, host and path defined separately can be built +as well: + + r := mux.NewRouter() + s := r.Host("{subdomain}.domain.com").Subrouter() + s.Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + + // "http://news.domain.com/articles/technology/42" + url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") + +Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking. + + type MiddlewareFunc func(http.Handler) http.Handler + +Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). + +A very basic middleware which logs the URI of the request being handled could be written as: + + func simpleMw(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Do stuff here + log.Println(r.RequestURI) + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + }) + } + +Middlewares can be added to a router using `Router.Use()`: + + r := mux.NewRouter() + r.HandleFunc("/", handler) + r.Use(simpleMw) + +A more complex authentication middleware, which maps session token to users, could be written as: + + // Define our struct + type authenticationMiddleware struct { + tokenUsers map[string]string + } + + // Initialize it somewhere + func (amw *authenticationMiddleware) Populate() { + amw.tokenUsers["00000000"] = "user0" + amw.tokenUsers["aaaaaaaa"] = "userA" + amw.tokenUsers["05f717e5"] = "randomUser" + amw.tokenUsers["deadbeef"] = "user0" + } + + // Middleware function, which will be called for each request + func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("X-Session-Token") + + if user, found := amw.tokenUsers[token]; found { + // We found the token in our map + log.Printf("Authenticated user %s\n", user) + next.ServeHTTP(w, r) + } else { + http.Error(w, "Forbidden", http.StatusForbidden) + } + }) + } + + r := mux.NewRouter() + r.HandleFunc("/", handler) + + amw := authenticationMiddleware{tokenUsers: make(map[string]string)} + amw.Populate() + + r.Use(amw.Middleware) + +Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. + +*/ +package mux diff --git a/vendor/github.com/gorilla/mux/go.mod b/vendor/github.com/gorilla/mux/go.mod new file mode 100644 index 0000000000..df170a3994 --- /dev/null +++ b/vendor/github.com/gorilla/mux/go.mod @@ -0,0 +1,3 @@ +module github.com/gorilla/mux + +go 1.12 diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go new file mode 100644 index 0000000000..cb51c565eb --- /dev/null +++ b/vendor/github.com/gorilla/mux/middleware.go @@ -0,0 +1,74 @@ +package mux + +import ( + "net/http" + "strings" +) + +// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler. +// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed +// to it, and then calls the handler passed as parameter to the MiddlewareFunc. +type MiddlewareFunc func(http.Handler) http.Handler + +// middleware interface is anything which implements a MiddlewareFunc named Middleware. +type middleware interface { + Middleware(handler http.Handler) http.Handler +} + +// Middleware allows MiddlewareFunc to implement the middleware interface. +func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler { + return mw(handler) +} + +// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. +func (r *Router) Use(mwf ...MiddlewareFunc) { + for _, fn := range mwf { + r.middlewares = append(r.middlewares, fn) + } +} + +// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. +func (r *Router) useInterface(mw middleware) { + r.middlewares = append(r.middlewares, mw) +} + +// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header +// on requests for routes that have an OPTIONS method matcher to all the method matchers on +// the route. Routes that do not explicitly handle OPTIONS requests will not be processed +// by the middleware. See examples for usage. +func CORSMethodMiddleware(r *Router) MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + allMethods, err := getAllMethodsForRoute(r, req) + if err == nil { + for _, v := range allMethods { + if v == http.MethodOptions { + w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ",")) + } + } + } + + next.ServeHTTP(w, req) + }) + } +} + +// getAllMethodsForRoute returns all the methods from method matchers matching a given +// request. +func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) { + var allMethods []string + + for _, route := range r.routes { + var match RouteMatch + if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch { + methods, err := route.GetMethods() + if err != nil { + return nil, err + } + + allMethods = append(allMethods, methods...) + } + } + + return allMethods, nil +} diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go new file mode 100644 index 0000000000..782a34b22a --- /dev/null +++ b/vendor/github.com/gorilla/mux/mux.go @@ -0,0 +1,606 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "context" + "errors" + "fmt" + "net/http" + "path" + "regexp" +) + +var ( + // ErrMethodMismatch is returned when the method in the request does not match + // the method defined against the route. + ErrMethodMismatch = errors.New("method is not allowed") + // ErrNotFound is returned when no route match is found. + ErrNotFound = errors.New("no matching route was found") +) + +// NewRouter returns a new router instance. +func NewRouter() *Router { + return &Router{namedRoutes: make(map[string]*Route)} +} + +// Router registers routes to be matched and dispatches a handler. +// +// It implements the http.Handler interface, so it can be registered to serve +// requests: +// +// var router = mux.NewRouter() +// +// func main() { +// http.Handle("/", router) +// } +// +// Or, for Google App Engine, register it in a init() function: +// +// func init() { +// http.Handle("/", router) +// } +// +// This will send all incoming requests to the router. +type Router struct { + // Configurable Handler to be used when no route matches. + NotFoundHandler http.Handler + + // Configurable Handler to be used when the request method does not match the route. + MethodNotAllowedHandler http.Handler + + // Routes to be matched, in order. + routes []*Route + + // Routes by name for URL building. + namedRoutes map[string]*Route + + // If true, do not clear the request context after handling the request. + // + // Deprecated: No effect, since the context is stored on the request itself. + KeepContext bool + + // Slice of middlewares to be called after a match is found + middlewares []middleware + + // configuration shared with `Route` + routeConf +} + +// common route configuration shared between `Router` and `Route` +type routeConf struct { + // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to" + useEncodedPath bool + + // If true, when the path pattern is "/path/", accessing "/path" will + // redirect to the former and vice versa. + strictSlash bool + + // If true, when the path pattern is "/path//to", accessing "/path//to" + // will not redirect + skipClean bool + + // Manager for the variables from host and path. + regexp routeRegexpGroup + + // List of matchers. + matchers []matcher + + // The scheme used when building URLs. + buildScheme string + + buildVarsFunc BuildVarsFunc +} + +// returns an effective deep copy of `routeConf` +func copyRouteConf(r routeConf) routeConf { + c := r + + if r.regexp.path != nil { + c.regexp.path = copyRouteRegexp(r.regexp.path) + } + + if r.regexp.host != nil { + c.regexp.host = copyRouteRegexp(r.regexp.host) + } + + c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries)) + for _, q := range r.regexp.queries { + c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q)) + } + + c.matchers = make([]matcher, len(r.matchers)) + copy(c.matchers, r.matchers) + + return c +} + +func copyRouteRegexp(r *routeRegexp) *routeRegexp { + c := *r + return &c +} + +// Match attempts to match the given request against the router's registered routes. +// +// If the request matches a route of this router or one of its subrouters the Route, +// Handler, and Vars fields of the the match argument are filled and this function +// returns true. +// +// If the request does not match any of this router's or its subrouters' routes +// then this function returns false. If available, a reason for the match failure +// will be filled in the match argument's MatchErr field. If the match failure type +// (eg: not found) has a registered handler, the handler is assigned to the Handler +// field of the match argument. +func (r *Router) Match(req *http.Request, match *RouteMatch) bool { + for _, route := range r.routes { + if route.Match(req, match) { + // Build middleware chain if no error was found + if match.MatchErr == nil { + for i := len(r.middlewares) - 1; i >= 0; i-- { + match.Handler = r.middlewares[i].Middleware(match.Handler) + } + } + return true + } + } + + if match.MatchErr == ErrMethodMismatch { + if r.MethodNotAllowedHandler != nil { + match.Handler = r.MethodNotAllowedHandler + return true + } + + return false + } + + // Closest match for a router (includes sub-routers) + if r.NotFoundHandler != nil { + match.Handler = r.NotFoundHandler + match.MatchErr = ErrNotFound + return true + } + + match.MatchErr = ErrNotFound + return false +} + +// ServeHTTP dispatches the handler registered in the matched route. +// +// When there is a match, the route variables can be retrieved calling +// mux.Vars(request). +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if !r.skipClean { + path := req.URL.Path + if r.useEncodedPath { + path = req.URL.EscapedPath() + } + // Clean path to canonical form and redirect. + if p := cleanPath(path); p != path { + + // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. + // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: + // http://code.google.com/p/go/issues/detail?id=5252 + url := *req.URL + url.Path = p + p = url.String() + + w.Header().Set("Location", p) + w.WriteHeader(http.StatusMovedPermanently) + return + } + } + var match RouteMatch + var handler http.Handler + if r.Match(req, &match) { + handler = match.Handler + req = requestWithVars(req, match.Vars) + req = requestWithRoute(req, match.Route) + } + + if handler == nil && match.MatchErr == ErrMethodMismatch { + handler = methodNotAllowedHandler() + } + + if handler == nil { + handler = http.NotFoundHandler() + } + + handler.ServeHTTP(w, req) +} + +// Get returns a route registered with the given name. +func (r *Router) Get(name string) *Route { + return r.namedRoutes[name] +} + +// GetRoute returns a route registered with the given name. This method +// was renamed to Get() and remains here for backwards compatibility. +func (r *Router) GetRoute(name string) *Route { + return r.namedRoutes[name] +} + +// StrictSlash defines the trailing slash behavior for new routes. The initial +// value is false. +// +// When true, if the route path is "/path/", accessing "/path" will perform a redirect +// to the former and vice versa. In other words, your application will always +// see the path as specified in the route. +// +// When false, if the route path is "/path", accessing "/path/" will not match +// this route and vice versa. +// +// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for +// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed +// request will be made as a GET by most clients. Use middleware or client settings +// to modify this behaviour as needed. +// +// Special case: when a route sets a path prefix using the PathPrefix() method, +// strict slash is ignored for that route because the redirect behavior can't +// be determined from a prefix alone. However, any subrouters created from that +// route inherit the original StrictSlash setting. +func (r *Router) StrictSlash(value bool) *Router { + r.strictSlash = value + return r +} + +// SkipClean defines the path cleaning behaviour for new routes. The initial +// value is false. Users should be careful about which routes are not cleaned +// +// When true, if the route path is "/path//to", it will remain with the double +// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ +// +// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will +// become /fetch/http/xkcd.com/534 +func (r *Router) SkipClean(value bool) *Router { + r.skipClean = value + return r +} + +// UseEncodedPath tells the router to match the encoded original path +// to the routes. +// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". +// +// If not called, the router will match the unencoded path to the routes. +// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" +func (r *Router) UseEncodedPath() *Router { + r.useEncodedPath = true + return r +} + +// ---------------------------------------------------------------------------- +// Route factories +// ---------------------------------------------------------------------------- + +// NewRoute registers an empty route. +func (r *Router) NewRoute() *Route { + // initialize a route with a copy of the parent router's configuration + route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} + r.routes = append(r.routes, route) + return route +} + +// Name registers a new route with a name. +// See Route.Name(). +func (r *Router) Name(name string) *Route { + return r.NewRoute().Name(name) +} + +// Handle registers a new route with a matcher for the URL path. +// See Route.Path() and Route.Handler(). +func (r *Router) Handle(path string, handler http.Handler) *Route { + return r.NewRoute().Path(path).Handler(handler) +} + +// HandleFunc registers a new route with a matcher for the URL path. +// See Route.Path() and Route.HandlerFunc(). +func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, + *http.Request)) *Route { + return r.NewRoute().Path(path).HandlerFunc(f) +} + +// Headers registers a new route with a matcher for request header values. +// See Route.Headers(). +func (r *Router) Headers(pairs ...string) *Route { + return r.NewRoute().Headers(pairs...) +} + +// Host registers a new route with a matcher for the URL host. +// See Route.Host(). +func (r *Router) Host(tpl string) *Route { + return r.NewRoute().Host(tpl) +} + +// MatcherFunc registers a new route with a custom matcher function. +// See Route.MatcherFunc(). +func (r *Router) MatcherFunc(f MatcherFunc) *Route { + return r.NewRoute().MatcherFunc(f) +} + +// Methods registers a new route with a matcher for HTTP methods. +// See Route.Methods(). +func (r *Router) Methods(methods ...string) *Route { + return r.NewRoute().Methods(methods...) +} + +// Path registers a new route with a matcher for the URL path. +// See Route.Path(). +func (r *Router) Path(tpl string) *Route { + return r.NewRoute().Path(tpl) +} + +// PathPrefix registers a new route with a matcher for the URL path prefix. +// See Route.PathPrefix(). +func (r *Router) PathPrefix(tpl string) *Route { + return r.NewRoute().PathPrefix(tpl) +} + +// Queries registers a new route with a matcher for URL query values. +// See Route.Queries(). +func (r *Router) Queries(pairs ...string) *Route { + return r.NewRoute().Queries(pairs...) +} + +// Schemes registers a new route with a matcher for URL schemes. +// See Route.Schemes(). +func (r *Router) Schemes(schemes ...string) *Route { + return r.NewRoute().Schemes(schemes...) +} + +// BuildVarsFunc registers a new route with a custom function for modifying +// route variables before building a URL. +func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { + return r.NewRoute().BuildVarsFunc(f) +} + +// Walk walks the router and all its sub-routers, calling walkFn for each route +// in the tree. The routes are walked in the order they were added. Sub-routers +// are explored depth-first. +func (r *Router) Walk(walkFn WalkFunc) error { + return r.walk(walkFn, []*Route{}) +} + +// SkipRouter is used as a return value from WalkFuncs to indicate that the +// router that walk is about to descend down to should be skipped. +var SkipRouter = errors.New("skip this router") + +// WalkFunc is the type of the function called for each route visited by Walk. +// At every invocation, it is given the current route, and the current router, +// and a list of ancestor routes that lead to the current route. +type WalkFunc func(route *Route, router *Router, ancestors []*Route) error + +func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { + for _, t := range r.routes { + err := walkFn(t, r, ancestors) + if err == SkipRouter { + continue + } + if err != nil { + return err + } + for _, sr := range t.matchers { + if h, ok := sr.(*Router); ok { + ancestors = append(ancestors, t) + err := h.walk(walkFn, ancestors) + if err != nil { + return err + } + ancestors = ancestors[:len(ancestors)-1] + } + } + if h, ok := t.handler.(*Router); ok { + ancestors = append(ancestors, t) + err := h.walk(walkFn, ancestors) + if err != nil { + return err + } + ancestors = ancestors[:len(ancestors)-1] + } + } + return nil +} + +// ---------------------------------------------------------------------------- +// Context +// ---------------------------------------------------------------------------- + +// RouteMatch stores information about a matched route. +type RouteMatch struct { + Route *Route + Handler http.Handler + Vars map[string]string + + // MatchErr is set to appropriate matching error + // It is set to ErrMethodMismatch if there is a mismatch in + // the request method and route method + MatchErr error +} + +type contextKey int + +const ( + varsKey contextKey = iota + routeKey +) + +// Vars returns the route variables for the current request, if any. +func Vars(r *http.Request) map[string]string { + if rv := r.Context().Value(varsKey); rv != nil { + return rv.(map[string]string) + } + return nil +} + +// CurrentRoute returns the matched route for the current request, if any. +// This only works when called inside the handler of the matched route +// because the matched route is stored in the request context which is cleared +// after the handler returns. +func CurrentRoute(r *http.Request) *Route { + if rv := r.Context().Value(routeKey); rv != nil { + return rv.(*Route) + } + return nil +} + +func requestWithVars(r *http.Request, vars map[string]string) *http.Request { + ctx := context.WithValue(r.Context(), varsKey, vars) + return r.WithContext(ctx) +} + +func requestWithRoute(r *http.Request, route *Route) *http.Request { + ctx := context.WithValue(r.Context(), routeKey, route) + return r.WithContext(ctx) +} + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +// cleanPath returns the canonical path for p, eliminating . and .. elements. +// Borrowed from the net/http package. +func cleanPath(p string) string { + if p == "" { + return "/" + } + if p[0] != '/' { + p = "/" + p + } + np := path.Clean(p) + // path.Clean removes trailing slash except for root; + // put the trailing slash back if necessary. + if p[len(p)-1] == '/' && np != "/" { + np += "/" + } + + return np +} + +// uniqueVars returns an error if two slices contain duplicated strings. +func uniqueVars(s1, s2 []string) error { + for _, v1 := range s1 { + for _, v2 := range s2 { + if v1 == v2 { + return fmt.Errorf("mux: duplicated route variable %q", v2) + } + } + } + return nil +} + +// checkPairs returns the count of strings passed in, and an error if +// the count is not an even number. +func checkPairs(pairs ...string) (int, error) { + length := len(pairs) + if length%2 != 0 { + return length, fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + } + return length, nil +} + +// mapFromPairsToString converts variadic string parameters to a +// string to string map. +func mapFromPairsToString(pairs ...string) (map[string]string, error) { + length, err := checkPairs(pairs...) + if err != nil { + return nil, err + } + m := make(map[string]string, length/2) + for i := 0; i < length; i += 2 { + m[pairs[i]] = pairs[i+1] + } + return m, nil +} + +// mapFromPairsToRegex converts variadic string parameters to a +// string to regex map. +func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { + length, err := checkPairs(pairs...) + if err != nil { + return nil, err + } + m := make(map[string]*regexp.Regexp, length/2) + for i := 0; i < length; i += 2 { + regex, err := regexp.Compile(pairs[i+1]) + if err != nil { + return nil, err + } + m[pairs[i]] = regex + } + return m, nil +} + +// matchInArray returns true if the given string value is in the array. +func matchInArray(arr []string, value string) bool { + for _, v := range arr { + if v == value { + return true + } + } + return false +} + +// matchMapWithString returns true if the given key/value pairs exist in a given map. +func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { + for k, v := range toCheck { + // Check if key exists. + if canonicalKey { + k = http.CanonicalHeaderKey(k) + } + if values := toMatch[k]; values == nil { + return false + } else if v != "" { + // If value was defined as an empty string we only check that the + // key exists. Otherwise we also check for equality. + valueExists := false + for _, value := range values { + if v == value { + valueExists = true + break + } + } + if !valueExists { + return false + } + } + } + return true +} + +// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against +// the given regex +func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { + for k, v := range toCheck { + // Check if key exists. + if canonicalKey { + k = http.CanonicalHeaderKey(k) + } + if values := toMatch[k]; values == nil { + return false + } else if v != nil { + // If value was defined as an empty string we only check that the + // key exists. Otherwise we also check for equality. + valueExists := false + for _, value := range values { + if v.MatchString(value) { + valueExists = true + break + } + } + if !valueExists { + return false + } + } + } + return true +} + +// methodNotAllowed replies to the request with an HTTP status code 405. +func methodNotAllowed(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusMethodNotAllowed) +} + +// methodNotAllowedHandler returns a simple request handler +// that replies to each request with a status code 405. +func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go new file mode 100644 index 0000000000..0144842bb2 --- /dev/null +++ b/vendor/github.com/gorilla/mux/regexp.go @@ -0,0 +1,388 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "bytes" + "fmt" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" +) + +type routeRegexpOptions struct { + strictSlash bool + useEncodedPath bool +} + +type regexpType int + +const ( + regexpTypePath regexpType = 0 + regexpTypeHost regexpType = 1 + regexpTypePrefix regexpType = 2 + regexpTypeQuery regexpType = 3 +) + +// newRouteRegexp parses a route template and returns a routeRegexp, +// used to match a host, a path or a query string. +// +// It will extract named variables, assemble a regexp to be matched, create +// a "reverse" template to build URLs and compile regexps to validate variable +// values used in URL building. +// +// Previously we accepted only Python-like identifiers for variable +// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that +// name and pattern can't be empty, and names can't contain a colon. +func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) { + // Check if it is well-formed. + idxs, errBraces := braceIndices(tpl) + if errBraces != nil { + return nil, errBraces + } + // Backup the original. + template := tpl + // Now let's parse it. + defaultPattern := "[^/]+" + if typ == regexpTypeQuery { + defaultPattern = ".*" + } else if typ == regexpTypeHost { + defaultPattern = "[^.]+" + } + // Only match strict slash if not matching + if typ != regexpTypePath { + options.strictSlash = false + } + // Set a flag for strictSlash. + endSlash := false + if options.strictSlash && strings.HasSuffix(tpl, "/") { + tpl = tpl[:len(tpl)-1] + endSlash = true + } + varsN := make([]string, len(idxs)/2) + varsR := make([]*regexp.Regexp, len(idxs)/2) + pattern := bytes.NewBufferString("") + pattern.WriteByte('^') + reverse := bytes.NewBufferString("") + var end int + var err error + for i := 0; i < len(idxs); i += 2 { + // Set all values we are interested in. + raw := tpl[end:idxs[i]] + end = idxs[i+1] + parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) + name := parts[0] + patt := defaultPattern + if len(parts) == 2 { + patt = parts[1] + } + // Name or pattern can't be empty. + if name == "" || patt == "" { + return nil, fmt.Errorf("mux: missing name or pattern in %q", + tpl[idxs[i]:end]) + } + // Build the regexp pattern. + fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) + + // Build the reverse template. + fmt.Fprintf(reverse, "%s%%s", raw) + + // Append variable name and compiled pattern. + varsN[i/2] = name + varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) + if err != nil { + return nil, err + } + } + // Add the remaining. + raw := tpl[end:] + pattern.WriteString(regexp.QuoteMeta(raw)) + if options.strictSlash { + pattern.WriteString("[/]?") + } + if typ == regexpTypeQuery { + // Add the default pattern if the query value is empty + if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { + pattern.WriteString(defaultPattern) + } + } + if typ != regexpTypePrefix { + pattern.WriteByte('$') + } + + var wildcardHostPort bool + if typ == regexpTypeHost { + if !strings.Contains(pattern.String(), ":") { + wildcardHostPort = true + } + } + reverse.WriteString(raw) + if endSlash { + reverse.WriteByte('/') + } + // Compile full regexp. + reg, errCompile := regexp.Compile(pattern.String()) + if errCompile != nil { + return nil, errCompile + } + + // Check for capturing groups which used to work in older versions + if reg.NumSubexp() != len(idxs)/2 { + panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + + "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") + } + + // Done! + return &routeRegexp{ + template: template, + regexpType: typ, + options: options, + regexp: reg, + reverse: reverse.String(), + varsN: varsN, + varsR: varsR, + wildcardHostPort: wildcardHostPort, + }, nil +} + +// routeRegexp stores a regexp to match a host or path and information to +// collect and validate route variables. +type routeRegexp struct { + // The unmodified template. + template string + // The type of match + regexpType regexpType + // Options for matching + options routeRegexpOptions + // Expanded regexp. + regexp *regexp.Regexp + // Reverse template. + reverse string + // Variable names. + varsN []string + // Variable regexps (validators). + varsR []*regexp.Regexp + // Wildcard host-port (no strict port match in hostname) + wildcardHostPort bool +} + +// Match matches the regexp against the URL host or path. +func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { + if r.regexpType == regexpTypeHost { + host := getHost(req) + if r.wildcardHostPort { + // Don't be strict on the port match + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + } + return r.regexp.MatchString(host) + } + + if r.regexpType == regexpTypeQuery { + return r.matchQueryString(req) + } + path := req.URL.Path + if r.options.useEncodedPath { + path = req.URL.EscapedPath() + } + return r.regexp.MatchString(path) +} + +// url builds a URL part using the given values. +func (r *routeRegexp) url(values map[string]string) (string, error) { + urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) + for k, v := range r.varsN { + value, ok := values[v] + if !ok { + return "", fmt.Errorf("mux: missing route variable %q", v) + } + if r.regexpType == regexpTypeQuery { + value = url.QueryEscape(value) + } + urlValues[k] = value + } + rv := fmt.Sprintf(r.reverse, urlValues...) + if !r.regexp.MatchString(rv) { + // The URL is checked against the full regexp, instead of checking + // individual variables. This is faster but to provide a good error + // message, we check individual regexps if the URL doesn't match. + for k, v := range r.varsN { + if !r.varsR[k].MatchString(values[v]) { + return "", fmt.Errorf( + "mux: variable %q doesn't match, expected %q", values[v], + r.varsR[k].String()) + } + } + } + return rv, nil +} + +// getURLQuery returns a single query parameter from a request URL. +// For a URL with foo=bar&baz=ding, we return only the relevant key +// value pair for the routeRegexp. +func (r *routeRegexp) getURLQuery(req *http.Request) string { + if r.regexpType != regexpTypeQuery { + return "" + } + templateKey := strings.SplitN(r.template, "=", 2)[0] + val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey) + if ok { + return templateKey + "=" + val + } + return "" +} + +// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0]. +// If key was not found, empty string and false is returned. +func findFirstQueryKey(rawQuery, key string) (value string, ok bool) { + query := []byte(rawQuery) + for len(query) > 0 { + foundKey := query + if i := bytes.IndexAny(foundKey, "&;"); i >= 0 { + foundKey, query = foundKey[:i], foundKey[i+1:] + } else { + query = query[:0] + } + if len(foundKey) == 0 { + continue + } + var value []byte + if i := bytes.IndexByte(foundKey, '='); i >= 0 { + foundKey, value = foundKey[:i], foundKey[i+1:] + } + if len(foundKey) < len(key) { + // Cannot possibly be key. + continue + } + keyString, err := url.QueryUnescape(string(foundKey)) + if err != nil { + continue + } + if keyString != key { + continue + } + valueString, err := url.QueryUnescape(string(value)) + if err != nil { + continue + } + return valueString, true + } + return "", false +} + +func (r *routeRegexp) matchQueryString(req *http.Request) bool { + return r.regexp.MatchString(r.getURLQuery(req)) +} + +// braceIndices returns the first level curly brace indices from a string. +// It returns an error in case of unbalanced braces. +func braceIndices(s string) ([]int, error) { + var level, idx int + var idxs []int + for i := 0; i < len(s); i++ { + switch s[i] { + case '{': + if level++; level == 1 { + idx = i + } + case '}': + if level--; level == 0 { + idxs = append(idxs, idx, i+1) + } else if level < 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + } + } + if level != 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + return idxs, nil +} + +// varGroupName builds a capturing group name for the indexed variable. +func varGroupName(idx int) string { + return "v" + strconv.Itoa(idx) +} + +// ---------------------------------------------------------------------------- +// routeRegexpGroup +// ---------------------------------------------------------------------------- + +// routeRegexpGroup groups the route matchers that carry variables. +type routeRegexpGroup struct { + host *routeRegexp + path *routeRegexp + queries []*routeRegexp +} + +// setMatch extracts the variables from the URL once a route matches. +func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { + // Store host variables. + if v.host != nil { + host := getHost(req) + if v.host.wildcardHostPort { + // Don't be strict on the port match + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + } + matches := v.host.regexp.FindStringSubmatchIndex(host) + if len(matches) > 0 { + extractVars(host, matches, v.host.varsN, m.Vars) + } + } + path := req.URL.Path + if r.useEncodedPath { + path = req.URL.EscapedPath() + } + // Store path variables. + if v.path != nil { + matches := v.path.regexp.FindStringSubmatchIndex(path) + if len(matches) > 0 { + extractVars(path, matches, v.path.varsN, m.Vars) + // Check if we should redirect. + if v.path.options.strictSlash { + p1 := strings.HasSuffix(path, "/") + p2 := strings.HasSuffix(v.path.template, "/") + if p1 != p2 { + u, _ := url.Parse(req.URL.String()) + if p1 { + u.Path = u.Path[:len(u.Path)-1] + } else { + u.Path += "/" + } + m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently) + } + } + } + } + // Store query string variables. + for _, q := range v.queries { + queryURL := q.getURLQuery(req) + matches := q.regexp.FindStringSubmatchIndex(queryURL) + if len(matches) > 0 { + extractVars(queryURL, matches, q.varsN, m.Vars) + } + } +} + +// getHost tries its best to return the request host. +// According to section 14.23 of RFC 2616 the Host header +// can include the port number if the default value of 80 is not used. +func getHost(r *http.Request) string { + if r.URL.IsAbs() { + return r.URL.Host + } + return r.Host +} + +func extractVars(input string, matches []int, names []string, output map[string]string) { + for i, name := range names { + output[name] = input[matches[2*i+2]:matches[2*i+3]] + } +} diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go new file mode 100644 index 0000000000..750afe570d --- /dev/null +++ b/vendor/github.com/gorilla/mux/route.go @@ -0,0 +1,736 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" +) + +// Route stores information to match a request and build URLs. +type Route struct { + // Request handler for the route. + handler http.Handler + // If true, this route never matches: it is only used to build URLs. + buildOnly bool + // The name used to build URLs. + name string + // Error resulted from building a route. + err error + + // "global" reference to all named routes + namedRoutes map[string]*Route + + // config possibly passed in from `Router` + routeConf +} + +// SkipClean reports whether path cleaning is enabled for this route via +// Router.SkipClean. +func (r *Route) SkipClean() bool { + return r.skipClean +} + +// Match matches the route against the request. +func (r *Route) Match(req *http.Request, match *RouteMatch) bool { + if r.buildOnly || r.err != nil { + return false + } + + var matchErr error + + // Match everything. + for _, m := range r.matchers { + if matched := m.Match(req, match); !matched { + if _, ok := m.(methodMatcher); ok { + matchErr = ErrMethodMismatch + continue + } + + // Ignore ErrNotFound errors. These errors arise from match call + // to Subrouters. + // + // This prevents subsequent matching subrouters from failing to + // run middleware. If not ignored, the middleware would see a + // non-nil MatchErr and be skipped, even when there was a + // matching route. + if match.MatchErr == ErrNotFound { + match.MatchErr = nil + } + + matchErr = nil + return false + } + } + + if matchErr != nil { + match.MatchErr = matchErr + return false + } + + if match.MatchErr == ErrMethodMismatch && r.handler != nil { + // We found a route which matches request method, clear MatchErr + match.MatchErr = nil + // Then override the mis-matched handler + match.Handler = r.handler + } + + // Yay, we have a match. Let's collect some info about it. + if match.Route == nil { + match.Route = r + } + if match.Handler == nil { + match.Handler = r.handler + } + if match.Vars == nil { + match.Vars = make(map[string]string) + } + + // Set variables. + r.regexp.setMatch(req, match, r) + return true +} + +// ---------------------------------------------------------------------------- +// Route attributes +// ---------------------------------------------------------------------------- + +// GetError returns an error resulted from building the route, if any. +func (r *Route) GetError() error { + return r.err +} + +// BuildOnly sets the route to never match: it is only used to build URLs. +func (r *Route) BuildOnly() *Route { + r.buildOnly = true + return r +} + +// Handler -------------------------------------------------------------------- + +// Handler sets a handler for the route. +func (r *Route) Handler(handler http.Handler) *Route { + if r.err == nil { + r.handler = handler + } + return r +} + +// HandlerFunc sets a handler function for the route. +func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { + return r.Handler(http.HandlerFunc(f)) +} + +// GetHandler returns the handler for the route, if any. +func (r *Route) GetHandler() http.Handler { + return r.handler +} + +// Name ----------------------------------------------------------------------- + +// Name sets the name for the route, used to build URLs. +// It is an error to call Name more than once on a route. +func (r *Route) Name(name string) *Route { + if r.name != "" { + r.err = fmt.Errorf("mux: route already has name %q, can't set %q", + r.name, name) + } + if r.err == nil { + r.name = name + r.namedRoutes[name] = r + } + return r +} + +// GetName returns the name for the route, if any. +func (r *Route) GetName() string { + return r.name +} + +// ---------------------------------------------------------------------------- +// Matchers +// ---------------------------------------------------------------------------- + +// matcher types try to match a request. +type matcher interface { + Match(*http.Request, *RouteMatch) bool +} + +// addMatcher adds a matcher to the route. +func (r *Route) addMatcher(m matcher) *Route { + if r.err == nil { + r.matchers = append(r.matchers, m) + } + return r +} + +// addRegexpMatcher adds a host or path matcher and builder to a route. +func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { + if r.err != nil { + return r.err + } + if typ == regexpTypePath || typ == regexpTypePrefix { + if len(tpl) > 0 && tpl[0] != '/' { + return fmt.Errorf("mux: path must start with a slash, got %q", tpl) + } + if r.regexp.path != nil { + tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl + } + } + rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ + strictSlash: r.strictSlash, + useEncodedPath: r.useEncodedPath, + }) + if err != nil { + return err + } + for _, q := range r.regexp.queries { + if err = uniqueVars(rr.varsN, q.varsN); err != nil { + return err + } + } + if typ == regexpTypeHost { + if r.regexp.path != nil { + if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { + return err + } + } + r.regexp.host = rr + } else { + if r.regexp.host != nil { + if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { + return err + } + } + if typ == regexpTypeQuery { + r.regexp.queries = append(r.regexp.queries, rr) + } else { + r.regexp.path = rr + } + } + r.addMatcher(rr) + return nil +} + +// Headers -------------------------------------------------------------------- + +// headerMatcher matches the request against header values. +type headerMatcher map[string]string + +func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchMapWithString(m, r.Header, true) +} + +// Headers adds a matcher for request header values. +// It accepts a sequence of key/value pairs to be matched. For example: +// +// r := mux.NewRouter() +// r.Headers("Content-Type", "application/json", +// "X-Requested-With", "XMLHttpRequest") +// +// The above route will only match if both request header values match. +// If the value is an empty string, it will match any value if the key is set. +func (r *Route) Headers(pairs ...string) *Route { + if r.err == nil { + var headers map[string]string + headers, r.err = mapFromPairsToString(pairs...) + return r.addMatcher(headerMatcher(headers)) + } + return r +} + +// headerRegexMatcher matches the request against the route given a regex for the header +type headerRegexMatcher map[string]*regexp.Regexp + +func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchMapWithRegex(m, r.Header, true) +} + +// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex +// support. For example: +// +// r := mux.NewRouter() +// r.HeadersRegexp("Content-Type", "application/(text|json)", +// "X-Requested-With", "XMLHttpRequest") +// +// The above route will only match if both the request header matches both regular expressions. +// If the value is an empty string, it will match any value if the key is set. +// Use the start and end of string anchors (^ and $) to match an exact value. +func (r *Route) HeadersRegexp(pairs ...string) *Route { + if r.err == nil { + var headers map[string]*regexp.Regexp + headers, r.err = mapFromPairsToRegex(pairs...) + return r.addMatcher(headerRegexMatcher(headers)) + } + return r +} + +// Host ----------------------------------------------------------------------- + +// Host adds a matcher for the URL host. +// It accepts a template with zero or more URL variables enclosed by {}. +// Variables can define an optional regexp pattern to be matched: +// +// - {name} matches anything until the next dot. +// +// - {name:pattern} matches the given regexp pattern. +// +// For example: +// +// r := mux.NewRouter() +// r.Host("www.example.com") +// r.Host("{subdomain}.domain.com") +// r.Host("{subdomain:[a-z]+}.domain.com") +// +// Variable names must be unique in a given route. They can be retrieved +// calling mux.Vars(request). +func (r *Route) Host(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, regexpTypeHost) + return r +} + +// MatcherFunc ---------------------------------------------------------------- + +// MatcherFunc is the function signature used by custom matchers. +type MatcherFunc func(*http.Request, *RouteMatch) bool + +// Match returns the match for a given request. +func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { + return m(r, match) +} + +// MatcherFunc adds a custom function to be used as request matcher. +func (r *Route) MatcherFunc(f MatcherFunc) *Route { + return r.addMatcher(f) +} + +// Methods -------------------------------------------------------------------- + +// methodMatcher matches the request against HTTP methods. +type methodMatcher []string + +func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchInArray(m, r.Method) +} + +// Methods adds a matcher for HTTP methods. +// It accepts a sequence of one or more methods to be matched, e.g.: +// "GET", "POST", "PUT". +func (r *Route) Methods(methods ...string) *Route { + for k, v := range methods { + methods[k] = strings.ToUpper(v) + } + return r.addMatcher(methodMatcher(methods)) +} + +// Path ----------------------------------------------------------------------- + +// Path adds a matcher for the URL path. +// It accepts a template with zero or more URL variables enclosed by {}. The +// template must start with a "/". +// Variables can define an optional regexp pattern to be matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. +// +// For example: +// +// r := mux.NewRouter() +// r.Path("/products/").Handler(ProductsHandler) +// r.Path("/products/{key}").Handler(ProductsHandler) +// r.Path("/articles/{category}/{id:[0-9]+}"). +// Handler(ArticleHandler) +// +// Variable names must be unique in a given route. They can be retrieved +// calling mux.Vars(request). +func (r *Route) Path(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, regexpTypePath) + return r +} + +// PathPrefix ----------------------------------------------------------------- + +// PathPrefix adds a matcher for the URL path prefix. This matches if the given +// template is a prefix of the full URL path. See Route.Path() for details on +// the tpl argument. +// +// Note that it does not treat slashes specially ("/foobar/" will be matched by +// the prefix "/foo") so you may want to use a trailing slash here. +// +// Also note that the setting of Router.StrictSlash() has no effect on routes +// with a PathPrefix matcher. +func (r *Route) PathPrefix(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, regexpTypePrefix) + return r +} + +// Query ---------------------------------------------------------------------- + +// Queries adds a matcher for URL query values. +// It accepts a sequence of key/value pairs. Values may define variables. +// For example: +// +// r := mux.NewRouter() +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") +// +// The above route will only match if the URL contains the defined queries +// values, e.g.: ?foo=bar&id=42. +// +// If the value is an empty string, it will match any value if the key is set. +// +// Variables can define an optional regexp pattern to be matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. +func (r *Route) Queries(pairs ...string) *Route { + length := len(pairs) + if length%2 != 0 { + r.err = fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + return nil + } + for i := 0; i < length; i += 2 { + if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil { + return r + } + } + + return r +} + +// Schemes -------------------------------------------------------------------- + +// schemeMatcher matches the request against URL schemes. +type schemeMatcher []string + +func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { + scheme := r.URL.Scheme + // https://golang.org/pkg/net/http/#Request + // "For [most] server requests, fields other than Path and RawQuery will be + // empty." + // Since we're an http muxer, the scheme is either going to be http or https + // though, so we can just set it based on the tls termination state. + if scheme == "" { + if r.TLS == nil { + scheme = "http" + } else { + scheme = "https" + } + } + return matchInArray(m, scheme) +} + +// Schemes adds a matcher for URL schemes. +// It accepts a sequence of schemes to be matched, e.g.: "http", "https". +// If the request's URL has a scheme set, it will be matched against. +// Generally, the URL scheme will only be set if a previous handler set it, +// such as the ProxyHeaders handler from gorilla/handlers. +// If unset, the scheme will be determined based on the request's TLS +// termination state. +// The first argument to Schemes will be used when constructing a route URL. +func (r *Route) Schemes(schemes ...string) *Route { + for k, v := range schemes { + schemes[k] = strings.ToLower(v) + } + if len(schemes) > 0 { + r.buildScheme = schemes[0] + } + return r.addMatcher(schemeMatcher(schemes)) +} + +// BuildVarsFunc -------------------------------------------------------------- + +// BuildVarsFunc is the function signature used by custom build variable +// functions (which can modify route variables before a route's URL is built). +type BuildVarsFunc func(map[string]string) map[string]string + +// BuildVarsFunc adds a custom function to be used to modify build variables +// before a route's URL is built. +func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { + if r.buildVarsFunc != nil { + // compose the old and new functions + old := r.buildVarsFunc + r.buildVarsFunc = func(m map[string]string) map[string]string { + return f(old(m)) + } + } else { + r.buildVarsFunc = f + } + return r +} + +// Subrouter ------------------------------------------------------------------ + +// Subrouter creates a subrouter for the route. +// +// It will test the inner routes only if the parent route matched. For example: +// +// r := mux.NewRouter() +// s := r.Host("www.example.com").Subrouter() +// s.HandleFunc("/products/", ProductsHandler) +// s.HandleFunc("/products/{key}", ProductHandler) +// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) +// +// Here, the routes registered in the subrouter won't be tested if the host +// doesn't match. +func (r *Route) Subrouter() *Router { + // initialize a subrouter with a copy of the parent route's configuration + router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} + r.addMatcher(router) + return router +} + +// ---------------------------------------------------------------------------- +// URL building +// ---------------------------------------------------------------------------- + +// URL builds a URL for the route. +// +// It accepts a sequence of key/value pairs for the route variables. For +// example, given this route: +// +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") +// +// ...a URL for it can be built using: +// +// url, err := r.Get("article").URL("category", "technology", "id", "42") +// +// ...which will return an url.URL with the following path: +// +// "/articles/technology/42" +// +// This also works for host variables: +// +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Host("{subdomain}.domain.com"). +// Name("article") +// +// // url.String() will be "http://news.domain.com/articles/technology/42" +// url, err := r.Get("article").URL("subdomain", "news", +// "category", "technology", +// "id", "42") +// +// The scheme of the resulting url will be the first argument that was passed to Schemes: +// +// // url.String() will be "https://example.com" +// r := mux.NewRouter() +// url, err := r.Host("example.com") +// .Schemes("https", "http").URL() +// +// All variables defined in the route are required, and their values must +// conform to the corresponding patterns. +func (r *Route) URL(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + values, err := r.prepareVars(pairs...) + if err != nil { + return nil, err + } + var scheme, host, path string + queries := make([]string, 0, len(r.regexp.queries)) + if r.regexp.host != nil { + if host, err = r.regexp.host.url(values); err != nil { + return nil, err + } + scheme = "http" + if r.buildScheme != "" { + scheme = r.buildScheme + } + } + if r.regexp.path != nil { + if path, err = r.regexp.path.url(values); err != nil { + return nil, err + } + } + for _, q := range r.regexp.queries { + var query string + if query, err = q.url(values); err != nil { + return nil, err + } + queries = append(queries, query) + } + return &url.URL{ + Scheme: scheme, + Host: host, + Path: path, + RawQuery: strings.Join(queries, "&"), + }, nil +} + +// URLHost builds the host part of the URL for a route. See Route.URL(). +// +// The route must have a host defined. +func (r *Route) URLHost(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp.host == nil { + return nil, errors.New("mux: route doesn't have a host") + } + values, err := r.prepareVars(pairs...) + if err != nil { + return nil, err + } + host, err := r.regexp.host.url(values) + if err != nil { + return nil, err + } + u := &url.URL{ + Scheme: "http", + Host: host, + } + if r.buildScheme != "" { + u.Scheme = r.buildScheme + } + return u, nil +} + +// URLPath builds the path part of the URL for a route. See Route.URL(). +// +// The route must have a path defined. +func (r *Route) URLPath(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp.path == nil { + return nil, errors.New("mux: route doesn't have a path") + } + values, err := r.prepareVars(pairs...) + if err != nil { + return nil, err + } + path, err := r.regexp.path.url(values) + if err != nil { + return nil, err + } + return &url.URL{ + Path: path, + }, nil +} + +// GetPathTemplate returns the template used to build the +// route match. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define a path. +func (r *Route) GetPathTemplate() (string, error) { + if r.err != nil { + return "", r.err + } + if r.regexp.path == nil { + return "", errors.New("mux: route doesn't have a path") + } + return r.regexp.path.template, nil +} + +// GetPathRegexp returns the expanded regular expression used to match route path. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define a path. +func (r *Route) GetPathRegexp() (string, error) { + if r.err != nil { + return "", r.err + } + if r.regexp.path == nil { + return "", errors.New("mux: route does not have a path") + } + return r.regexp.path.regexp.String(), nil +} + +// GetQueriesRegexp returns the expanded regular expressions used to match the +// route queries. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not have queries. +func (r *Route) GetQueriesRegexp() ([]string, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp.queries == nil { + return nil, errors.New("mux: route doesn't have queries") + } + queries := make([]string, 0, len(r.regexp.queries)) + for _, query := range r.regexp.queries { + queries = append(queries, query.regexp.String()) + } + return queries, nil +} + +// GetQueriesTemplates returns the templates used to build the +// query matching. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define queries. +func (r *Route) GetQueriesTemplates() ([]string, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp.queries == nil { + return nil, errors.New("mux: route doesn't have queries") + } + queries := make([]string, 0, len(r.regexp.queries)) + for _, query := range r.regexp.queries { + queries = append(queries, query.template) + } + return queries, nil +} + +// GetMethods returns the methods the route matches against +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if route does not have methods. +func (r *Route) GetMethods() ([]string, error) { + if r.err != nil { + return nil, r.err + } + for _, m := range r.matchers { + if methods, ok := m.(methodMatcher); ok { + return []string(methods), nil + } + } + return nil, errors.New("mux: route doesn't have methods") +} + +// GetHostTemplate returns the template used to build the +// route match. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define a host. +func (r *Route) GetHostTemplate() (string, error) { + if r.err != nil { + return "", r.err + } + if r.regexp.host == nil { + return "", errors.New("mux: route doesn't have a host") + } + return r.regexp.host.template, nil +} + +// prepareVars converts the route variable pairs into a map. If the route has a +// BuildVarsFunc, it is invoked. +func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { + m, err := mapFromPairsToString(pairs...) + if err != nil { + return nil, err + } + return r.buildVars(m), nil +} + +func (r *Route) buildVars(m map[string]string) map[string]string { + if r.buildVarsFunc != nil { + m = r.buildVarsFunc(m) + } + return m +} diff --git a/vendor/github.com/gorilla/mux/test_helpers.go b/vendor/github.com/gorilla/mux/test_helpers.go new file mode 100644 index 0000000000..5f5c496de0 --- /dev/null +++ b/vendor/github.com/gorilla/mux/test_helpers.go @@ -0,0 +1,19 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import "net/http" + +// SetURLVars sets the URL variables for the given request, to be accessed via +// mux.Vars for testing route behaviour. Arguments are not modified, a shallow +// copy is returned. +// +// This API should only be used for testing purposes; it provides a way to +// inject variables into the request context. Alternatively, URL variables +// can be set by making a route that captures the required variables, +// starting a server and sending the request to that server. +func SetURLVars(r *http.Request, val map[string]string) *http.Request { + return requestWithVars(r, val) +} diff --git a/vendor/golang.org/x/net/publicsuffix/list.go b/vendor/golang.org/x/net/publicsuffix/list.go new file mode 100644 index 0000000000..200617ea86 --- /dev/null +++ b/vendor/golang.org/x/net/publicsuffix/list.go @@ -0,0 +1,181 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go + +// Package publicsuffix provides a public suffix list based on data from +// https://publicsuffix.org/ +// +// A public suffix is one under which Internet users can directly register +// names. It is related to, but different from, a TLD (top level domain). +// +// "com" is a TLD (top level domain). Top level means it has no dots. +// +// "com" is also a public suffix. Amazon and Google have registered different +// siblings under that domain: "amazon.com" and "google.com". +// +// "au" is another TLD, again because it has no dots. But it's not "amazon.au". +// Instead, it's "amazon.com.au". +// +// "com.au" isn't an actual TLD, because it's not at the top level (it has +// dots). But it is an eTLD (effective TLD), because that's the branching point +// for domain name registrars. +// +// Another name for "an eTLD" is "a public suffix". Often, what's more of +// interest is the eTLD+1, or one more label than the public suffix. For +// example, browsers partition read/write access to HTTP cookies according to +// the eTLD+1. Web pages served from "amazon.com.au" can't read cookies from +// "google.com.au", but web pages served from "maps.google.com" can share +// cookies from "www.google.com", so you don't have to sign into Google Maps +// separately from signing into Google Web Search. Note that all four of those +// domains have 3 labels and 2 dots. The first two domains are each an eTLD+1, +// the last two are not (but share the same eTLD+1: "google.com"). +// +// All of these domains have the same eTLD+1: +// - "www.books.amazon.co.uk" +// - "books.amazon.co.uk" +// - "amazon.co.uk" +// Specifically, the eTLD+1 is "amazon.co.uk", because the eTLD is "co.uk". +// +// There is no closed form algorithm to calculate the eTLD of a domain. +// Instead, the calculation is data driven. This package provides a +// pre-compiled snapshot of Mozilla's PSL (Public Suffix List) data at +// https://publicsuffix.org/ +package publicsuffix // import "golang.org/x/net/publicsuffix" + +// TODO: specify case sensitivity and leading/trailing dot behavior for +// func PublicSuffix and func EffectiveTLDPlusOne. + +import ( + "fmt" + "net/http/cookiejar" + "strings" +) + +// List implements the cookiejar.PublicSuffixList interface by calling the +// PublicSuffix function. +var List cookiejar.PublicSuffixList = list{} + +type list struct{} + +func (list) PublicSuffix(domain string) string { + ps, _ := PublicSuffix(domain) + return ps +} + +func (list) String() string { + return version +} + +// PublicSuffix returns the public suffix of the domain using a copy of the +// publicsuffix.org database compiled into the library. +// +// icann is whether the public suffix is managed by the Internet Corporation +// for Assigned Names and Numbers. If not, the public suffix is either a +// privately managed domain (and in practice, not a top level domain) or an +// unmanaged top level domain (and not explicitly mentioned in the +// publicsuffix.org list). For example, "foo.org" and "foo.co.uk" are ICANN +// domains, "foo.dyndns.org" and "foo.blogspot.co.uk" are private domains and +// "cromulent" is an unmanaged top level domain. +// +// Use cases for distinguishing ICANN domains like "foo.com" from private +// domains like "foo.appspot.com" can be found at +// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases +func PublicSuffix(domain string) (publicSuffix string, icann bool) { + lo, hi := uint32(0), uint32(numTLD) + s, suffix, icannNode, wildcard := domain, len(domain), false, false +loop: + for { + dot := strings.LastIndex(s, ".") + if wildcard { + icann = icannNode + suffix = 1 + dot + } + if lo == hi { + break + } + f := find(s[1+dot:], lo, hi) + if f == notFound { + break + } + + u := nodes[f] >> (nodesBitsTextOffset + nodesBitsTextLength) + icannNode = u&(1<>= nodesBitsICANN + u = children[u&(1<>= childrenBitsLo + hi = u & (1<>= childrenBitsHi + switch u & (1<>= childrenBitsNodeType + wildcard = u&(1<>= nodesBitsTextLength + offset := x & (1< + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/vendor/maunium.net/go/maulogger/v2/README.md b/vendor/maunium.net/go/maulogger/v2/README.md new file mode 100644 index 0000000000..68fe253e50 --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/README.md @@ -0,0 +1,6 @@ +# maulogger +A logger in Go. + +Docs: [godoc.org/maunium.net/go/maulogger](https://godoc.org/maunium.net/go/maulogger) + +Go get: `go get maunium.net/go/maulogger` diff --git a/vendor/maunium.net/go/maulogger/v2/defaults.go b/vendor/maunium.net/go/maulogger/v2/defaults.go new file mode 100644 index 0000000000..c26905612e --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/defaults.go @@ -0,0 +1,289 @@ +// mauLogger - A logger for Go programs +// Copyright (C) 2016-2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package maulogger + +import ( + "os" +) + +// DefaultLogger ... +var DefaultLogger = Create().(*BasicLogger) + +// SetWriter formats the given parts with fmt.Sprint and logs the result with the SetWriter level +func SetWriter(w *os.File) { + DefaultLogger.SetWriter(w) +} + +// OpenFile formats the given parts with fmt.Sprint and logs the result with the OpenFile level +func OpenFile() error { + return DefaultLogger.OpenFile() +} + +// Close formats the given parts with fmt.Sprint and logs the result with the Close level +func Close() error { + return DefaultLogger.Close() +} + +// Sub creates a Sublogger +func Sub(module string) Logger { + return DefaultLogger.Sub(module) +} + +// Raw formats the given parts with fmt.Sprint and logs the result with the Raw level +func Raw(level Level, module, message string) { + DefaultLogger.Raw(level, module, message) +} + +// Log formats the given parts with fmt.Sprint and logs the result with the given level +func Log(level Level, parts ...interface{}) { + DefaultLogger.DefaultSub.Log(level, parts...) +} + +// Logln formats the given parts with fmt.Sprintln and logs the result with the given level +func Logln(level Level, parts ...interface{}) { + DefaultLogger.DefaultSub.Logln(level, parts...) +} + +// Logf formats the given message and args with fmt.Sprintf and logs the result with the given level +func Logf(level Level, message string, args ...interface{}) { + DefaultLogger.DefaultSub.Logf(level, message, args...) +} + +// Logfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the given level +func Logfln(level Level, message string, args ...interface{}) { + DefaultLogger.DefaultSub.Logfln(level, message, args...) +} + +// Debug formats the given parts with fmt.Sprint and logs the result with the Debug level +func Debug(parts ...interface{}) { + DefaultLogger.DefaultSub.Debug(parts...) +} + +// Debugln formats the given parts with fmt.Sprintln and logs the result with the Debug level +func Debugln(parts ...interface{}) { + DefaultLogger.DefaultSub.Debugln(parts...) +} + +// Debugf formats the given message and args with fmt.Sprintf and logs the result with the Debug level +func Debugf(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Debugf(message, args...) +} + +// Debugfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Debug level +func Debugfln(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Debugfln(message, args...) +} + +// Info formats the given parts with fmt.Sprint and logs the result with the Info level +func Info(parts ...interface{}) { + DefaultLogger.DefaultSub.Info(parts...) +} + +// Infoln formats the given parts with fmt.Sprintln and logs the result with the Info level +func Infoln(parts ...interface{}) { + DefaultLogger.DefaultSub.Infoln(parts...) +} + +// Infof formats the given message and args with fmt.Sprintf and logs the result with the Info level +func Infof(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Infof(message, args...) +} + +// Infofln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Info level +func Infofln(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Infofln(message, args...) +} + +// Warn formats the given parts with fmt.Sprint and logs the result with the Warn level +func Warn(parts ...interface{}) { + DefaultLogger.DefaultSub.Warn(parts...) +} + +// Warnln formats the given parts with fmt.Sprintln and logs the result with the Warn level +func Warnln(parts ...interface{}) { + DefaultLogger.DefaultSub.Warnln(parts...) +} + +// Warnf formats the given message and args with fmt.Sprintf and logs the result with the Warn level +func Warnf(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Warnf(message, args...) +} + +// Warnfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Warn level +func Warnfln(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Warnfln(message, args...) +} + +// Error formats the given parts with fmt.Sprint and logs the result with the Error level +func Error(parts ...interface{}) { + DefaultLogger.DefaultSub.Error(parts...) +} + +// Errorln formats the given parts with fmt.Sprintln and logs the result with the Error level +func Errorln(parts ...interface{}) { + DefaultLogger.DefaultSub.Errorln(parts...) +} + +// Errorf formats the given message and args with fmt.Sprintf and logs the result with the Error level +func Errorf(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Errorf(message, args...) +} + +// Errorfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Error level +func Errorfln(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Errorfln(message, args...) +} + +// Fatal formats the given parts with fmt.Sprint and logs the result with the Fatal level +func Fatal(parts ...interface{}) { + DefaultLogger.DefaultSub.Fatal(parts...) +} + +// Fatalln formats the given parts with fmt.Sprintln and logs the result with the Fatal level +func Fatalln(parts ...interface{}) { + DefaultLogger.DefaultSub.Fatalln(parts...) +} + +// Fatalf formats the given message and args with fmt.Sprintf and logs the result with the Fatal level +func Fatalf(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Fatalf(message, args...) +} + +// Fatalfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Fatal level +func Fatalfln(message string, args ...interface{}) { + DefaultLogger.DefaultSub.Fatalfln(message, args...) +} + +// Log formats the given parts with fmt.Sprint and logs the result with the given level +func (log *BasicLogger) Log(level Level, parts ...interface{}) { + log.DefaultSub.Log(level, parts...) +} + +// Logln formats the given parts with fmt.Sprintln and logs the result with the given level +func (log *BasicLogger) Logln(level Level, parts ...interface{}) { + log.DefaultSub.Logln(level, parts...) +} + +// Logf formats the given message and args with fmt.Sprintf and logs the result with the given level +func (log *BasicLogger) Logf(level Level, message string, args ...interface{}) { + log.DefaultSub.Logf(level, message, args...) +} + +// Logfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the given level +func (log *BasicLogger) Logfln(level Level, message string, args ...interface{}) { + log.DefaultSub.Logfln(level, message, args...) +} + +// Debug formats the given parts with fmt.Sprint and logs the result with the Debug level +func (log *BasicLogger) Debug(parts ...interface{}) { + log.DefaultSub.Debug(parts...) +} + +// Debugln formats the given parts with fmt.Sprintln and logs the result with the Debug level +func (log *BasicLogger) Debugln(parts ...interface{}) { + log.DefaultSub.Debugln(parts...) +} + +// Debugf formats the given message and args with fmt.Sprintf and logs the result with the Debug level +func (log *BasicLogger) Debugf(message string, args ...interface{}) { + log.DefaultSub.Debugf(message, args...) +} + +// Debugfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Debug level +func (log *BasicLogger) Debugfln(message string, args ...interface{}) { + log.DefaultSub.Debugfln(message, args...) +} + +// Info formats the given parts with fmt.Sprint and logs the result with the Info level +func (log *BasicLogger) Info(parts ...interface{}) { + log.DefaultSub.Info(parts...) +} + +// Infoln formats the given parts with fmt.Sprintln and logs the result with the Info level +func (log *BasicLogger) Infoln(parts ...interface{}) { + log.DefaultSub.Infoln(parts...) +} + +// Infofln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Info level +func (log *BasicLogger) Infofln(message string, args ...interface{}) { + log.DefaultSub.Infofln(message, args...) +} + +// Infof formats the given message and args with fmt.Sprintf and logs the result with the Info level +func (log *BasicLogger) Infof(message string, args ...interface{}) { + log.DefaultSub.Infof(message, args...) +} + +// Warn formats the given parts with fmt.Sprint and logs the result with the Warn level +func (log *BasicLogger) Warn(parts ...interface{}) { + log.DefaultSub.Warn(parts...) +} + +// Warnln formats the given parts with fmt.Sprintln and logs the result with the Warn level +func (log *BasicLogger) Warnln(parts ...interface{}) { + log.DefaultSub.Warnln(parts...) +} + +// Warnfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Warn level +func (log *BasicLogger) Warnfln(message string, args ...interface{}) { + log.DefaultSub.Warnfln(message, args...) +} + +// Warnf formats the given message and args with fmt.Sprintf and logs the result with the Warn level +func (log *BasicLogger) Warnf(message string, args ...interface{}) { + log.DefaultSub.Warnf(message, args...) +} + +// Error formats the given parts with fmt.Sprint and logs the result with the Error level +func (log *BasicLogger) Error(parts ...interface{}) { + log.DefaultSub.Error(parts...) +} + +// Errorln formats the given parts with fmt.Sprintln and logs the result with the Error level +func (log *BasicLogger) Errorln(parts ...interface{}) { + log.DefaultSub.Errorln(parts...) +} + +// Errorf formats the given message and args with fmt.Sprintf and logs the result with the Error level +func (log *BasicLogger) Errorf(message string, args ...interface{}) { + log.DefaultSub.Errorf(message, args...) +} + +// Errorfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Error level +func (log *BasicLogger) Errorfln(message string, args ...interface{}) { + log.DefaultSub.Errorfln(message, args...) +} + +// Fatal formats the given parts with fmt.Sprint and logs the result with the Fatal level +func (log *BasicLogger) Fatal(parts ...interface{}) { + log.DefaultSub.Fatal(parts...) +} + +// Fatalln formats the given parts with fmt.Sprintln and logs the result with the Fatal level +func (log *BasicLogger) Fatalln(parts ...interface{}) { + log.DefaultSub.Fatalln(parts...) +} + +// Fatalf formats the given message and args with fmt.Sprintf and logs the result with the Fatal level +func (log *BasicLogger) Fatalf(message string, args ...interface{}) { + log.DefaultSub.Fatalf(message, args...) +} + +// Fatalfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Fatal level +func (log *BasicLogger) Fatalfln(message string, args ...interface{}) { + log.DefaultSub.Fatalfln(message, args...) +} diff --git a/vendor/maunium.net/go/maulogger/v2/go.mod b/vendor/maunium.net/go/maulogger/v2/go.mod new file mode 100644 index 0000000000..ace177e4df --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/go.mod @@ -0,0 +1,3 @@ +module maunium.net/go/maulogger/v2 + +go 1.11 diff --git a/vendor/maunium.net/go/maulogger/v2/level.go b/vendor/maunium.net/go/maulogger/v2/level.go new file mode 100644 index 0000000000..498d6763c0 --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/level.go @@ -0,0 +1,56 @@ +// mauLogger - A logger for Go programs +// Copyright (C) 2016-2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package maulogger + +import ( + "fmt" +) + +// Level is the severity level of a log entry. +type Level struct { + Name string + Severity, Color int +} + +var ( + // LevelDebug is the level for debug messages. + LevelDebug = Level{Name: "DEBUG", Color: -1, Severity: 0} + // LevelInfo is the level for basic log messages. + LevelInfo = Level{Name: "INFO", Color: 36, Severity: 10} + // LevelWarn is the level saying that something went wrong, but the program will continue operating mostly normally. + LevelWarn = Level{Name: "WARN", Color: 33, Severity: 50} + // LevelError is the level saying that something went wrong and the program may not operate as expected, but will still continue. + LevelError = Level{Name: "ERROR", Color: 31, Severity: 100} + // LevelFatal is the level saying that something went wrong and the program will not operate normally. + LevelFatal = Level{Name: "FATAL", Color: 35, Severity: 9001} +) + +// GetColor gets the ANSI escape color code for the log level. +func (lvl Level) GetColor() string { + if lvl.Color < 0 { + return "\x1b[0m" + } + return fmt.Sprintf("\x1b[%dm", lvl.Color) +} + +// GetReset gets the ANSI escape reset code. +func (lvl Level) GetReset() string { + if lvl.Color < 0 { + return "" + } + return "\x1b[0m" +} diff --git a/vendor/maunium.net/go/maulogger/v2/logger.go b/vendor/maunium.net/go/maulogger/v2/logger.go new file mode 100644 index 0000000000..857007bbc2 --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/logger.go @@ -0,0 +1,212 @@ +// mauLogger - A logger for Go programs +// Copyright (C) 2016-2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package maulogger + +import ( + "encoding/json" + "fmt" + "io" + "os" + "strings" + "sync" + "time" +) + +// LoggerFileFormat ... +type LoggerFileFormat func(now string, i int) string + +type BasicLogger struct { + PrintLevel int + FlushLineThreshold int + FileTimeFormat string + FileFormat LoggerFileFormat + TimeFormat string + FileMode os.FileMode + DefaultSub Logger + + JSONFile bool + JSONStdout bool + + stdoutEncoder *json.Encoder + fileEncoder *json.Encoder + + writer *os.File + writerLock sync.Mutex + StdoutLock sync.Mutex + StderrLock sync.Mutex + lines int +} + +// Logger contains advanced logging functions +type Logger interface { + Sub(module string) Logger + WithDefaultLevel(level Level) Logger + GetParent() Logger + + Writer(level Level) io.WriteCloser + + Log(level Level, parts ...interface{}) + Logln(level Level, parts ...interface{}) + Logf(level Level, message string, args ...interface{}) + Logfln(level Level, message string, args ...interface{}) + + Debug(parts ...interface{}) + Debugln(parts ...interface{}) + Debugf(message string, args ...interface{}) + Debugfln(message string, args ...interface{}) + Info(parts ...interface{}) + Infoln(parts ...interface{}) + Infof(message string, args ...interface{}) + Infofln(message string, args ...interface{}) + Warn(parts ...interface{}) + Warnln(parts ...interface{}) + Warnf(message string, args ...interface{}) + Warnfln(message string, args ...interface{}) + Error(parts ...interface{}) + Errorln(parts ...interface{}) + Errorf(message string, args ...interface{}) + Errorfln(message string, args ...interface{}) + Fatal(parts ...interface{}) + Fatalln(parts ...interface{}) + Fatalf(message string, args ...interface{}) + Fatalfln(message string, args ...interface{}) +} + +// Create a Logger +func Create() Logger { + var log = &BasicLogger{ + PrintLevel: 10, + FileTimeFormat: "2006-01-02", + FileFormat: func(now string, i int) string { return fmt.Sprintf("%[1]s-%02[2]d.log", now, i) }, + TimeFormat: "15:04:05 02.01.2006", + FileMode: 0600, + FlushLineThreshold: 5, + lines: 0, + } + log.DefaultSub = log.Sub("") + return log +} + +func (log *BasicLogger) EnableJSONStdout() { + log.JSONStdout = true + log.stdoutEncoder = json.NewEncoder(os.Stdout) +} + +func (log *BasicLogger) GetParent() Logger { + return nil +} + +// SetWriter formats the given parts with fmt.Sprint and logs the result with the SetWriter level +func (log *BasicLogger) SetWriter(w *os.File) { + log.writer = w + if log.JSONFile { + log.fileEncoder = json.NewEncoder(w) + } +} + +// OpenFile formats the given parts with fmt.Sprint and logs the result with the OpenFile level +func (log *BasicLogger) OpenFile() error { + now := time.Now().Format(log.FileTimeFormat) + i := 1 + for ; ; i++ { + if _, err := os.Stat(log.FileFormat(now, i)); os.IsNotExist(err) { + break + } else if i == 99 { + i = 1 + break + } + } + writer, err := os.OpenFile(log.FileFormat(now, i), os.O_WRONLY|os.O_CREATE|os.O_APPEND, log.FileMode) + if err != nil { + return err + } else if writer == nil { + return os.ErrInvalid + } + log.SetWriter(writer) + return nil +} + +// Close formats the given parts with fmt.Sprint and logs the result with the Close level +func (log *BasicLogger) Close() error { + if log.writer != nil { + return log.writer.Close() + } + return nil +} + +type logLine struct { + log *BasicLogger + + Command string `json:"command"` + Time time.Time `json:"time"` + Level string `json:"level"` + Module string `json:"module"` + Message string `json:"message"` +} + +func (ll logLine) String() string { + if len(ll.Module) == 0 { + return fmt.Sprintf("[%s] [%s] %s", ll.Time.Format(ll.log.TimeFormat), ll.Level, ll.Message) + } else { + return fmt.Sprintf("[%s] [%s/%s] %s", ll.Time.Format(ll.log.TimeFormat), ll.Module, ll.Level, ll.Message) + } +} + +// Raw formats the given parts with fmt.Sprint and logs the result with the Raw level +func (log *BasicLogger) Raw(level Level, module, origMessage string) { + message := logLine{log, "log", time.Now(), level.Name, module, strings.TrimSpace(origMessage)} + + if log.writer != nil { + log.writerLock.Lock() + var err error + if log.JSONFile { + err = log.fileEncoder.Encode(&message) + } else { + _, err = log.writer.WriteString(message.String()) + _, _ = log.writer.WriteString("\n") + } + log.writerLock.Unlock() + if err != nil { + log.StderrLock.Lock() + _, _ = os.Stderr.WriteString("Failed to write to log file:") + _, _ = os.Stderr.WriteString(err.Error()) + log.StderrLock.Unlock() + } + } + + if level.Severity >= log.PrintLevel { + if log.JSONStdout { + log.StdoutLock.Lock() + _ = log.stdoutEncoder.Encode(&message) + log.StdoutLock.Unlock() + } else if level.Severity >= LevelError.Severity { + log.StderrLock.Lock() + _, _ = os.Stderr.WriteString(level.GetColor()) + _, _ = os.Stderr.WriteString(message.String()) + _, _ = os.Stderr.WriteString(level.GetReset()) + _, _ = os.Stderr.WriteString("\n") + log.StderrLock.Unlock() + } else { + log.StdoutLock.Lock() + _, _ = os.Stdout.WriteString(level.GetColor()) + _, _ = os.Stdout.WriteString(message.String()) + _, _ = os.Stdout.WriteString(level.GetReset()) + _, _ = os.Stdout.WriteString("\n") + log.StdoutLock.Unlock() + } + } +} diff --git a/vendor/maunium.net/go/maulogger/v2/sublogger.go b/vendor/maunium.net/go/maulogger/v2/sublogger.go new file mode 100644 index 0000000000..655b547c5c --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/sublogger.go @@ -0,0 +1,208 @@ +// mauLogger - A logger for Go programs +// Copyright (C) 2016-2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package maulogger + +import ( + "fmt" +) + +type Sublogger struct { + topLevel *BasicLogger + parent Logger + Module string + DefaultLevel Level +} + +// Sub creates a Sublogger +func (log *BasicLogger) Sub(module string) Logger { + return &Sublogger{ + topLevel: log, + parent: log, + Module: module, + DefaultLevel: LevelInfo, + } +} + +// WithDefaultLevel creates a Sublogger with the same Module but different DefaultLevel +func (log *BasicLogger) WithDefaultLevel(lvl Level) Logger { + return log.DefaultSub.WithDefaultLevel(lvl) +} + +func (log *Sublogger) GetParent() Logger { + return log.parent +} + +// Sub creates a Sublogger +func (log *Sublogger) Sub(module string) Logger { + return &Sublogger{ + topLevel: log.topLevel, + parent: log, + Module: fmt.Sprintf("%s/%s", log.Module, module), + DefaultLevel: log.DefaultLevel, + } +} + +// WithDefaultLevel creates a Sublogger with the same Module but different DefaultLevel +func (log *Sublogger) WithDefaultLevel(lvl Level) Logger { + return &Sublogger{ + topLevel: log.topLevel, + parent: log.parent, + Module: log.Module, + DefaultLevel: lvl, + } +} + +// SetModule changes the module name of this Sublogger +func (log *Sublogger) SetModule(mod string) { + log.Module = mod +} + +// SetDefaultLevel changes the default logging level of this Sublogger +func (log *Sublogger) SetDefaultLevel(lvl Level) { + log.DefaultLevel = lvl +} + +// SetParent changes the parent of this Sublogger +func (log *Sublogger) SetParent(parent *BasicLogger) { + log.topLevel = parent +} + +//Write ... +func (log *Sublogger) Write(p []byte) (n int, err error) { + log.topLevel.Raw(log.DefaultLevel, log.Module, string(p)) + return len(p), nil +} + +// Log formats the given parts with fmt.Sprint and logs the result with the given level +func (log *Sublogger) Log(level Level, parts ...interface{}) { + log.topLevel.Raw(level, "", fmt.Sprint(parts...)) +} + +// Logln formats the given parts with fmt.Sprintln and logs the result with the given level +func (log *Sublogger) Logln(level Level, parts ...interface{}) { + log.topLevel.Raw(level, "", fmt.Sprintln(parts...)) +} + +// Logf formats the given message and args with fmt.Sprintf and logs the result with the given level +func (log *Sublogger) Logf(level Level, message string, args ...interface{}) { + log.topLevel.Raw(level, "", fmt.Sprintf(message, args...)) +} + +// Logfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the given level +func (log *Sublogger) Logfln(level Level, message string, args ...interface{}) { + log.topLevel.Raw(level, log.Module, fmt.Sprintf(message+"\n", args...)) +} + +// Debug formats the given parts with fmt.Sprint and logs the result with the Debug level +func (log *Sublogger) Debug(parts ...interface{}) { + log.topLevel.Raw(LevelDebug, log.Module, fmt.Sprint(parts...)) +} + +// Debugln formats the given parts with fmt.Sprintln and logs the result with the Debug level +func (log *Sublogger) Debugln(parts ...interface{}) { + log.topLevel.Raw(LevelDebug, log.Module, fmt.Sprintln(parts...)) +} + +// Debugf formats the given message and args with fmt.Sprintf and logs the result with the Debug level +func (log *Sublogger) Debugf(message string, args ...interface{}) { + log.topLevel.Raw(LevelDebug, log.Module, fmt.Sprintf(message, args...)) +} + +// Debugfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Debug level +func (log *Sublogger) Debugfln(message string, args ...interface{}) { + log.topLevel.Raw(LevelDebug, log.Module, fmt.Sprintf(message+"\n", args...)) +} + +// Info formats the given parts with fmt.Sprint and logs the result with the Info level +func (log *Sublogger) Info(parts ...interface{}) { + log.topLevel.Raw(LevelInfo, log.Module, fmt.Sprint(parts...)) +} + +// Infoln formats the given parts with fmt.Sprintln and logs the result with the Info level +func (log *Sublogger) Infoln(parts ...interface{}) { + log.topLevel.Raw(LevelInfo, log.Module, fmt.Sprintln(parts...)) +} + +// Infof formats the given message and args with fmt.Sprintf and logs the result with the Info level +func (log *Sublogger) Infof(message string, args ...interface{}) { + log.topLevel.Raw(LevelInfo, log.Module, fmt.Sprintf(message, args...)) +} + +// Infofln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Info level +func (log *Sublogger) Infofln(message string, args ...interface{}) { + log.topLevel.Raw(LevelInfo, log.Module, fmt.Sprintf(message+"\n", args...)) +} + +// Warn formats the given parts with fmt.Sprint and logs the result with the Warn level +func (log *Sublogger) Warn(parts ...interface{}) { + log.topLevel.Raw(LevelWarn, log.Module, fmt.Sprint(parts...)) +} + +// Warnln formats the given parts with fmt.Sprintln and logs the result with the Warn level +func (log *Sublogger) Warnln(parts ...interface{}) { + log.topLevel.Raw(LevelWarn, log.Module, fmt.Sprintln(parts...)) +} + +// Warnf formats the given message and args with fmt.Sprintf and logs the result with the Warn level +func (log *Sublogger) Warnf(message string, args ...interface{}) { + log.topLevel.Raw(LevelWarn, log.Module, fmt.Sprintf(message, args...)) +} + +// Warnfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Warn level +func (log *Sublogger) Warnfln(message string, args ...interface{}) { + log.topLevel.Raw(LevelWarn, log.Module, fmt.Sprintf(message+"\n", args...)) +} + +// Error formats the given parts with fmt.Sprint and logs the result with the Error level +func (log *Sublogger) Error(parts ...interface{}) { + log.topLevel.Raw(LevelError, log.Module, fmt.Sprint(parts...)) +} + +// Errorln formats the given parts with fmt.Sprintln and logs the result with the Error level +func (log *Sublogger) Errorln(parts ...interface{}) { + log.topLevel.Raw(LevelError, log.Module, fmt.Sprintln(parts...)) +} + +// Errorf formats the given message and args with fmt.Sprintf and logs the result with the Error level +func (log *Sublogger) Errorf(message string, args ...interface{}) { + log.topLevel.Raw(LevelError, log.Module, fmt.Sprintf(message, args...)) +} + +// Errorfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Error level +func (log *Sublogger) Errorfln(message string, args ...interface{}) { + log.topLevel.Raw(LevelError, log.Module, fmt.Sprintf(message+"\n", args...)) +} + +// Fatal formats the given parts with fmt.Sprint and logs the result with the Fatal level +func (log *Sublogger) Fatal(parts ...interface{}) { + log.topLevel.Raw(LevelFatal, log.Module, fmt.Sprint(parts...)) +} + +// Fatalln formats the given parts with fmt.Sprintln and logs the result with the Fatal level +func (log *Sublogger) Fatalln(parts ...interface{}) { + log.topLevel.Raw(LevelFatal, log.Module, fmt.Sprintln(parts...)) +} + +// Fatalf formats the given message and args with fmt.Sprintf and logs the result with the Fatal level +func (log *Sublogger) Fatalf(message string, args ...interface{}) { + log.topLevel.Raw(LevelFatal, log.Module, fmt.Sprintf(message, args...)) +} + +// Fatalfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Fatal level +func (log *Sublogger) Fatalfln(message string, args ...interface{}) { + log.topLevel.Raw(LevelFatal, log.Module, fmt.Sprintf(message+"\n", args...)) +} diff --git a/vendor/maunium.net/go/maulogger/v2/writer.go b/vendor/maunium.net/go/maulogger/v2/writer.go new file mode 100644 index 0000000000..92fdc2007c --- /dev/null +++ b/vendor/maunium.net/go/maulogger/v2/writer.go @@ -0,0 +1,87 @@ +// mauLogger - A logger for Go programs +// Copyright (C) 2021 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package maulogger + +import ( + "bytes" + "io" + "sync" +) + +// LogWriter is a buffered io.Writer that writes lines to a Logger. +type LogWriter struct { + log Logger + lock sync.Mutex + level Level + buf bytes.Buffer +} + +func (log *BasicLogger) Writer(level Level) io.WriteCloser { + return &LogWriter{ + log: log, + level: level, + } +} + +func (log *Sublogger) Writer(level Level) io.WriteCloser { + return &LogWriter{ + log: log, + level: level, + } +} + +func (lw *LogWriter) writeLine(data []byte) { + if lw.buf.Len() == 0 { + if len(data) == 0 { + return + } + lw.log.Logln(lw.level, string(data)) + } else { + lw.buf.Write(data) + lw.log.Logln(lw.level, lw.buf.String()) + lw.buf.Reset() + } +} + +// Write will write lines from the given data to the buffer. If the data doesn't end with a line break, +// everything after the last line break will be buffered until the next Write or Close call. +func (lw *LogWriter) Write(data []byte) (int, error) { + lw.lock.Lock() + newline := bytes.IndexByte(data, '\n') + if newline == len(data)-1 { + lw.writeLine(data[:1]) + } else if newline < 0 { + lw.buf.Write(data) + } else { + lines := bytes.Split(data, []byte("\n")) + for _, line := range lines[:len(lines)-1] { + lw.writeLine(line) + } + lw.buf.Write(lines[len(lines)-1]) + } + lw.lock.Unlock() + return len(data), nil +} + +// Close will flush remaining data in the buffer into the logger. +func (lw *LogWriter) Close() error { + lw.lock.Lock() + lw.log.Logln(lw.level, lw.buf.String()) + lw.buf.Reset() + lw.lock.Unlock() + return nil +} diff --git a/vendor/maunium.net/go/mautrix/appservice/appservice.go b/vendor/maunium.net/go/mautrix/appservice/appservice.go new file mode 100644 index 0000000000..e69a65ae14 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/appservice.go @@ -0,0 +1,374 @@ +// 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 appservice + +import ( + "errors" + "fmt" + "html/template" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/gorilla/mux" + "github.com/gorilla/websocket" + "golang.org/x/net/publicsuffix" + "gopkg.in/yaml.v2" + + "maunium.net/go/maulogger/v2" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// EventChannelSize is the size for the Events channel in Appservice instances. +var EventChannelSize = 64 + +// Create a blank appservice instance. +func Create() *AppService { + jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + return &AppService{ + LogConfig: CreateLogConfig(), + clients: make(map[id.UserID]*mautrix.Client), + intents: make(map[id.UserID]*IntentAPI), + HTTPClient: &http.Client{Timeout: 180 * time.Second, Jar: jar}, + StateStore: NewBasicStateStore(), + Router: mux.NewRouter(), + UserAgent: mautrix.DefaultUserAgent, + } +} + +// Load an appservice config from a file. +func Load(path string) (*AppService, error) { + data, readErr := ioutil.ReadFile(path) + if readErr != nil { + return nil, readErr + } + + config := Create() + return config, yaml.Unmarshal(data, config) +} + +// QueryHandler handles room alias and user ID queries from the homeserver. +type QueryHandler interface { + QueryAlias(alias string) bool + QueryUser(userID id.UserID) bool +} + +type QueryHandlerStub struct{} + +func (qh *QueryHandlerStub) QueryAlias(alias string) bool { + return false +} + +func (qh *QueryHandlerStub) QueryUser(userID id.UserID) bool { + return false +} + +// AppService is the main config for all appservices. +// It also serves as the appservice instance struct. +type AppService struct { + HomeserverDomain string `yaml:"homeserver_domain"` + HomeserverURL string `yaml:"homeserver_url"` + RegistrationPath string `yaml:"registration"` + Host HostConfig `yaml:"host"` + LogConfig LogConfig `yaml:"logging"` + Sync struct { + Enabled bool `yaml:"enabled"` + FilterID string `yaml:"filter_id"` + NextBatch string `yaml:"next_batch"` + } `yaml:"sync"` + + Registration *Registration `yaml:"-"` + Log maulogger.Logger `yaml:"-"` + + lastProcessedTransaction string + + Events chan *event.Event `yaml:"-"` + QueryHandler QueryHandler `yaml:"-"` + StateStore StateStore `yaml:"-"` + + Router *mux.Router `yaml:"-"` + UserAgent string `yaml:"-"` + server *http.Server + HTTPClient *http.Client + botClient *mautrix.Client + botIntent *IntentAPI + + DefaultHTTPRetries int + + clients map[id.UserID]*mautrix.Client + clientsLock sync.RWMutex + intents map[id.UserID]*IntentAPI + intentsLock sync.RWMutex + + ws *websocket.Conn + StopWebsocket func(error) + WebsocketCommands chan WebsocketCommand +} + +func (as *AppService) PrepareWebsocket() { + if as.WebsocketCommands == nil { + as.WebsocketCommands = make(chan WebsocketCommand, 32) + } +} + +// HostConfig contains info about how to host the appservice. +type HostConfig struct { + Hostname string `yaml:"hostname"` + Port uint16 `yaml:"port"` + TLSKey string `yaml:"tls_key,omitempty"` + TLSCert string `yaml:"tls_cert,omitempty"` +} + +// Address gets the whole address of the Appservice. +func (hc *HostConfig) Address() string { + return fmt.Sprintf("%s:%d", hc.Hostname, hc.Port) +} + +// Save saves this config into a file at the given path. +func (as *AppService) Save(path string) error { + data, err := yaml.Marshal(as) + if err != nil { + return err + } + return ioutil.WriteFile(path, data, 0644) +} + +// YAML returns the config in YAML format. +func (as *AppService) YAML() (string, error) { + data, err := yaml.Marshal(as) + if err != nil { + return "", err + } + return string(data), nil +} + +func (as *AppService) BotMXID() id.UserID { + return id.NewUserID(as.Registration.SenderLocalpart, as.HomeserverDomain) +} + +func (as *AppService) makeIntent(userID id.UserID) *IntentAPI { + as.intentsLock.Lock() + defer as.intentsLock.Unlock() + + intent, ok := as.intents[userID] + if ok { + return intent + } + + localpart, homeserver, err := userID.Parse() + if err != nil || len(localpart) == 0 || homeserver != as.HomeserverDomain { + if err != nil { + as.Log.Fatalfln("Failed to parse user ID %s: %v", userID, err) + } else if len(localpart) == 0 { + as.Log.Fatalfln("Failed to make intent for %s: localpart is empty", userID) + } else if homeserver != as.HomeserverDomain { + as.Log.Fatalfln("Failed to make intent for %s: homeserver isn't %s", userID, as.HomeserverDomain) + } + return nil + } + intent = as.NewIntentAPI(localpart) + as.intents[userID] = intent + return intent +} + +func (as *AppService) Intent(userID id.UserID) *IntentAPI { + as.intentsLock.RLock() + intent, ok := as.intents[userID] + as.intentsLock.RUnlock() + if !ok { + return as.makeIntent(userID) + } + return intent +} + +func (as *AppService) BotIntent() *IntentAPI { + if as.botIntent == nil { + as.botIntent = as.makeIntent(as.BotMXID()) + } + return as.botIntent +} + +func (as *AppService) makeClient(userID id.UserID) *mautrix.Client { + as.clientsLock.Lock() + defer as.clientsLock.Unlock() + + client, ok := as.clients[userID] + if ok { + return client + } + + client, err := mautrix.NewClient(as.HomeserverURL, userID, as.Registration.AppToken) + if err != nil { + as.Log.Fatalln("Failed to create mautrix client instance:", err) + return nil + } + client.UserAgent = as.UserAgent + client.Syncer = nil + client.Store = nil + client.AppServiceUserID = userID + client.Logger = as.Log.Sub(string(userID)) + client.Client = as.HTTPClient + client.DefaultHTTPRetries = as.DefaultHTTPRetries + as.clients[userID] = client + return client +} + +func (as *AppService) Client(userID id.UserID) *mautrix.Client { + as.clientsLock.RLock() + client, ok := as.clients[userID] + as.clientsLock.RUnlock() + if !ok { + return as.makeClient(userID) + } + return client +} + +func (as *AppService) BotClient() *mautrix.Client { + if as.botClient == nil { + as.botClient = as.makeClient(as.BotMXID()) + as.botClient.Logger = as.Log.Sub("Bot") + } + return as.botClient +} + +// Init initializes the logger and loads the registration of this appservice. +func (as *AppService) Init() (bool, error) { + as.Events = make(chan *event.Event, EventChannelSize) + as.QueryHandler = &QueryHandlerStub{} + + if len(as.UserAgent) == 0 { + as.UserAgent = mautrix.DefaultUserAgent + } + + as.Log = maulogger.Create() + as.LogConfig.Configure(as.Log) + as.Log.Debugln("Logger initialized successfully.") + + if len(as.RegistrationPath) > 0 { + var err error + as.Registration, err = LoadRegistration(as.RegistrationPath) + if err != nil { + return false, err + } + } + + as.Log.Debugln("Appservice initialized successfully.") + return true, nil +} + +// LogConfig contains configs for the logger. +type LogConfig struct { + Directory string `yaml:"directory"` + FileNameFormat string `yaml:"file_name_format"` + FileDateFormat string `yaml:"file_date_format"` + FileMode uint32 `yaml:"file_mode"` + TimestampFormat string `yaml:"timestamp_format"` + RawPrintLevel string `yaml:"print_level"` + JSONStdout bool `yaml:"print_json"` + JSONFile bool `yaml:"file_json"` + PrintLevel int `yaml:"-"` +} + +type umLogConfig LogConfig + +func (lc *LogConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + err := unmarshal((*umLogConfig)(lc)) + if err != nil { + return err + } + + switch strings.ToUpper(lc.RawPrintLevel) { + case "TRACE": + lc.PrintLevel = -10 + case "DEBUG": + lc.PrintLevel = maulogger.LevelDebug.Severity + case "INFO": + lc.PrintLevel = maulogger.LevelInfo.Severity + case "WARN", "WARNING": + lc.PrintLevel = maulogger.LevelWarn.Severity + case "ERR", "ERROR": + lc.PrintLevel = maulogger.LevelError.Severity + case "FATAL": + lc.PrintLevel = maulogger.LevelFatal.Severity + default: + return errors.New("invalid print level " + lc.RawPrintLevel) + } + return err +} + +func (lc *LogConfig) MarshalYAML() (interface{}, error) { + switch { + case lc.PrintLevel >= maulogger.LevelFatal.Severity: + lc.RawPrintLevel = maulogger.LevelFatal.Name + case lc.PrintLevel >= maulogger.LevelError.Severity: + lc.RawPrintLevel = maulogger.LevelError.Name + case lc.PrintLevel >= maulogger.LevelWarn.Severity: + lc.RawPrintLevel = maulogger.LevelWarn.Name + case lc.PrintLevel >= maulogger.LevelInfo.Severity: + lc.RawPrintLevel = maulogger.LevelInfo.Name + default: + lc.RawPrintLevel = maulogger.LevelDebug.Name + } + return lc, nil +} + +// CreateLogConfig creates a basic LogConfig. +func CreateLogConfig() LogConfig { + return LogConfig{ + Directory: "./logs", + FileNameFormat: "%[1]s-%02[2]d.log", + TimestampFormat: "Jan _2, 2006 15:04:05", + FileMode: 0600, + FileDateFormat: "2006-01-02", + PrintLevel: 10, + } +} + +type FileFormatData struct { + Date string + Index int +} + +// GetFileFormat returns a mauLogger-compatible logger file format based on the data in the struct. +func (lc LogConfig) GetFileFormat() maulogger.LoggerFileFormat { + if len(lc.Directory) > 0 { + _ = os.MkdirAll(lc.Directory, 0700) + } + path := filepath.Join(lc.Directory, lc.FileNameFormat) + tpl, _ := template.New("fileformat").Parse(path) + + return func(now string, i int) string { + var buf strings.Builder + _ = tpl.Execute(&buf, FileFormatData{ + Date: now, + Index: i, + }) + return buf.String() + } +} + +// Configure configures a mauLogger instance with the data in this struct. +func (lc LogConfig) Configure(log maulogger.Logger) { + basicLogger := log.(*maulogger.BasicLogger) + basicLogger.FileFormat = lc.GetFileFormat() + basicLogger.FileMode = os.FileMode(lc.FileMode) + basicLogger.FileTimeFormat = lc.FileDateFormat + basicLogger.TimeFormat = lc.TimestampFormat + basicLogger.PrintLevel = lc.PrintLevel + basicLogger.JSONFile = lc.JSONFile + if lc.JSONStdout { + basicLogger.EnableJSONStdout() + } +} diff --git a/vendor/maunium.net/go/mautrix/appservice/eventprocessor.go b/vendor/maunium.net/go/mautrix/appservice/eventprocessor.go new file mode 100644 index 0000000000..79a8bdef02 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/eventprocessor.go @@ -0,0 +1,103 @@ +// 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 appservice + +import ( + "encoding/json" + "runtime/debug" + + log "maunium.net/go/maulogger/v2" + + "maunium.net/go/mautrix/event" +) + +type ExecMode uint8 + +const ( + AsyncHandlers ExecMode = iota + AsyncLoop + Sync +) + +type EventHandler func(evt *event.Event) + +type EventProcessor struct { + ExecMode ExecMode + + as *AppService + log log.Logger + stop chan struct{} + handlers map[event.Type][]EventHandler +} + +func NewEventProcessor(as *AppService) *EventProcessor { + return &EventProcessor{ + ExecMode: AsyncHandlers, + as: as, + log: as.Log.Sub("Events"), + stop: make(chan struct{}, 1), + handlers: make(map[event.Type][]EventHandler), + } +} + +func (ep *EventProcessor) On(evtType event.Type, handler EventHandler) { + handlers, ok := ep.handlers[evtType] + if !ok { + handlers = []EventHandler{handler} + } else { + handlers = append(handlers, handler) + } + ep.handlers[evtType] = handlers +} + +func (ep *EventProcessor) callHandler(handler EventHandler, evt *event.Event) { + defer func() { + if err := recover(); err != nil { + d, _ := json.Marshal(evt) + ep.log.Errorfln("Panic in Matrix event handler: %v (event content: %s):\n%s", err, string(d), string(debug.Stack())) + } + }() + handler(evt) +} + +func (ep *EventProcessor) Dispatch(evt *event.Event) { + handlers, ok := ep.handlers[evt.Type] + if !ok { + return + } + switch ep.ExecMode { + case AsyncHandlers: + for _, handler := range handlers { + go ep.callHandler(handler, evt) + } + case AsyncLoop: + go func() { + for _, handler := range handlers { + ep.callHandler(handler, evt) + } + }() + case Sync: + for _, handler := range handlers { + ep.callHandler(handler, evt) + } + } +} + +func (ep *EventProcessor) Start() { + for { + select { + case evt := <-ep.as.Events: + ep.Dispatch(evt) + case <-ep.stop: + return + } + } +} + +func (ep *EventProcessor) Stop() { + ep.stop <- struct{}{} +} diff --git a/vendor/maunium.net/go/mautrix/appservice/http.go b/vendor/maunium.net/go/mautrix/appservice/http.go new file mode 100644 index 0000000000..e30c0111a7 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/http.go @@ -0,0 +1,203 @@ +// 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 appservice + +import ( + "context" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/gorilla/mux" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// Listen starts the HTTP server that listens for calls from the Matrix homeserver. +func (as *AppService) Start() { + as.Router.HandleFunc("/transactions/{txnID}", as.PutTransaction).Methods(http.MethodPut) + as.Router.HandleFunc("/rooms/{roomAlias}", as.GetRoom).Methods(http.MethodGet) + as.Router.HandleFunc("/users/{userID}", as.GetUser).Methods(http.MethodGet) + as.Router.HandleFunc("/_matrix/app/v1/transactions/{txnID}", as.PutTransaction).Methods(http.MethodPut) + as.Router.HandleFunc("/_matrix/app/v1/rooms/{roomAlias}", as.GetRoom).Methods(http.MethodGet) + as.Router.HandleFunc("/_matrix/app/v1/users/{userID}", as.GetUser).Methods(http.MethodGet) + + var err error + as.server = &http.Server{ + Addr: as.Host.Address(), + Handler: as.Router, + } + as.Log.Infoln("Listening on", as.Host.Address()) + if len(as.Host.TLSCert) == 0 || len(as.Host.TLSKey) == 0 { + err = as.server.ListenAndServe() + } else { + err = as.server.ListenAndServeTLS(as.Host.TLSCert, as.Host.TLSKey) + } + if err != nil && err.Error() != "http: Server closed" { + as.Log.Fatalln("Error while listening:", err) + } else { + as.Log.Debugln("Listener stopped.") + } +} + +func (as *AppService) Stop() { + if as.server == nil { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = as.server.Shutdown(ctx) + as.server = nil +} + +// CheckServerToken checks if the given request originated from the Matrix homeserver. +func (as *AppService) CheckServerToken(w http.ResponseWriter, r *http.Request) (isValid bool) { + authHeader := r.Header.Get("Authorization") + if len(authHeader) > 0 && strings.HasPrefix(authHeader, "Bearer ") { + isValid = authHeader[len("Bearer "):] == as.Registration.ServerToken + } else { + queryToken := r.URL.Query().Get("access_token") + if len(queryToken) > 0 { + isValid = queryToken == as.Registration.ServerToken + } else { + Error{ + ErrorCode: ErrUnknownToken, + HTTPStatus: http.StatusForbidden, + Message: "Missing access token", + }.Write(w) + return + } + } + if !isValid { + Error{ + ErrorCode: ErrUnknownToken, + HTTPStatus: http.StatusForbidden, + Message: "Incorrect access token", + }.Write(w) + } + return +} + +// PutTransaction handles a /transactions PUT call from the homeserver. +func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) { + if !as.CheckServerToken(w, r) { + return + } + + vars := mux.Vars(r) + txnID := vars["txnID"] + if len(txnID) == 0 { + Error{ + ErrorCode: ErrNoTransactionID, + HTTPStatus: http.StatusBadRequest, + Message: "Missing transaction ID", + }.Write(w) + return + } + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil || len(body) == 0 { + Error{ + ErrorCode: ErrNotJSON, + HTTPStatus: http.StatusBadRequest, + Message: "Missing request body", + }.Write(w) + return + } + if as.lastProcessedTransaction == txnID { + // Duplicate transaction ID: no-op + WriteBlankOK(w) + return + } + + eventList := EventList{} + err = json.Unmarshal(body, &eventList) + if err != nil { + as.Log.Warnfln("Failed to parse JSON of transaction %s: %v", txnID, err) + Error{ + ErrorCode: ErrBadJSON, + HTTPStatus: http.StatusBadRequest, + Message: "Failed to parse body JSON", + }.Write(w) + } else { + if as.Registration.EphemeralEvents { + if eventList.EphemeralEvents != nil { + as.handleEvents(eventList.EphemeralEvents, event.EphemeralEventType) + } else if eventList.SoruEphemeralEvents != nil { + as.handleEvents(eventList.SoruEphemeralEvents, event.EphemeralEventType) + } + } + as.handleEvents(eventList.Events, event.UnknownEventType) + WriteBlankOK(w) + } + as.lastProcessedTransaction = txnID +} + +func (as *AppService) handleEvents(evts []*event.Event, typeClass event.TypeClass) { + for _, evt := range evts { + if typeClass != event.UnknownEventType { + evt.Type.Class = typeClass + } else if evt.StateKey != nil { + evt.Type.Class = event.StateEventType + } else { + evt.Type.Class = event.MessageEventType + } + err := evt.Content.ParseRaw(evt.Type) + if errors.Is(err, event.UnsupportedContentType) { + as.Log.Debugfln("Not parsing content of %s: %v", evt.ID, err) + } else if err != nil { + as.Log.Debugfln("Failed to parse content of %s (type %s): %v", evt.ID, evt.Type.Type, err) + } + if evt.Type.IsState() { + as.UpdateState(evt) + } + as.Events <- evt + } +} + +// GetRoom handles a /rooms GET call from the homeserver. +func (as *AppService) GetRoom(w http.ResponseWriter, r *http.Request) { + if !as.CheckServerToken(w, r) { + return + } + + vars := mux.Vars(r) + roomAlias := vars["roomAlias"] + ok := as.QueryHandler.QueryAlias(roomAlias) + if ok { + WriteBlankOK(w) + } else { + Error{ + ErrorCode: ErrUnknown, + HTTPStatus: http.StatusNotFound, + }.Write(w) + } +} + +// GetUser handles a /users GET call from the homeserver. +func (as *AppService) GetUser(w http.ResponseWriter, r *http.Request) { + if !as.CheckServerToken(w, r) { + return + } + + vars := mux.Vars(r) + userID := id.UserID(vars["userID"]) + ok := as.QueryHandler.QueryUser(userID) + if ok { + WriteBlankOK(w) + } else { + Error{ + ErrorCode: ErrUnknown, + HTTPStatus: http.StatusNotFound, + }.Write(w) + } +} diff --git a/vendor/maunium.net/go/mautrix/appservice/intent.go b/vendor/maunium.net/go/mautrix/appservice/intent.go new file mode 100644 index 0000000000..725e4abad9 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/intent.go @@ -0,0 +1,307 @@ +// 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 appservice + +import ( + "errors" + "fmt" + "strings" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type IntentAPI struct { + *mautrix.Client + bot *mautrix.Client + as *AppService + Localpart string + UserID id.UserID + + IsCustomPuppet bool +} + +func (as *AppService) NewIntentAPI(localpart string) *IntentAPI { + userID := id.NewUserID(localpart, as.HomeserverDomain) + bot := as.BotClient() + if userID == bot.UserID { + bot = nil + } + return &IntentAPI{ + Client: as.Client(userID), + bot: bot, + as: as, + Localpart: localpart, + UserID: userID, + + IsCustomPuppet: false, + } +} + +func (intent *IntentAPI) Register() error { + _, _, err := intent.Client.Register(&mautrix.ReqRegister{ + Username: intent.Localpart, + Type: mautrix.AuthTypeAppservice, + }) + return err +} + +func (intent *IntentAPI) EnsureRegistered() error { + if intent.IsCustomPuppet || intent.as.StateStore.IsRegistered(intent.UserID) { + return nil + } + + err := intent.Register() + if err != nil && !errors.Is(err, mautrix.MUserInUse) { + return fmt.Errorf("failed to ensure registered: %w", err) + } + intent.as.StateStore.MarkRegistered(intent.UserID) + return nil +} + +func (intent *IntentAPI) EnsureJoined(roomID id.RoomID) error { + if intent.as.StateStore.IsInRoom(roomID, intent.UserID) { + return nil + } + + if err := intent.EnsureRegistered(); err != nil { + return fmt.Errorf("failed to ensure joined: %w", err) + } + + resp, err := intent.JoinRoomByID(roomID) + if err != nil { + if !errors.Is(err, mautrix.MForbidden) || intent.bot == nil { + return fmt.Errorf("failed to ensure joined: %w", err) + } + _, inviteErr := intent.bot.InviteUser(roomID, &mautrix.ReqInviteUser{ + UserID: intent.UserID, + }) + if inviteErr != nil { + return fmt.Errorf("failed to ensure joined: %w", err) + } + resp, err = intent.JoinRoomByID(roomID) + if err != nil { + return fmt.Errorf("failed to ensure joined: %w", err) + } + } + intent.as.StateStore.SetMembership(resp.RoomID, intent.UserID, "join") + return nil +} + +func (intent *IntentAPI) SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendMessageEvent(roomID, eventType, contentJSON) +} + +func (intent *IntentAPI) SendMassagedMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}, ts int64) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendMessageEvent(roomID, eventType, contentJSON, mautrix.ReqSendEvent{Timestamp: ts}) +} + +func (intent *IntentAPI) SendStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendStateEvent(roomID, eventType, stateKey, contentJSON) +} + +func (intent *IntentAPI) SendMassagedStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}, ts int64) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendMassagedStateEvent(roomID, eventType, stateKey, contentJSON, ts) +} + +func (intent *IntentAPI) StateEvent(roomID id.RoomID, eventType event.Type, stateKey string, outContent interface{}) (err error) { + if err := intent.EnsureJoined(roomID); err != nil { + return err + } + return intent.Client.StateEvent(roomID, eventType, stateKey, outContent) +} + +func (intent *IntentAPI) Member(roomID id.RoomID, userID id.UserID) *event.MemberEventContent { + member, ok := intent.as.StateStore.TryGetMember(roomID, userID) + if !ok { + _ = intent.StateEvent(roomID, event.StateMember, string(userID), &member) + intent.as.StateStore.SetMember(roomID, userID, member) + } + return member +} + +func (intent *IntentAPI) PowerLevels(roomID id.RoomID) (pl *event.PowerLevelsEventContent, err error) { + pl = intent.as.StateStore.GetPowerLevels(roomID) + if pl == nil { + pl = &event.PowerLevelsEventContent{} + err = intent.StateEvent(roomID, event.StatePowerLevels, "", pl) + if err == nil { + intent.as.StateStore.SetPowerLevels(roomID, pl) + } + } + return +} + +func (intent *IntentAPI) SetPowerLevels(roomID id.RoomID, levels *event.PowerLevelsEventContent) (resp *mautrix.RespSendEvent, err error) { + resp, err = intent.SendStateEvent(roomID, event.StatePowerLevels, "", &levels) + if err == nil { + intent.as.StateStore.SetPowerLevels(roomID, levels) + } + return +} + +func (intent *IntentAPI) SetPowerLevel(roomID id.RoomID, userID id.UserID, level int) (*mautrix.RespSendEvent, error) { + pl, err := intent.PowerLevels(roomID) + if err != nil { + return nil, err + } + + if pl.GetUserLevel(userID) != level { + pl.SetUserLevel(userID, level) + return intent.SendStateEvent(roomID, event.StatePowerLevels, "", &pl) + } + return nil, nil +} + +func (intent *IntentAPI) UserTyping(roomID id.RoomID, typing bool, timeout int64) (resp *mautrix.RespTyping, err error) { + if intent.as.StateStore.IsTyping(roomID, intent.UserID) == typing { + return + } + resp, err = intent.Client.UserTyping(roomID, typing, timeout) + if err != nil { + return + } + if !typing { + timeout = -1 + } + intent.as.StateStore.SetTyping(roomID, intent.UserID, timeout) + return +} + +func (intent *IntentAPI) SendText(roomID id.RoomID, text string) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendText(roomID, text) +} + +func (intent *IntentAPI) SendImage(roomID id.RoomID, body string, url id.ContentURI) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendImage(roomID, body, url) +} + +func (intent *IntentAPI) SendVideo(roomID id.RoomID, body string, url id.ContentURI) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendVideo(roomID, body, url) +} + +func (intent *IntentAPI) SendNotice(roomID id.RoomID, text string) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.SendNotice(roomID, text) +} + +func (intent *IntentAPI) RedactEvent(roomID id.RoomID, eventID id.EventID, req ...mautrix.ReqRedact) (*mautrix.RespSendEvent, error) { + if err := intent.EnsureJoined(roomID); err != nil { + return nil, err + } + return intent.Client.RedactEvent(roomID, eventID, req...) +} + +func (intent *IntentAPI) SetRoomName(roomID id.RoomID, roomName string) (*mautrix.RespSendEvent, error) { + return intent.SendStateEvent(roomID, event.StateRoomName, "", map[string]interface{}{ + "name": roomName, + }) +} + +func (intent *IntentAPI) SetRoomAvatar(roomID id.RoomID, avatarURL id.ContentURI) (*mautrix.RespSendEvent, error) { + return intent.SendStateEvent(roomID, event.StateRoomAvatar, "", map[string]interface{}{ + "url": avatarURL.String(), + }) +} + +func (intent *IntentAPI) SetRoomTopic(roomID id.RoomID, topic string) (*mautrix.RespSendEvent, error) { + return intent.SendStateEvent(roomID, event.StateTopic, "", map[string]interface{}{ + "topic": topic, + }) +} + +func (intent *IntentAPI) SetDisplayName(displayName string) error { + if err := intent.EnsureRegistered(); err != nil { + return err + } + return intent.Client.SetDisplayName(displayName) +} + +func (intent *IntentAPI) SetAvatarURL(avatarURL id.ContentURI) error { + if err := intent.EnsureRegistered(); err != nil { + return err + } + return intent.Client.SetAvatarURL(avatarURL) +} + +func (intent *IntentAPI) Whoami() (*mautrix.RespWhoami, error) { + if err := intent.EnsureRegistered(); err != nil { + return nil, err + } + return intent.Client.Whoami() +} + +func (intent *IntentAPI) JoinedMembers(roomID id.RoomID) (resp *mautrix.RespJoinedMembers, err error) { + resp, err = intent.Client.JoinedMembers(roomID) + if err != nil { + return + } + for userID, member := range resp.Joined { + var displayname string + var avatarURL id.ContentURIString + if member.DisplayName != nil { + displayname = *member.DisplayName + } + if member.AvatarURL != nil { + avatarURL = id.ContentURIString(*member.AvatarURL) + } + intent.as.StateStore.SetMember(roomID, userID, &event.MemberEventContent{ + Membership: event.MembershipJoin, + AvatarURL: avatarURL, + Displayname: displayname, + }) + } + return +} + +func (intent *IntentAPI) Members(roomID id.RoomID, req ...mautrix.ReqMembers) (resp *mautrix.RespMembers, err error) { + resp, err = intent.Client.Members(roomID, req...) + if err != nil { + return + } + for _, evt := range resp.Chunk { + intent.as.UpdateState(evt) + } + return +} + +func (intent *IntentAPI) EnsureInvited(roomID id.RoomID, userID id.UserID) error { + if !intent.as.StateStore.IsInvited(roomID, userID) { + _, err := intent.Client.InviteUser(roomID, &mautrix.ReqInviteUser{ + UserID: userID, + }) + if httpErr, ok := err.(mautrix.HTTPError); ok && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") { + return nil + } + return err + } + return nil +} diff --git a/vendor/maunium.net/go/mautrix/appservice/protocol.go b/vendor/maunium.net/go/mautrix/appservice/protocol.go new file mode 100644 index 0000000000..b6cc13e63e --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/protocol.go @@ -0,0 +1,71 @@ +// 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 appservice + +import ( + "encoding/json" + "net/http" + + "maunium.net/go/mautrix/event" +) + +// EventList contains a list of events. +type EventList struct { + Events []*event.Event `json:"events"` + EphemeralEvents []*event.Event `json:"ephemeral"` + SoruEphemeralEvents []*event.Event `json:"de.sorunome.msc2409.ephemeral"` +} + +// EventListener is a function that receives events. +type EventListener func(evt *event.Event) + +// WriteBlankOK writes a blank OK message as a reply to a HTTP request. +func WriteBlankOK(w http.ResponseWriter) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("{}")) +} + +// Respond responds to a HTTP request with a JSON object. +func Respond(w http.ResponseWriter, data interface{}) error { + w.Header().Add("Content-Type", "application/json") + dataStr, err := json.Marshal(data) + if err != nil { + return err + } + _, err = w.Write(dataStr) + return err +} + +// Error represents a Matrix protocol error. +type Error struct { + HTTPStatus int `json:"-"` + ErrorCode ErrorCode `json:"errcode"` + Message string `json:"message"` +} + +func (err Error) Write(w http.ResponseWriter) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(err.HTTPStatus) + _ = Respond(w, &err) +} + +// ErrorCode is the machine-readable code in an Error. +type ErrorCode string + +// Native ErrorCodes +const ( + ErrUnknownToken ErrorCode = "M_UNKNOWN_TOKEN" + ErrBadJSON ErrorCode = "M_BAD_JSON" + ErrNotJSON ErrorCode = "M_NOT_JSON" + ErrUnknown ErrorCode = "M_UNKNOWN" +) + +// Custom ErrorCodes +const ( + ErrNoTransactionID ErrorCode = "NET.MAUNIUM.NO_TRANSACTION_ID" +) diff --git a/vendor/maunium.net/go/mautrix/appservice/random.go b/vendor/maunium.net/go/mautrix/appservice/random.go new file mode 100644 index 0000000000..3e3c1dd2a4 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/random.go @@ -0,0 +1,34 @@ +package appservice + +import ( + "math/rand" + "time" +) + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +const ( + letterIdxBits = 6 + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} diff --git a/vendor/maunium.net/go/mautrix/appservice/registration.go b/vendor/maunium.net/go/mautrix/appservice/registration.go new file mode 100644 index 0000000000..87557746ae --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/registration.go @@ -0,0 +1,106 @@ +// Copyright (c) 2019 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 appservice + +import ( + "io/ioutil" + "regexp" + + "gopkg.in/yaml.v2" +) + +// Registration contains the data in a Matrix appservice registration. +// See https://matrix.org/docs/spec/application_service/unstable.html#registration +type Registration struct { + ID string `yaml:"id"` + URL string `yaml:"url"` + AppToken string `yaml:"as_token"` + ServerToken string `yaml:"hs_token"` + SenderLocalpart string `yaml:"sender_localpart"` + RateLimited *bool `yaml:"rate_limited,omitempty"` + Namespaces Namespaces `yaml:"namespaces"` + EphemeralEvents bool `yaml:"de.sorunome.msc2409.push_ephemeral,omitempty"` + Protocols []string `yaml:"protocols,omitempty"` +} + +// CreateRegistration creates a Registration with random appservice and homeserver tokens. +func CreateRegistration() *Registration { + return &Registration{ + AppToken: RandomString(64), + ServerToken: RandomString(64), + } +} + +// LoadRegistration loads a YAML file and turns it into a Registration. +func LoadRegistration(path string) (*Registration, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + reg := &Registration{} + err = yaml.Unmarshal(data, reg) + if err != nil { + return nil, err + } + return reg, nil +} + +// Save saves this Registration into a file at the given path. +func (reg *Registration) Save(path string) error { + data, err := yaml.Marshal(reg) + if err != nil { + return err + } + return ioutil.WriteFile(path, data, 0600) +} + +// YAML returns the registration in YAML format. +func (reg *Registration) YAML() (string, error) { + data, err := yaml.Marshal(reg) + if err != nil { + return "", err + } + return string(data), nil +} + +// Namespaces contains the three areas that appservices can reserve parts of. +type Namespaces struct { + UserIDs []Namespace `yaml:"users,omitempty"` + RoomAliases []Namespace `yaml:"aliases,omitempty"` + RoomIDs []Namespace `yaml:"rooms,omitempty"` +} + +// Namespace is a reserved namespace in any area. +type Namespace struct { + Regex string `yaml:"regex"` + Exclusive bool `yaml:"exclusive"` +} + +// RegisterUserIDs creates an user ID namespace registration. +func (nslist *Namespaces) RegisterUserIDs(regex *regexp.Regexp, exclusive bool) { + nslist.UserIDs = append(nslist.UserIDs, Namespace{ + Regex: regex.String(), + Exclusive: exclusive, + }) +} + +// RegisterRoomAliases creates an room alias namespace registration. +func (nslist *Namespaces) RegisterRoomAliases(regex *regexp.Regexp, exclusive bool) { + nslist.RoomAliases = append(nslist.RoomAliases, Namespace{ + Regex: regex.String(), + Exclusive: exclusive, + }) +} + +// RegisterRoomIDs creates an room ID namespace registration. +func (nslist *Namespaces) RegisterRoomIDs(regex *regexp.Regexp, exclusive bool) { + nslist.RoomIDs = append(nslist.RoomIDs, Namespace{ + Regex: regex.String(), + Exclusive: exclusive, + }) +} diff --git a/vendor/maunium.net/go/mautrix/appservice/statestore.go b/vendor/maunium.net/go/mautrix/appservice/statestore.go new file mode 100644 index 0000000000..4bdce05761 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/statestore.go @@ -0,0 +1,236 @@ +// 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 appservice + +import ( + "sync" + "time" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type StateStore interface { + IsRegistered(userID id.UserID) bool + MarkRegistered(userID id.UserID) + + IsTyping(roomID id.RoomID, userID id.UserID) bool + SetTyping(roomID id.RoomID, userID id.UserID, timeout int64) + + IsInRoom(roomID id.RoomID, userID id.UserID) bool + IsInvited(roomID id.RoomID, userID id.UserID) bool + IsMembership(roomID id.RoomID, userID id.UserID, allowedMemberships ...event.Membership) bool + GetMember(roomID id.RoomID, userID id.UserID) *event.MemberEventContent + TryGetMember(roomID id.RoomID, userID id.UserID) (*event.MemberEventContent, bool) + SetMembership(roomID id.RoomID, userID id.UserID, membership event.Membership) + SetMember(roomID id.RoomID, userID id.UserID, member *event.MemberEventContent) + + SetPowerLevels(roomID id.RoomID, levels *event.PowerLevelsEventContent) + GetPowerLevels(roomID id.RoomID) *event.PowerLevelsEventContent + GetPowerLevel(roomID id.RoomID, userID id.UserID) int + GetPowerLevelRequirement(roomID id.RoomID, eventType event.Type) int + HasPowerLevel(roomID id.RoomID, userID id.UserID, eventType event.Type) bool +} + +func (as *AppService) UpdateState(evt *event.Event) { + switch content := evt.Content.Parsed.(type) { + case *event.MemberEventContent: + as.StateStore.SetMember(evt.RoomID, id.UserID(evt.GetStateKey()), content) + case *event.PowerLevelsEventContent: + as.StateStore.SetPowerLevels(evt.RoomID, content) + } +} + +type TypingStateStore struct { + typing map[id.RoomID]map[id.UserID]int64 + typingLock sync.RWMutex +} + +func NewTypingStateStore() *TypingStateStore { + return &TypingStateStore{ + typing: make(map[id.RoomID]map[id.UserID]int64), + } +} + +func (store *TypingStateStore) IsTyping(roomID id.RoomID, userID id.UserID) bool { + store.typingLock.RLock() + defer store.typingLock.RUnlock() + roomTyping, ok := store.typing[roomID] + if !ok { + return false + } + typingEndsAt, _ := roomTyping[userID] + return typingEndsAt >= time.Now().Unix() +} + +func (store *TypingStateStore) SetTyping(roomID id.RoomID, userID id.UserID, timeout int64) { + store.typingLock.Lock() + defer store.typingLock.Unlock() + roomTyping, ok := store.typing[roomID] + if !ok { + if timeout >= 0 { + roomTyping = map[id.UserID]int64{ + userID: time.Now().Unix() + timeout, + } + } else { + return + } + } else { + if timeout >= 0 { + roomTyping[userID] = time.Now().Unix() + timeout + } else { + delete(roomTyping, userID) + } + } + store.typing[roomID] = roomTyping +} + +type BasicStateStore struct { + registrationsLock sync.RWMutex `json:"-"` + Registrations map[id.UserID]bool `json:"registrations"` + membersLock sync.RWMutex `json:"-"` + Members map[id.RoomID]map[id.UserID]*event.MemberEventContent `json:"memberships"` + powerLevelsLock sync.RWMutex `json:"-"` + PowerLevels map[id.RoomID]*event.PowerLevelsEventContent `json:"power_levels"` + + *TypingStateStore +} + +func NewBasicStateStore() StateStore { + return &BasicStateStore{ + Registrations: make(map[id.UserID]bool), + Members: make(map[id.RoomID]map[id.UserID]*event.MemberEventContent), + PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent), + TypingStateStore: NewTypingStateStore(), + } +} + +func (store *BasicStateStore) IsRegistered(userID id.UserID) bool { + store.registrationsLock.RLock() + defer store.registrationsLock.RUnlock() + registered, ok := store.Registrations[userID] + return ok && registered +} + +func (store *BasicStateStore) MarkRegistered(userID id.UserID) { + store.registrationsLock.Lock() + defer store.registrationsLock.Unlock() + store.Registrations[userID] = true +} + +func (store *BasicStateStore) GetRoomMembers(roomID id.RoomID) map[id.UserID]*event.MemberEventContent { + store.membersLock.RLock() + members, ok := store.Members[roomID] + store.membersLock.RUnlock() + if !ok { + members = make(map[id.UserID]*event.MemberEventContent) + store.membersLock.Lock() + store.Members[roomID] = members + store.membersLock.Unlock() + } + return members +} + +func (store *BasicStateStore) GetMembership(roomID id.RoomID, userID id.UserID) event.Membership { + return store.GetMember(roomID, userID).Membership +} + +func (store *BasicStateStore) GetMember(roomID id.RoomID, userID id.UserID) *event.MemberEventContent { + member, ok := store.TryGetMember(roomID, userID) + if !ok { + member = &event.MemberEventContent{Membership: event.MembershipLeave} + } + return member +} + +func (store *BasicStateStore) TryGetMember(roomID id.RoomID, userID id.UserID) (member *event.MemberEventContent, ok bool) { + store.membersLock.RLock() + defer store.membersLock.RUnlock() + members, membersOk := store.Members[roomID] + if !membersOk { + return + } + member, ok = members[userID] + return +} + +func (store *BasicStateStore) IsInRoom(roomID id.RoomID, userID id.UserID) bool { + return store.IsMembership(roomID, userID, "join") +} + +func (store *BasicStateStore) IsInvited(roomID id.RoomID, userID id.UserID) bool { + return store.IsMembership(roomID, userID, "join", "invite") +} + +func (store *BasicStateStore) IsMembership(roomID id.RoomID, userID id.UserID, allowedMemberships ...event.Membership) bool { + membership := store.GetMembership(roomID, userID) + for _, allowedMembership := range allowedMemberships { + if allowedMembership == membership { + return true + } + } + return false +} + +func (store *BasicStateStore) SetMembership(roomID id.RoomID, userID id.UserID, membership event.Membership) { + store.membersLock.Lock() + members, ok := store.Members[roomID] + if !ok { + members = map[id.UserID]*event.MemberEventContent{ + userID: {Membership: membership}, + } + } else { + member, ok := members[userID] + if !ok { + members[userID] = &event.MemberEventContent{Membership: membership} + } else { + member.Membership = membership + members[userID] = member + } + } + store.Members[roomID] = members + store.membersLock.Unlock() +} + +func (store *BasicStateStore) SetMember(roomID id.RoomID, userID id.UserID, member *event.MemberEventContent) { + store.membersLock.Lock() + members, ok := store.Members[roomID] + if !ok { + members = map[id.UserID]*event.MemberEventContent{ + userID: member, + } + } else { + members[userID] = member + } + store.Members[roomID] = members + store.membersLock.Unlock() +} + +func (store *BasicStateStore) SetPowerLevels(roomID id.RoomID, levels *event.PowerLevelsEventContent) { + store.powerLevelsLock.Lock() + store.PowerLevels[roomID] = levels + store.powerLevelsLock.Unlock() +} + +func (store *BasicStateStore) GetPowerLevels(roomID id.RoomID) (levels *event.PowerLevelsEventContent) { + store.powerLevelsLock.RLock() + levels, _ = store.PowerLevels[roomID] + store.powerLevelsLock.RUnlock() + return +} + +func (store *BasicStateStore) GetPowerLevel(roomID id.RoomID, userID id.UserID) int { + return store.GetPowerLevels(roomID).GetUserLevel(userID) +} + +func (store *BasicStateStore) GetPowerLevelRequirement(roomID id.RoomID, eventType event.Type) int { + return store.GetPowerLevels(roomID).GetEventLevel(eventType) +} + +func (store *BasicStateStore) HasPowerLevel(roomID id.RoomID, userID id.UserID, eventType event.Type) bool { + return store.GetPowerLevel(roomID, userID) >= store.GetPowerLevelRequirement(roomID, eventType) +} diff --git a/vendor/maunium.net/go/mautrix/appservice/websocket.go b/vendor/maunium.net/go/mautrix/appservice/websocket.go new file mode 100644 index 0000000000..21700fd57e --- /dev/null +++ b/vendor/maunium.net/go/mautrix/appservice/websocket.go @@ -0,0 +1,195 @@ +// 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 appservice + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "path/filepath" + "sync" + + "github.com/gorilla/websocket" + + "maunium.net/go/mautrix/event" +) + +type ErrorResponse struct { + ErrorCode ErrorCode `json:"errcode"` + Error string `json:"error"` +} + +type WebsocketCommand struct { + ReqID int `json:"id,omitempty"` + Command string `json:"command"` + Data json.RawMessage `json:"data"` +} + +type WebsocketTransaction struct { + Status string `json:"status"` + TxnID string `json:"txn_id"` + EventList +} + +type WebsocketMessage struct { + WebsocketTransaction + WebsocketCommand +} + +type MeowWebsocketCloseCode string + +const ( + MeowServerShuttingDown MeowWebsocketCloseCode = "server_shutting_down" + MeowConnectionReplaced MeowWebsocketCloseCode = "conn_replaced" +) + +var ( + WebsocketManualStop = errors.New("the websocket was disconnected manually") + WebsocketOverridden = errors.New("a new call to StartWebsocket overrode the previous connection") + WebsocketUnknownError = errors.New("an unknown error occurred") +) + +func (mwcc MeowWebsocketCloseCode) String() string { + switch mwcc { + case MeowServerShuttingDown: + return "the server is shutting down" + case MeowConnectionReplaced: + return "the connection was replaced by another client" + default: + return string(mwcc) + } +} + +type CloseCommand struct { + Code int `json:"-"` + Command string `json:"command"` + Status MeowWebsocketCloseCode `json:"status"` +} + +func (cc CloseCommand) Error() string { + return fmt.Sprintf("websocket: close %d: %s", cc.Code, cc.Status.String()) +} + +func parseCloseError(err error) error { + closeError := &websocket.CloseError{} + if !errors.As(err, &closeError) { + return err + } + var closeCommand CloseCommand + closeCommand.Code = closeError.Code + closeCommand.Command = "disconnect" + if len(closeError.Text) > 0 { + jsonErr := json.Unmarshal([]byte(closeError.Text), &closeCommand) + if jsonErr != nil { + return err + } + } + if len(closeCommand.Status) == 0 { + if closeCommand.Code == 4001 { + closeCommand.Status = MeowConnectionReplaced + } else if closeCommand.Code == websocket.CloseServiceRestart { + closeCommand.Status = MeowServerShuttingDown + } + } + return &closeCommand +} + +func (as *AppService) SendWebsocket(cmd WebsocketCommand) error { + if as.ws == nil { + return errors.New("websocket not connected") + } + return as.ws.WriteJSON(&cmd) +} + +func (as *AppService) consumeWebsocket(stopFunc func(error), ws *websocket.Conn) { + defer stopFunc(WebsocketUnknownError) + for { + var msg WebsocketMessage + err := ws.ReadJSON(&msg) + if err != nil { + as.Log.Debugln("Error reading from websocket:", err) + stopFunc(parseCloseError(err)) + return + } + if msg.Command == "" || msg.Command == "transaction" { + if as.Registration.EphemeralEvents && msg.EphemeralEvents != nil { + as.handleEvents(msg.EphemeralEvents, event.EphemeralEventType) + } + as.handleEvents(msg.Events, event.UnknownEventType) + } else if msg.Command == "connect" { + as.Log.Debugln("Websocket connect confirmation received") + } else { + select { + case as.WebsocketCommands <- msg.WebsocketCommand: + default: + as.Log.Warnln("Dropping websocket command %s %d / %s", msg.Command, msg.ReqID, msg.Data) + } + } + } +} + +func (as *AppService) StartWebsocket(baseURL string, onConnect func()) error { + parsed, err := url.Parse(baseURL) + if err != nil { + return fmt.Errorf("failed to parse URL: %w", err) + } + parsed.Path = filepath.Join(parsed.Path, "_matrix/client/unstable/fi.mau.as_sync") + if parsed.Scheme == "http" { + parsed.Scheme = "ws" + } else if parsed.Scheme == "https" { + parsed.Scheme = "wss" + } + ws, resp, err := websocket.DefaultDialer.Dial(parsed.String(), http.Header{ + "Authorization": []string{fmt.Sprintf("Bearer %s", as.Registration.AppToken)}, + "User-Agent": []string{as.BotClient().UserAgent}, + }) + if resp != nil && resp.StatusCode >= 400 { + var errResp ErrorResponse + err = json.NewDecoder(resp.Body).Decode(&errResp) + if err != nil { + return fmt.Errorf("websocket request returned HTTP %d with non-JSON body", resp.StatusCode) + } else { + return fmt.Errorf("websocket request returned %s (HTTP %d): %s", errResp.ErrorCode, resp.StatusCode, errResp.Error) + } + } else if err != nil { + return fmt.Errorf("failed to open websocket: %w", err) + } + if as.StopWebsocket != nil { + as.StopWebsocket(WebsocketOverridden) + } + closeChan := make(chan error) + closeChanSync := sync.Once{} + stopFunc := func(err error) { + closeChanSync.Do(func() { + closeChan <- err + }) + } + as.ws = ws + as.StopWebsocket = stopFunc + as.PrepareWebsocket() + as.Log.Debugln("Appservice transaction websocket connected") + + go as.consumeWebsocket(stopFunc, ws) + + if onConnect != nil { + onConnect() + } + + closeErr := <-closeChan + + err = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, "")) + if err != nil && err != websocket.ErrCloseSent { + as.Log.Warnln("Error writing close message to websocket:", err) + } + err = ws.Close() + if err != nil { + as.Log.Warnln("Error closing websocket:", err) + } + return closeErr +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 65e54decc0..e741a19107 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -85,6 +85,8 @@ github.com/google/uuid # github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 ## explicit github.com/gopackage/ddp +# github.com/gorilla/mux v1.8.0 +github.com/gorilla/mux # github.com/gorilla/schema v1.2.0 ## explicit github.com/gorilla/schema @@ -137,8 +139,6 @@ github.com/labstack/gommon/random github.com/lrstanley/girc # github.com/magiconair/properties v1.8.5 github.com/magiconair/properties -# github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 -## explicit # github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d ## explicit github.com/matterbridge/Rocket.Chat.Go.SDK/models @@ -352,6 +352,7 @@ golang.org/x/net/http2 golang.org/x/net/http2/h2c golang.org/x/net/http2/hpack golang.org/x/net/idna +golang.org/x/net/publicsuffix golang.org/x/net/websocket # golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1 ## explicit @@ -447,9 +448,12 @@ layeh.com/gumble/gumble layeh.com/gumble/gumble/MumbleProto layeh.com/gumble/gumble/varint layeh.com/gumble/gumbleutil +# maunium.net/go/maulogger/v2 v2.2.4 +maunium.net/go/maulogger/v2 # maunium.net/go/mautrix v0.9.14 ## explicit maunium.net/go/mautrix +maunium.net/go/mautrix/appservice maunium.net/go/mautrix/crypto/attachment maunium.net/go/mautrix/crypto/utils maunium.net/go/mautrix/event