[WIP] Fix ban by software version, some other under-hood changes
- Use updated version of upstream go-xmpp + fixes on client-side - Introduce golangci-lint v2 + fixes - Move especially noisy debugging stuff to separate config handles to be able to switch them off - Add a bit debug to ban by software path
This commit is contained in:
@@ -15,7 +15,7 @@ import (
|
|||||||
"buny-jabber-bot/internal/jabber"
|
"buny-jabber-bot/internal/jabber"
|
||||||
"buny-jabber-bot/internal/log"
|
"buny-jabber-bot/internal/log"
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
"gopkg.in/tomb.v2"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,8 +81,10 @@ func main() {
|
|||||||
verboseClient := false
|
verboseClient := false
|
||||||
|
|
||||||
if log.LogLevel == slog.LevelDebug {
|
if log.LogLevel == slog.LevelDebug {
|
||||||
|
if j.C.Debug.Lib {
|
||||||
verboseClient = true
|
verboseClient = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог.
|
// Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог.
|
||||||
if err := j.ReadWhitelist(); err != nil {
|
if err := j.ReadWhitelist(); err != nil {
|
||||||
@@ -97,9 +99,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace.
|
|
||||||
xmpp.DebugWriter = log.Writer
|
|
||||||
|
|
||||||
// Хэндлер сигналов.
|
// Хэндлер сигналов.
|
||||||
j.GTomb.Go(func() error { return j.SigHandler() }) //nolint: gocritic
|
j.GTomb.Go(func() error { return j.SigHandler() }) //nolint: gocritic
|
||||||
|
|
||||||
@@ -122,6 +121,12 @@ func main() {
|
|||||||
Status: "xa",
|
Status: "xa",
|
||||||
StatusMessage: jabber.RandomPhrase(j.C.Jabber.StartupStatus),
|
StatusMessage: jabber.RandomPhrase(j.C.Jabber.StartupStatus),
|
||||||
DialTimeout: time.Duration(j.C.Jabber.ConnectionTimeout) * time.Second,
|
DialTimeout: time.Duration(j.C.Jabber.ConnectionTimeout) * time.Second,
|
||||||
|
DebugWriter: log.Writer,
|
||||||
|
PeriodicServerPings: false,
|
||||||
|
ReportSoftwareVersion: true,
|
||||||
|
SoftwareName: "Buny Jabber Bot",
|
||||||
|
SoftwareVersion: version,
|
||||||
|
ReportSoftwareOS: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Враппер основной программы. Фактически на каждой серьёзной ошибке, например, сетевой, mainLoop и некоторые
|
// Враппер основной программы. Фактически на каждой серьёзной ошибке, например, сетевой, mainLoop и некоторые
|
||||||
|
|||||||
@@ -196,5 +196,14 @@
|
|||||||
"loglevel": "debug",
|
"loglevel": "debug",
|
||||||
|
|
||||||
# Если не задан, логи идут в STDOUT
|
# Если не задан, логи идут в STDOUT
|
||||||
"log": "/var/log/buny-jabber-bot/error.log"
|
"log": "/var/log/buny-jabber-bot/error.log",
|
||||||
|
|
||||||
|
# Некоторый дебаг создаёт лишний шум. Самые "шумные" опции вынесены сюда.
|
||||||
|
"debug": {
|
||||||
|
# Это дебаг самой либы go-xmpp. Это бывает полезно, если надо похэндлить какой-то формат xmpp-сообщений, т.к.
|
||||||
|
# логгирует то, что пишется "в провод"
|
||||||
|
"lib": false,
|
||||||
|
# Дебаг периодических пингов. Мало полезная вещь, разве что для выявления сетевых аномалий.
|
||||||
|
"ping": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -4,15 +4,11 @@ go 1.24.3
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/eleksir/go-xmpp v1.6.0
|
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
||||||
|
github.com/xmppo/go-xmpp v0.3.1
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/crypto v0.38.0 // indirect
|
|
||||||
golang.org/x/net v0.40.0 // indirect
|
|
||||||
golang.org/x/text v0.25.0 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -1,18 +1,18 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/eleksir/go-xmpp v1.6.0 h1:bkGr0YVczJWpkyk/IHhZb2ewv+NeHLvdNfds0TFgqao=
|
|
||||||
github.com/eleksir/go-xmpp v1.6.0/go.mod h1:Ii6Wemk+yuGQOF+Glt0syjvtcPvdaBLstuNp/+rRJrg=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||||
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a h1:zSc8tkEGo49/E6cQ9o00si0L9Q/Lu1Hsg8JWKCvzEDU=
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a h1:zSc8tkEGo49/E6cQ9o00si0L9Q/Lu1Hsg8JWKCvzEDU=
|
||||||
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a/go.mod h1:SELxwZQq/mPnfPCR2mchLmT4TQaPJvYtLcCtDWSM7vM=
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a/go.mod h1:SELxwZQq/mPnfPCR2mchLmT4TQaPJvYtLcCtDWSM7vM=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
github.com/xmppo/go-xmpp v0.2.19-0.20251123101827-6986f11d85b8 h1:Q6MDZxJVNPlwH/Pm3uQhXhdA2eSUP8YMmhSkCUHbfLU=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
github.com/xmppo/go-xmpp v0.2.19-0.20251123101827-6986f11d85b8/go.mod h1:RyX2+ufcANlJ/ItVhrvfzy4vrvr4537qpPXRsOB+Nz4=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
github.com/xmppo/go-xmpp v0.3.1 h1:GZUCGZozZdfvv25FVvlgwKoNdRjmfJCUcVG8349/E4A=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
github.com/xmppo/go-xmpp v0.3.1/go.mod h1:EBLbzPt4Y9OJBEF58Lhc4IrTnO226aIkbfosQo6KWeA=
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то
|
// BunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то
|
||||||
@@ -28,7 +28,11 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
room := strings.SplitN(v.From, "/", 2)[0]
|
room := strings.SplitN(v.From, "/", 2)[0]
|
||||||
|
|
||||||
if room == "" {
|
if room == "" {
|
||||||
log.Infof("We got empty room field in presence event, which kinda strange: %s", spew.Sdump(v))
|
msg := "We got empty room field in presence event, which kinda strange: " + spew.Sdump(v)
|
||||||
|
|
||||||
|
for str := range strings.SplitSeq(msg, "\n") {
|
||||||
|
log.Info(str)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -419,6 +423,9 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||||
|
|
||||||
|
if v.From == p.From {
|
||||||
|
log.Infof("Found jid of %s in presence db: %s", v.From, p.JID)
|
||||||
|
|
||||||
if p.Affiliation == "admin" || p.Affiliation == "owner" {
|
if p.Affiliation == "admin" || p.Affiliation == "owner" {
|
||||||
log.Infof("Skipping useragent blacklist check for %s because his affiliation is %s", v.From, p.Affiliation)
|
log.Infof("Skipping useragent blacklist check for %s because his affiliation is %s", v.From, p.Affiliation)
|
||||||
|
|
||||||
@@ -432,19 +439,26 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.From == p.From {
|
log.Debug("Useragent is not owner or moderator, proceeding with check...")
|
||||||
log.Errorf("Found jid of %s in presence db: %s", v.From, p.JID)
|
|
||||||
|
|
||||||
// Lookup in global blacklist.
|
// Lookup in global blacklist.
|
||||||
|
log.Debug("Find our room in connected room list...")
|
||||||
|
|
||||||
for _, cRoom := range j.RoomsConnected {
|
for _, cRoom := range j.RoomsConnected {
|
||||||
if cRoom == room {
|
if cRoom == room {
|
||||||
|
log.Debug("Found our room in list of connected rooms")
|
||||||
|
|
||||||
for _, bEntry := range j.BlackList.Blacklist {
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
// Обработаем правила глобального чёрного списка.
|
// Обработаем правила глобального чёрного списка.
|
||||||
if bEntry.RoomName == "" {
|
if bEntry.RoomName == "" {
|
||||||
|
log.Debugf("Check in global blacklist")
|
||||||
|
|
||||||
for _, useragent := range bEntry.UserAgent {
|
for _, useragent := range bEntry.UserAgent {
|
||||||
switch {
|
switch {
|
||||||
case useragent.Name != "" && useragent.Version != "" && useragent.Os != "":
|
case useragent.Name != "" && useragent.Version != "" && useragent.Os != "":
|
||||||
if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os {
|
if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os {
|
||||||
|
log.Debug("Found all 3 components in black list, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -462,6 +476,8 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
case useragent.Name != "" && useragent.Version != "":
|
case useragent.Name != "" && useragent.Version != "":
|
||||||
if ver.Name == useragent.Name && ver.Version == useragent.Version {
|
if ver.Name == useragent.Name && ver.Version == useragent.Version {
|
||||||
|
log.Debug("Found sw name and version in blacklist, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -479,6 +495,8 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
case useragent.Name != "":
|
case useragent.Name != "":
|
||||||
if ver.Name == useragent.Name {
|
if ver.Name == useragent.Name {
|
||||||
|
log.Debug("Found sw name in blacklist, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -496,6 +514,8 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
case useragent.Version != "":
|
case useragent.Version != "":
|
||||||
if ver.Version == useragent.Version {
|
if ver.Version == useragent.Version {
|
||||||
|
log.Debug("Found sw version in blacklist, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -515,10 +535,14 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bEntry.RoomName == room {
|
if bEntry.RoomName == room {
|
||||||
|
log.Debug("Found blacklist entries for room")
|
||||||
|
|
||||||
for _, useragent := range bEntry.UserAgent {
|
for _, useragent := range bEntry.UserAgent {
|
||||||
switch {
|
switch {
|
||||||
case useragent.Name != "" && useragent.Version != "" && useragent.Os != "":
|
case useragent.Name != "" && useragent.Version != "" && useragent.Os != "":
|
||||||
if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os {
|
if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os {
|
||||||
|
log.Debug("Found all 3 components in black list, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -536,6 +560,8 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
case useragent.Name != "" && useragent.Version != "":
|
case useragent.Name != "" && useragent.Version != "":
|
||||||
if ver.Name == useragent.Name && ver.Version == useragent.Version {
|
if ver.Name == useragent.Name && ver.Version == useragent.Version {
|
||||||
|
log.Debug("Found sw name and version in blacklist, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -553,6 +579,8 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
case useragent.Name != "":
|
case useragent.Name != "":
|
||||||
if ver.Name == useragent.Name {
|
if ver.Name == useragent.Name {
|
||||||
|
log.Debug("Found sw name in blacklist, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -570,6 +598,8 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
case useragent.Version != "":
|
case useragent.Version != "":
|
||||||
if ver.Version == useragent.Version {
|
if ver.Version == useragent.Version {
|
||||||
|
log.Debug("Found sw version in blacklist, it is ban")
|
||||||
|
|
||||||
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cmd парсит команды из чятика.
|
// Cmd парсит команды из чятика.
|
||||||
@@ -222,7 +222,11 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Infof("Rehash command from outer space: %s", spew.Sdump(v))
|
msg := "Rehash command from outer space: " + spew.Sdump(v)
|
||||||
|
|
||||||
|
for str := range strings.SplitSeq(msg, "\n") {
|
||||||
|
log.Info(str)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
||||||
@@ -83,7 +82,9 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
case "error":
|
case "error":
|
||||||
// Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят
|
// Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят
|
||||||
// асинхронно, связать их с конкретными нашими действиями довольно сложно.
|
// асинхронно, связать их с конкретными нашими действиями довольно сложно.
|
||||||
log.Warn(spew.Sdump(e))
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Error(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,66 +114,6 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
switch iqStruct.Xmlns { //nolint:wsl,whitespace
|
switch iqStruct.Xmlns { //nolint:wsl,whitespace
|
||||||
// Запрос номера версии приложения.
|
|
||||||
case "jabber:iq:version":
|
|
||||||
log.Infof("Got IQ get request for version from %s", v.From)
|
|
||||||
|
|
||||||
if id, err := j.Talk.IqVersionResponse(
|
|
||||||
v,
|
|
||||||
j.C.ExeName,
|
|
||||||
j.C.Version,
|
|
||||||
(strings.SplitN(runtime.GOOS, "/", 2))[0],
|
|
||||||
); err != nil {
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"unable to send version info to jabber server: id=%s, err=%w",
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
j.GTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parseError = false
|
|
||||||
|
|
||||||
// Нам прислали попингуй.
|
|
||||||
case "urn:xmpp:ping":
|
|
||||||
log.Infof("Got IQ get request for pong from %s", v.From)
|
|
||||||
|
|
||||||
if id, err := j.Talk.PingResponse(v); err != nil {
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"unable to send pong to jabber server: id=%s, err=%w",
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
j.GTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parseError = false
|
|
||||||
|
|
||||||
// У нас запросили время последней активности. В нашем случае это либо время запуска клиента, либо время
|
|
||||||
// последней фразы в чяти. Xep-0012.
|
|
||||||
case "jabber:iq:last":
|
|
||||||
log.Infof("Got IQ get last activity time request from %s", v.From)
|
|
||||||
|
|
||||||
if id, err := j.Talk.JabberIqLastResponse(v, j.LastActivity); err != nil {
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"unable to send last activity time to jabber server: id=%s, err=%w",
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
j.GTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parseError = false
|
|
||||||
|
|
||||||
// Запрос на список поддерживаемых фич - версия, локальное время, итд.
|
// Запрос на список поддерживаемых фич - версия, локальное время, итд.
|
||||||
case xmpp.XMPPNS_DISCO_INFO:
|
case xmpp.XMPPNS_DISCO_INFO:
|
||||||
log.Infof("Got IQ get disco#info request from %s", v.From)
|
log.Infof("Got IQ get disco#info request from %s", v.From)
|
||||||
@@ -233,7 +174,10 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Запрашивают что-то, о чём мы не имеем представления.
|
// Запрашивают что-то, о чём мы не имеем представления.
|
||||||
default:
|
default:
|
||||||
log.Info("Got an unknown IQ get request, discarding")
|
log.Info("Got an unknown IQ get request, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -266,7 +210,10 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
log.Info("Got bogus IQ get time request, discarding")
|
log.Info("Got bogus IQ get time request, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -303,7 +250,10 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
log.Info("Got unknown IQ get pubsub something request, discarding")
|
log.Info("Got unknown IQ get pubsub something request, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
|
|
||||||
parseError = true //nolint:wsl
|
parseError = true //nolint:wsl
|
||||||
}
|
}
|
||||||
@@ -312,35 +262,13 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat).
|
|
||||||
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
|
||||||
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
|
||||||
if parseError {
|
|
||||||
var iqStruct IqPing
|
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
|
||||||
log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From)
|
|
||||||
|
|
||||||
if id, err := j.Talk.PingResponse(v); err != nil {
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"unable to send pong to jabber server: id=%s, err=%w",
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
j.GTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseError = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Не знаю, как парсить... залоггируем это дело и пойдём дальше.
|
// Не знаю, как парсить... залоггируем это дело и пойдём дальше.
|
||||||
if parseError {
|
if parseError {
|
||||||
log.Infof("Does not look like parsable via iqStruct")
|
log.Infof("Does not look like parsable via iqStruct")
|
||||||
log.Info(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case xmpp.IQTypeResult:
|
case xmpp.IQTypeResult:
|
||||||
@@ -381,7 +309,10 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
} else {
|
} else {
|
||||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ.
|
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ.
|
||||||
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,15 +346,24 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
log.Infof("Got ban successful from %s to %s", v.From, v.To)
|
log.Infof("Got ban successful from %s to %s", v.From, v.To)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Got ban successful from %s to %s, from room we are not in", v.From, v.To)
|
log.Infof("Got ban successful from %s to %s, from room we are not in", v.From, v.To)
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Got strange ban successful message from %s to %s", v.From, v.To)
|
log.Infof("Got strange ban successful message from %s to %s", v.From, v.To)
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Got strange ban successful message from %s to %s", v.From, v.To)
|
log.Infof("Got strange ban successful message from %s to %s", v.From, v.To)
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ответ с результатом адресован нам.
|
// Ответ с результатом адресован нам.
|
||||||
@@ -459,11 +399,17 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем.
|
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем.
|
||||||
@@ -492,8 +438,9 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(spew.Sdump(e))
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
// Нам прилетело сообщение об ошибке.
|
// Нам прилетело сообщение об ошибке.
|
||||||
case xmpp.IQTypeError:
|
case xmpp.IQTypeError:
|
||||||
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
||||||
@@ -517,7 +464,10 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Error("Iq parsed as ping, but does not belong to xmlns urn:xmpp:ping")
|
log.Error("Iq parsed as ping, but does not belong to xmlns urn:xmpp:ping")
|
||||||
log.Error(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Error(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseError = false
|
parseError = false
|
||||||
@@ -556,14 +506,68 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
"looks like message parsed as jabberIqErrorCancelNotAcceptable but we're not in given room",
|
"looks like message parsed as jabberIqErrorCancelNotAcceptable but we're not in given room",
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Error(spew.Sdump(e))
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Error(
|
log.Error(
|
||||||
"Looks like message parsed as jabberIqErrorCancelNotAcceptable but addressed not to us",
|
"Looks like message parsed as jabberIqErrorCancelNotAcceptable but addressed not to us",
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Error(spew.Sdump(e))
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parseError {
|
||||||
|
var IqErrorCancelServiceNotAvailable IqErrorCancelServiceNotAvailable
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &IqErrorCancelServiceNotAvailable); err == nil {
|
||||||
|
if v.To == j.Talk.JID() {
|
||||||
|
log.Infof("Jid %s disabled response to iq.version(?) request", v.From)
|
||||||
|
} else {
|
||||||
|
log.Error(
|
||||||
|
"Looks like message parsed as jabberIqErrorCancelServiceNotAvailable but addressed not to us",
|
||||||
|
)
|
||||||
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<iq from='slackware-current@conference.jabber.ru/Batman46(new)' to='buny@jabber.ru/Лунная призма'
|
||||||
|
type='error' id='94e55122-6c15-4e88-a0df-ae9f02e0b550'>
|
||||||
|
<query xmlns='jabber:iq:version'/>
|
||||||
|
<error code='404' type='cancel'>
|
||||||
|
<item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
||||||
|
<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Адресата нет в конференции</text>
|
||||||
|
</error>
|
||||||
|
</iq>
|
||||||
|
*/
|
||||||
|
if parseError {
|
||||||
|
var IqErrorCancelItemNotFound IqErrorCancelItemNotFound
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &IqErrorCancelItemNotFound); err == nil {
|
||||||
|
if v.To == j.Talk.JID() {
|
||||||
|
log.Infof("Jid %s disabled response to iq.version(?) request, hello batman46(new)", v.From)
|
||||||
|
} else {
|
||||||
|
log.Error(
|
||||||
|
"Looks like message parsed as jabberIqErrorCancelServiceNotAvailable but addressed not to us",
|
||||||
|
)
|
||||||
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseError = false
|
parseError = false
|
||||||
@@ -572,12 +576,19 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
if parseError {
|
if parseError {
|
||||||
log.Error("Unhandled IQ Error message")
|
log.Error("Unhandled IQ Error message")
|
||||||
log.Error(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Нам прилетело что-то неизвестное из семейства IQ stanza.
|
// Нам прилетело что-то неизвестное из семейства IQ stanza.
|
||||||
default:
|
default:
|
||||||
log.Info("Got an unknown IQ request. Dunno how deal with it, discarding")
|
log.Info("Got an unknown IQ request. Dunno how deal with it, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Смена статуса участника.
|
// Смена статуса участника.
|
||||||
@@ -614,7 +625,10 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
} else {
|
} else {
|
||||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||||
v.Type, v.From, v.Show, v.Status)
|
v.Type, v.From, v.Show, v.Status)
|
||||||
log.Error(spew.Sdump(v))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Error(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
@@ -746,13 +760,18 @@ func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
log.Debug("Got unknown reply to disco#info")
|
log.Debug("Got unknown reply to disco#info")
|
||||||
log.Debug(spew.Sdump(e))
|
|
||||||
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Debug(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Это что-то неизвестное, подампим событие в лог.
|
// Это что-то неизвестное, подампим событие в лог.
|
||||||
default:
|
default:
|
||||||
log.Info(spew.Sdump(e))
|
for str := range strings.SplitSeq(spew.Sdump(e), "\n") {
|
||||||
|
log.Info(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import (
|
|||||||
"buny-jabber-bot/internal/log"
|
"buny-jabber-bot/internal/log"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QuerySoftwareVersion запрошивает версию и название клиента.
|
// QuerySoftwareVersion запрашивает версию и название клиента.
|
||||||
func (j *Jabber) QuerySoftwareVersion(jid string) (string, error) {
|
func (j *Jabber) QuerySoftwareVersion(jid string) (string, error) {
|
||||||
var (
|
var (
|
||||||
id string
|
id string
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
log.Info("loglevel not defined in config, using info")
|
log.Info("loglevel not defined in config, using info")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sampleConfig.DebugLib = false
|
||||||
// sampleConfig.Log = "" if not set.
|
// sampleConfig.Log = "" if not set.
|
||||||
|
|
||||||
j.C = sampleConfig
|
j.C = sampleConfig
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем.
|
// SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Squash банит указанный jid в указанной комнате.
|
// Squash банит указанный jid в указанной комнате.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
"gopkg.in/tomb.v2"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +14,10 @@ type (
|
|||||||
CSign string `json:"csign,omitempty"`
|
CSign string `json:"csign,omitempty"`
|
||||||
Loglevel string `json:"loglevel,omitempty"`
|
Loglevel string `json:"loglevel,omitempty"`
|
||||||
Log string `json:"log,omitempty"`
|
Log string `json:"log,omitempty"`
|
||||||
|
Debug struct {
|
||||||
|
Lib bool `json:"lib,omitempty"`
|
||||||
|
Ping bool `json:"ping,omitempty"`
|
||||||
|
} `json:"debug"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
ExeName string `json:"exe_name,omitempty"`
|
ExeName string `json:"exe_name,omitempty"`
|
||||||
Jabber struct {
|
Jabber struct {
|
||||||
@@ -202,6 +206,46 @@ type (
|
|||||||
} `xml:"not-acceptable"`
|
} `xml:"not-acceptable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IqErrorCancelServiceNotAvailable обычно возвращается сервером в ответ на iq-запрос к несуществующей сущности.
|
||||||
|
// Если мы такое получаем от клиента, значит мы запросили что-то, что клиент явно не объявил как существущее в
|
||||||
|
// списке своих capability. https://xmpp.org/extensions/xep-0030.html#errors
|
||||||
|
IqErrorCancelServiceNotAvailable struct {
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
ServiceUnavailable struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <iq from='slackware-current@conference.jabber.ru/Batman46(new)' to='buny@jabber.ru/Лунная призма' type='error' id='94e55122-6
|
||||||
|
c15-4e88-a0df-ae9f02e0b550'>
|
||||||
|
<query xmlns='jabber:iq:version'/>
|
||||||
|
<error code='404' type='cancel'>
|
||||||
|
<item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
||||||
|
<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Адресата нет в конференции</text>
|
||||||
|
</error>
|
||||||
|
</iq>
|
||||||
|
*/
|
||||||
|
|
||||||
|
// IqErrorCancelItemNotFound не знаю, насколько корректно так отвечать на запрос версии ПО, но такое иногда
|
||||||
|
// встречаетсяю. Придётся поддержать.
|
||||||
|
IqErrorCancelItemNotFound struct {
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Chardata string `xml:",chardata"`
|
||||||
|
Code string `xml:"code,attr"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
ItemNotFound struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
} `xml:"item-not-found"`
|
||||||
|
Text struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
} `xml:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
||||||
IqResultSoftwareVersion struct {
|
IqResultSoftwareVersion struct {
|
||||||
XMLName xml.Name `xml:"query"`
|
XMLName xml.Name `xml:"query"`
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/xmppo/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RandomPhrase выдаёт одну рандомную фразу из даденного списка фраз.
|
// RandomPhrase выдаёт одну рандомную фразу из даденного списка фраз.
|
||||||
@@ -125,7 +125,7 @@ func (j *Jabber) RotateStatus(room string) error {
|
|||||||
func (j *Jabber) JoinMuc(room string) error {
|
func (j *Jabber) JoinMuc(room string) error {
|
||||||
log.Debugf("Sending disco#info from %s to %s", j.Talk.JID(), room)
|
log.Debugf("Sending disco#info from %s to %s", j.Talk.JID(), room)
|
||||||
|
|
||||||
if _, err := j.Talk.DiscoverInfo(j.Talk.JID(), room); err != nil {
|
if _, err := j.Talk.DiscoverInfo(j.Talk.JID()); err != nil {
|
||||||
return fmt.Errorf("unable to send disco#info to MUC %s: %w", room, err)
|
return fmt.Errorf("unable to send disco#info to MUC %s: %w", room, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
|||||||
var v xmpp.Presence
|
var v xmpp.Presence
|
||||||
|
|
||||||
_ = json.Unmarshal([]byte(name), &v)
|
_ = json.Unmarshal([]byte(name), &v)
|
||||||
log.Infof("Fake presence forged for %s just for on-enter check", name)
|
log.Infof("Fake presence forged for %s just for on-enter check", v.JID)
|
||||||
// Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится.
|
// Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится.
|
||||||
_ = j.BunyPresense(v)
|
_ = j.BunyPresense(v)
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ func (j *Jabber) EstablishConnection() error {
|
|||||||
|
|
||||||
log.Debugf("Sending disco#info to %s", j.C.Jabber.Server)
|
log.Debugf("Sending disco#info to %s", j.C.Jabber.Server)
|
||||||
|
|
||||||
_, err = j.Talk.DiscoverInfo(j.Talk.JID(), j.C.Jabber.Server)
|
_, err = j.Talk.DiscoverInfo(j.Talk.JID())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to send disco#info to jabber server: %w", err)
|
return fmt.Errorf("unable to send disco#info to jabber server: %w", err)
|
||||||
@@ -315,7 +315,9 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
|
|
||||||
// По-умолчанию, мы отправляем c2s пинг.
|
// По-умолчанию, мы отправляем c2s пинг.
|
||||||
default:
|
default:
|
||||||
|
if j.C.Debug.Ping {
|
||||||
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
}
|
||||||
|
|
||||||
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -324,7 +326,9 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
j.ServerPingTimestampTx = time.Now().Unix()
|
j.ServerPingTimestampTx = time.Now().Unix()
|
||||||
}
|
}
|
||||||
} else { // Первая пуля пока не вылетела, отправляем.
|
} else { // Первая пуля пока не вылетела, отправляем.
|
||||||
|
if j.C.Debug.Ping {
|
||||||
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
}
|
||||||
|
|
||||||
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -335,14 +339,18 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
|
|
||||||
// Сервер не анонсировал, что умеет в c2s пинги.
|
// Сервер не анонсировал, что умеет в c2s пинги.
|
||||||
default:
|
default:
|
||||||
|
if j.C.Debug.Ping {
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Сервер не ответил на disco#info.
|
} else { // Сервер не ответил на disco#info.
|
||||||
|
if j.C.Debug.Ping {
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -408,7 +416,9 @@ func (j *Jabber) ProbeMUCLiveness() { //nolint:gocognit
|
|||||||
sleepTime := time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
sleepTime := time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
||||||
time.Sleep(sleepTime)
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
if j.C.Debug.Ping {
|
||||||
log.Debugf("Sending MUC ping from %s to %s", j.Talk.JID(), room)
|
log.Debugf("Sending MUC ping from %s to %s", j.Talk.JID(), room)
|
||||||
|
}
|
||||||
|
|
||||||
if e := j.Talk.PingS2S(j.Talk.JID(), room); e != nil {
|
if e := j.Talk.PingS2S(j.Talk.JID(), room); e != nil {
|
||||||
err = fmt.Errorf("unable to ping MUC %s: %w", room, e)
|
err = fmt.Errorf("unable to ping MUC %s: %w", room, e)
|
||||||
|
|||||||
66
vendor/github.com/eleksir/go-xmpp/.golangci.yml
generated
vendored
66
vendor/github.com/eleksir/go-xmpp/.golangci.yml
generated
vendored
@@ -1,66 +0,0 @@
|
|||||||
run:
|
|
||||||
tests: false
|
|
||||||
skip-dirs:
|
|
||||||
- _example
|
|
||||||
modules-download-mode: readonly
|
|
||||||
allow-parallel-runners: true
|
|
||||||
go: '1.21'
|
|
||||||
output:
|
|
||||||
sort-results: true
|
|
||||||
linters-settings:
|
|
||||||
gosec:
|
|
||||||
excludes:
|
|
||||||
# Оно не используется для повышения безопасности или каких-то таких секъюрых вещей, оно используется для выборки
|
|
||||||
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
|
|
||||||
- G404 # Insecure random number source (rand)
|
|
||||||
linters:
|
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- errcheck
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- staticcheck
|
|
||||||
- unused
|
|
||||||
- asasalint
|
|
||||||
- asciicheck
|
|
||||||
- bodyclose
|
|
||||||
- containedctx
|
|
||||||
- contextcheck
|
|
||||||
- decorder
|
|
||||||
- durationcheck
|
|
||||||
- errchkjson
|
|
||||||
- errorlint
|
|
||||||
- exhaustive
|
|
||||||
- exportloopref
|
|
||||||
- gci
|
|
||||||
- ginkgolinter
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gocritic
|
|
||||||
- godot
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- gosec
|
|
||||||
- gosmopolitan
|
|
||||||
- grouper
|
|
||||||
- importas
|
|
||||||
- ireturn
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- nakedret
|
|
||||||
- nilerr
|
|
||||||
- nilnil
|
|
||||||
- nlreturn
|
|
||||||
- noctx
|
|
||||||
- nolintlint
|
|
||||||
- nonamedreturns
|
|
||||||
- predeclared
|
|
||||||
- promlinter
|
|
||||||
- reassign
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
|
||||||
- stylecheck
|
|
||||||
- unconvert
|
|
||||||
- wastedassign
|
|
||||||
- whitespace
|
|
||||||
- wsl
|
|
||||||
30
vendor/github.com/eleksir/go-xmpp/LICENSE
generated
vendored
30
vendor/github.com/eleksir/go-xmpp/LICENSE
generated
vendored
@@ -1,30 +0,0 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
Copyright (c) 2012 Russ Cox <rsc@swtch.com>
|
|
||||||
Copyright (c) 2023 Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
|
||||||
Copyright (c) 2023 Sergei Fedosoff <eleksir@gmail.com>
|
|
||||||
|
|
||||||
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.
|
|
||||||
14
vendor/github.com/eleksir/go-xmpp/README.md
generated
vendored
14
vendor/github.com/eleksir/go-xmpp/README.md
generated
vendored
@@ -1,14 +0,0 @@
|
|||||||
go-xmpp
|
|
||||||
=======
|
|
||||||
|
|
||||||
go xmpp library.
|
|
||||||
|
|
||||||
[Original](https://github.com/rsc/rsc/blob/master/xmpp/xmpp.go) work done by [Russ Cox](https://github.com/rsc),
|
|
||||||
then [Yasuhiro Matsumoto](https://github.com/mattn) fork it and continue to improve it.
|
|
||||||
|
|
||||||
Since both of them have almost zero interest to continue develop this lib, I decide to fork it.
|
|
||||||
|
|
||||||
Documentation can be found at [godoc project](https://godoc.org/github.com/eleksir/go-xmpp)
|
|
||||||
|
|
||||||
This fork maintains my needs for xmpp protocol. Feel free to contribute via PR-s if you find it interesting too and feel
|
|
||||||
that it needs more love than it already has. And of course fell free to for it if needed.
|
|
||||||
1805
vendor/github.com/eleksir/go-xmpp/xmpp.go
generated
vendored
1805
vendor/github.com/eleksir/go-xmpp/xmpp.go
generated
vendored
File diff suppressed because it is too large
Load Diff
125
vendor/github.com/eleksir/go-xmpp/xmpp_information_query.go
generated
vendored
125
vendor/github.com/eleksir/go-xmpp/xmpp_information_query.go
generated
vendored
@@ -1,125 +0,0 @@
|
|||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const IQTypeGet = "get"
|
|
||||||
const IQTypeSet = "set"
|
|
||||||
const IQTypeResult = "result"
|
|
||||||
const IQTypeError = "error"
|
|
||||||
|
|
||||||
func (c *Client) Discovery() (string, error) {
|
|
||||||
// use getCookie for a pseudo random id.
|
|
||||||
reqID := strconv.FormatUint(uint64(getCookie()), 10)
|
|
||||||
|
|
||||||
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverNodeInfo discovers information about a node. Empty node queries info about server itself.
|
|
||||||
func (c *Client) DiscoverNodeInfo(node string) (string, error) {
|
|
||||||
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node)
|
|
||||||
|
|
||||||
return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverInfo discovers information about given item from given jid.
|
|
||||||
func (c *Client) DiscoverInfo(from string, to string) (string, error) {
|
|
||||||
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_INFO)
|
|
||||||
|
|
||||||
return c.RawInformation(from, to, "info3", IQTypeGet, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverServerItems discover items that the server exposes.
|
|
||||||
func (c *Client) DiscoverServerItems() (string, error) {
|
|
||||||
return c.DiscoverEntityItems(c.domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoverEntityItems discovers items that an entity exposes.
|
|
||||||
func (c *Client) DiscoverEntityItems(jid string) (string, error) {
|
|
||||||
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
|
|
||||||
|
|
||||||
return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawInformationQuery sends an information query request to the server.
|
|
||||||
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
|
||||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
|
||||||
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawInformation sends a IQ request with the payload body to the server.
|
|
||||||
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
|
|
||||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>"
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
|
|
||||||
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IqVersionResponse responding with software version, according to xep-0092.
|
|
||||||
func (c *Client) IqVersionResponse(v IQ, name, version, os string) (string, error) {
|
|
||||||
if name == "" {
|
|
||||||
name = "go-xmpp"
|
|
||||||
}
|
|
||||||
|
|
||||||
if version == "" {
|
|
||||||
version = "undefined"
|
|
||||||
}
|
|
||||||
|
|
||||||
query := "<query xmlns=\"jabber:iq:version\">"
|
|
||||||
query += fmt.Sprintf("<name>%s</name>", name)
|
|
||||||
query += fmt.Sprintf("<version>%s</version>", version)
|
|
||||||
|
|
||||||
if os != "" {
|
|
||||||
query += fmt.Sprintf("<os>%s</os>", os)
|
|
||||||
}
|
|
||||||
|
|
||||||
query += "</query>"
|
|
||||||
|
|
||||||
return c.RawInformation(
|
|
||||||
v.To,
|
|
||||||
v.From,
|
|
||||||
v.ID,
|
|
||||||
IQTypeResult,
|
|
||||||
query,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JabberIqLastResponse responding with relative time since last activity.
|
|
||||||
// Here lastActivity is unix time stamp when last activity took place since.
|
|
||||||
func (c *Client) JabberIqLastResponse(v IQ, lastActivity int64) (string, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"<query xmlns=\"jabber:iq:last\" seconds=\"%d\" />",
|
|
||||||
time.Now().Unix()-lastActivity,
|
|
||||||
)
|
|
||||||
|
|
||||||
return c.RawInformation(
|
|
||||||
v.To,
|
|
||||||
v.From,
|
|
||||||
v.ID,
|
|
||||||
IQTypeResult,
|
|
||||||
query,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UrnXMPPTimeResponse implements response to query entity's current time (xep-0202).
|
|
||||||
// TimezoneOffset have format +HH:MM or -HH:MM.
|
|
||||||
func (c *Client) UrnXMPPTimeResponse(v IQ, timezoneOffset string) (string, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"<time xmlns=\"urn:xmpp:time\"><tzo>%s</tzo><utc>%s</utc></time>",
|
|
||||||
timezoneOffset,
|
|
||||||
time.Now().UTC().Format(time.RFC3339),
|
|
||||||
)
|
|
||||||
|
|
||||||
return c.RawInformation(
|
|
||||||
v.To,
|
|
||||||
v.From,
|
|
||||||
v.ID,
|
|
||||||
IQTypeResult,
|
|
||||||
query,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
140
vendor/github.com/eleksir/go-xmpp/xmpp_muc.go
generated
vendored
140
vendor/github.com/eleksir/go-xmpp/xmpp_muc.go
generated
vendored
@@ -1,140 +0,0 @@
|
|||||||
// Copyright 2013 Flo Lauber <dev@qatfy.at>. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// TODO(flo):
|
|
||||||
// - cleanup signatures of join/leave functions
|
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nsMUC = "http://jabber.org/protocol/muc"
|
|
||||||
nsMUCUser = "http://jabber.org/protocol/muc#user"
|
|
||||||
NoHistory = 0
|
|
||||||
CharHistory = 1
|
|
||||||
StanzaHistory = 2
|
|
||||||
SecondsHistory = 3
|
|
||||||
SinceHistory = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendTopic sends room topic wrapped inside an XMPP message stanza body.
|
|
||||||
func (c *Client) SendTopic(chat Chat) (int, error) {
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
|
|
||||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinMUCNoHistory joins room and instructs server that no history data required.
|
|
||||||
func (c *Client) JoinMUCNoHistory(jid, nick string) (int, error) {
|
|
||||||
if nick == "" {
|
|
||||||
nick = c.jid
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>"+
|
|
||||||
"<history maxchars='0'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinMUC joins room, xep-0045 7.2.
|
|
||||||
func (c *Client) JoinMUC(jid, nick string, historyType, history int, historyDate *time.Time) (int, error) {
|
|
||||||
if nick == "" {
|
|
||||||
nick = c.jid
|
|
||||||
}
|
|
||||||
|
|
||||||
switch historyType {
|
|
||||||
case NoHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s' />\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
|
||||||
case CharHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<history maxchars='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
|
||||||
case StanzaHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<history maxstanzas='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
|
||||||
case SecondsHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<history seconds='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
|
||||||
case SinceHistory:
|
|
||||||
if historyDate != nil {
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<history since='%s'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, historyDate.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, errors.New("unknown history option")
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinProtectedMUC joins password protected room, xep-0045 7.2.6.
|
|
||||||
func (c *Client) JoinProtectedMUC(jid, nick string, password string, historyType, history int, historyDate *time.Time) (int, error) {
|
|
||||||
if nick == "" {
|
|
||||||
nick = c.jid
|
|
||||||
}
|
|
||||||
|
|
||||||
switch historyType {
|
|
||||||
case NoHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<password>%s</password>"+
|
|
||||||
"</x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
|
|
||||||
case CharHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history maxchars='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
|
||||||
case StanzaHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history maxstanzas='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
|
||||||
case SecondsHistory:
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history seconds='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
|
||||||
case SinceHistory:
|
|
||||||
if historyDate != nil {
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>\n"+
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history since='%s'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), historyDate.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, errors.New("unknown history option")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeaveMUC quits from room xep-0045 7.14.
|
|
||||||
func (c *Client) LeaveMUC(jid string) (int, error) {
|
|
||||||
return fmt.Fprintf(StanzaWriter, "<presence from='%s' to='%s' type='unavailable' />",
|
|
||||||
c.jid, xmlEscape(jid))
|
|
||||||
}
|
|
||||||
49
vendor/github.com/eleksir/go-xmpp/xmpp_ping.go
generated
vendored
49
vendor/github.com/eleksir/go-xmpp/xmpp_ping.go
generated
vendored
@@ -1,49 +0,0 @@
|
|||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) PingC2S(jid, server string) error {
|
|
||||||
if jid == "" {
|
|
||||||
jid = c.jid
|
|
||||||
}
|
|
||||||
|
|
||||||
if server == "" {
|
|
||||||
server = c.domain
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
|
|
||||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
|
||||||
"</iq>",
|
|
||||||
xmlEscape(jid), xmlEscape(server))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) PingS2S(fromServer, toServer string) error {
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
|
|
||||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
|
||||||
"</iq>",
|
|
||||||
xmlEscape(fromServer), xmlEscape(toServer))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SendResultPing(id, toServer string) error {
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, "<iq type='result' to='%s' id='%s'/>",
|
|
||||||
xmlEscape(toServer), xmlEscape(id))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PingResponse responding to ping query according to xep-0199.
|
|
||||||
func (c *Client) PingResponse(v IQ) (string, error) {
|
|
||||||
return c.RawInformation(
|
|
||||||
v.To,
|
|
||||||
v.From,
|
|
||||||
v.ID,
|
|
||||||
IQTypeResult,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
33
vendor/github.com/eleksir/go-xmpp/xmpp_subscription.go
generated
vendored
33
vendor/github.com/eleksir/go-xmpp/xmpp_subscription.go
generated
vendored
@@ -1,33 +0,0 @@
|
|||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) ApproveSubscription(jid string) error {
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribed'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RevokeSubscription(jid string) error {
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, "<presence to='%s' type='unsubscribed'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RetrieveSubscription(jid string) error {
|
|
||||||
_, err := fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RequestSubscription(jid string) error {
|
|
||||||
_, err := fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribe'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
5
vendor/github.com/xmppo/go-xmpp/.travis.yml
generated
vendored
Normal file
5
vendor/github.com/xmppo/go-xmpp/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
script:
|
||||||
|
- go test
|
||||||
4
vendor/golang.org/x/text/LICENSE → vendor/github.com/xmppo/go-xmpp/LICENSE
generated
vendored
4
vendor/golang.org/x/text/LICENSE → vendor/github.com/xmppo/go-xmpp/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2009 The Go Authors.
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
|||||||
copyright notice, this list of conditions and the following disclaimer
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
in the documentation and/or other materials provided with the
|
in the documentation and/or other materials provided with the
|
||||||
distribution.
|
distribution.
|
||||||
* Neither the name of Google LLC nor the names of its
|
* Neither the name of Google Inc. nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
6
vendor/github.com/xmppo/go-xmpp/README.md
generated
vendored
Normal file
6
vendor/github.com/xmppo/go-xmpp/README.md
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
go-xmpp
|
||||||
|
=======
|
||||||
|
|
||||||
|
go xmpp library (original was written by russ cox )
|
||||||
|
|
||||||
|
[Documentation](https://godoc.org/github.com/xmppo/go-xmpp)
|
||||||
51
vendor/github.com/xmppo/go-xmpp/const.go
generated
vendored
Normal file
51
vendor/github.com/xmppo/go-xmpp/const.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = "0.3.1"
|
||||||
|
// FAST Mechanisms
|
||||||
|
HT_SHA_256_ENDP = "HT-SHA-256-ENDP"
|
||||||
|
HT_SHA_256_EXPR = "HT-SHA-256-EXPR"
|
||||||
|
HT_SHA_256_NONE = "HT-SHA-256-NONE"
|
||||||
|
HT_SHA_256_UNIQ = "HT-SHA-256-UNIQ"
|
||||||
|
// IQ Types
|
||||||
|
IQTypeError = "error"
|
||||||
|
IQTypeGet = "get"
|
||||||
|
IQTypeResult = "result"
|
||||||
|
IQTypeSet = "set"
|
||||||
|
// SCRAM Mechanisms
|
||||||
|
SCRAM_SHA_1_PLUS = "SCRAM-SHA-1-PLUS"
|
||||||
|
SCRAM_SHA_1 = "SCRAM-SHA-1"
|
||||||
|
SCRAM_SHA_256_PLUS = "SCRAM-SHA-256-PLUS"
|
||||||
|
SCRAM_SHA_256 = "SCRAM-SHA-256"
|
||||||
|
SCRAM_SHA_512_PLUS = "SCRAM-SHA-512-PLUS"
|
||||||
|
SCRAM_SHA_512 = "SCRAM-SHA-512"
|
||||||
|
UPGR_SCRAM_SHA_256 = "UPGR-SCRAM-SHA-256"
|
||||||
|
UPGR_SCRAM_SHA_512 = "UPGR-SCRAM-SHA-512"
|
||||||
|
// XMPP Namespaces
|
||||||
|
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data"
|
||||||
|
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata"
|
||||||
|
XMPPNS_BIND_0 = "urn:xmpp:bind:0"
|
||||||
|
XMPPNS_CLIENT = "jabber:client"
|
||||||
|
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
|
||||||
|
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
|
||||||
|
XMPPNS_FAST_0 = "urn:xmpp:fast:0"
|
||||||
|
XMPPNS_HTTP_UPLOAD_0 = "urn:xmpp:http:upload:0"
|
||||||
|
XMPPNS_IQ_VERSION = "jabber:iq:version"
|
||||||
|
XMPPNS_MUC = "http://jabber.org/protocol/muc"
|
||||||
|
XMPPNS_MUC_USER = "http://jabber.org/protocol/muc#user"
|
||||||
|
XMPPNS_PING = "urn:xmpp:ping"
|
||||||
|
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event"
|
||||||
|
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub"
|
||||||
|
XMPPNS_SASL_2 = "urn:xmpp:sasl:2"
|
||||||
|
XMPPNS_SASL_CB_0 = "urn:xmpp:sasl-cb:0"
|
||||||
|
XMPPNS_SASL_UPGRADE_0 = "urn:xmpp:sasl:upgrade:0"
|
||||||
|
XMPPNS_XMPP_SASL = "urn:ietf:params:xml:ns:xmpp-sasl"
|
||||||
|
XMPPNS_SCRAM_UPGRADE_0 = "urn:xmpp:scram-upgrade:0"
|
||||||
|
XMPPNS_SID_0 = "urn:xmpp:sid:0"
|
||||||
|
XMPPNS_STREAM = "http://etherx.jabber.org/streams"
|
||||||
|
XMPPNS_STREAM_LIMITS_0 = "urn:xmpp:stream-limits:0"
|
||||||
|
XMPPNS_TIME = "urn:xmpp:time"
|
||||||
|
XMPPNS_XMPP_TLS = "urn:ietf:params:xml:ns:xmpp-tls"
|
||||||
|
XMPPNS_XMPP_BIND = "urn:ietf:params:xml:ns:xmpp-bind"
|
||||||
|
XMPPNS_XMPP_SESSION = "urn:ietf:params:xml:ns:xmpp-session"
|
||||||
|
)
|
||||||
2460
vendor/github.com/xmppo/go-xmpp/xmpp.go
generated
vendored
Normal file
2460
vendor/github.com/xmppo/go-xmpp/xmpp.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
package xmpp
|
package xmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1" //nolint:gosec // go fuck yourself, gosec. You don't even fucking read rfc. So shout the fuck up.
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
@@ -9,11 +9,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data" //nocritic:revive
|
|
||||||
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata" //nocritic:revive
|
|
||||||
)
|
|
||||||
|
|
||||||
type clientAvatarData struct {
|
type clientAvatarData struct {
|
||||||
XMLName xml.Name `xml:"data"`
|
XMLName xml.Name `xml:"data"`
|
||||||
Data []byte `xml:",innerxml"`
|
Data []byte `xml:",innerxml"`
|
||||||
@@ -52,8 +47,8 @@ type AvatarMetadata struct {
|
|||||||
|
|
||||||
func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
|
func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
|
||||||
var data clientAvatarData
|
var data clientAvatarData
|
||||||
|
err := xml.Unmarshal(itemsBody, &data)
|
||||||
if err := xml.Unmarshal(itemsBody, &data); err != nil {
|
if err != nil {
|
||||||
return AvatarData{}, err
|
return AvatarData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,9 +59,8 @@ func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
|
|||||||
return AvatarData{}, err
|
return AvatarData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := sha1.Sum(dataRaw) //nolint:gosec // go fuck yourself, gosec. This exact mechanics described in standard.
|
hash := sha1.Sum(dataRaw) //nolint: gosec,G401 // It is not about security.
|
||||||
hashStr := hex.EncodeToString(hash[:])
|
hashStr := hex.EncodeToString(hash[:])
|
||||||
|
|
||||||
if hashStr != id {
|
if hashStr != id {
|
||||||
return AvatarData{}, errors.New("SHA1 hashes do not match")
|
return AvatarData{}, errors.New("SHA1 hashes do not match")
|
||||||
}
|
}
|
||||||
@@ -78,12 +72,9 @@ func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
|
func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
|
||||||
var (
|
var meta clientAvatarMetadata
|
||||||
meta clientAvatarMetadata
|
err := xml.Unmarshal(body, &meta)
|
||||||
err error
|
if err != nil {
|
||||||
)
|
|
||||||
|
|
||||||
if err = xml.Unmarshal(body, &meta); err != nil {
|
|
||||||
return AvatarMetadata{}, err
|
return AvatarMetadata{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,10 +86,10 @@ func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
|
|||||||
ID: meta.Info.ID,
|
ID: meta.Info.ID,
|
||||||
Type: meta.Info.Type,
|
Type: meta.Info.Type,
|
||||||
URL: meta.Info.URL,
|
URL: meta.Info.URL,
|
||||||
}, err
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// atoiw is a wrapper for atoi which just returns -1 if an error occurs.
|
// A wrapper for atoi which just returns -1 if an error occurs
|
||||||
func atoiw(str string) int {
|
func atoiw(str string) int {
|
||||||
i, err := strconv.Atoi(str)
|
i, err := strconv.Atoi(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,22 +99,22 @@ func atoiw(str string) int {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AvatarSubscribeMetadata(jid string) (string, error) {
|
func (c *Client) AvatarSubscribeMetadata(jid string) error {
|
||||||
return c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
return c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AvatarUnsubscribeMetadata(jid string) (string, error) {
|
func (c *Client) AvatarUnsubscribeMetadata(jid string) error {
|
||||||
return c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
return c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AvatarRequestData(jid string) (string, error) {
|
func (c *Client) AvatarRequestData(jid string) error {
|
||||||
return c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
|
return c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AvatarRequestDataByID(jid, id string) (string, error) {
|
func (c *Client) AvatarRequestDataByID(jid, id string) error {
|
||||||
return c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
|
return c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AvatarRequestMetadata(jid string) (string, error) {
|
func (c *Client) AvatarRequestMetadata(jid string) error {
|
||||||
return c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
|
return c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,6 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items" //nocritic:revive
|
|
||||||
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info" //nocritic:revive
|
|
||||||
)
|
|
||||||
|
|
||||||
type clientDiscoFeature struct {
|
type clientDiscoFeature struct {
|
||||||
XMLName xml.Name `xml:"feature"`
|
XMLName xml.Name `xml:"feature"`
|
||||||
Var string `xml:"var,attr"`
|
Var string `xml:"var,attr"`
|
||||||
@@ -25,6 +20,7 @@ type clientDiscoQuery struct {
|
|||||||
XMLName xml.Name `xml:"query"`
|
XMLName xml.Name `xml:"query"`
|
||||||
Features []clientDiscoFeature `xml:"feature"`
|
Features []clientDiscoFeature `xml:"feature"`
|
||||||
Identities []clientDiscoIdentity `xml:"identity"`
|
Identities []clientDiscoIdentity `xml:"identity"`
|
||||||
|
X []DiscoX `xml:"x"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientDiscoItem struct {
|
type clientDiscoItem struct {
|
||||||
@@ -57,6 +53,18 @@ type DiscoResult struct {
|
|||||||
To string
|
To string
|
||||||
Features []string
|
Features []string
|
||||||
Identities []DiscoIdentity
|
Identities []DiscoIdentity
|
||||||
|
X []DiscoX
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoX struct {
|
||||||
|
XMLName xml.Name `xml:"x"`
|
||||||
|
Field []DiscoXField `xml:"field"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoXField struct {
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Var string `xml:"var,attr"`
|
||||||
|
Value []string `xml:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiscoItems struct {
|
type DiscoItems struct {
|
||||||
@@ -90,7 +98,6 @@ func clientIdentitiesToReturn(identities []clientDiscoIdentity) []DiscoIdentity
|
|||||||
|
|
||||||
func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
|
func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
|
||||||
var ret []DiscoItem
|
var ret []DiscoItem
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
ret = append(ret, DiscoItem{
|
ret = append(ret, DiscoItem{
|
||||||
Jid: item.Jid,
|
Jid: item.Jid,
|
||||||
@@ -4,23 +4,23 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorServiceUnavailable implements error response about feature that is not available. Currently implemented for
|
// ErrorServiceUnavailable implements error response about a feature that is not available. Currently implemented for
|
||||||
// xep-0030.
|
// xep-0030.
|
||||||
// QueryXmlns is about incoming xmlns attribute in query tag.
|
// QueryXmlns is about incoming xmlns attribute in query tag.
|
||||||
// Node is about incoming node attribute in query tag (looks like it used only in disco#commands).
|
// Node is about incoming node attribute in query tag (looks like it used only in disco#commands).
|
||||||
//
|
//
|
||||||
// If queried feature is not here on purpose, standards suggest answer with this stanza.
|
// If queried feature is not here on purpose, standards suggest to answer with this stanza.
|
||||||
func (c *Client) ErrorServiceUnavailable(v IQ, queryXmlns, node string) (string, error) {
|
func (c *Client) ErrorServiceUnavailable(v IQ, queryXmlns, node string) (string, error) {
|
||||||
query := fmt.Sprintf("<query xmlns=\"%s\" ", queryXmlns)
|
query := fmt.Sprintf("<query xmlns='%s' ", queryXmlns)
|
||||||
|
|
||||||
if node != "" {
|
if node != "" {
|
||||||
query += fmt.Sprintf("node=\"%s\" />", node)
|
query += fmt.Sprintf("node='%s' />", node)
|
||||||
} else {
|
} else {
|
||||||
query += "/>"
|
query += "/>"
|
||||||
}
|
}
|
||||||
|
|
||||||
query += "<error type=\"cancel\">"
|
query += "<error type='cancel'>"
|
||||||
query += "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
|
query += "<service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' />"
|
||||||
query += "</error>"
|
query += "</error>"
|
||||||
|
|
||||||
return c.RawInformation(
|
return c.RawInformation(
|
||||||
@@ -32,16 +32,16 @@ func (c *Client) ErrorServiceUnavailable(v IQ, queryXmlns, node string) (string,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorNotImplemented implements error response about feature that is not (yet?) implemented.
|
// ErrorNotImplemented implements error response about a feature that is not (yet?) implemented.
|
||||||
// Xmlns is about not implemented feature.
|
// Xmlns is about not implemented feature.
|
||||||
//
|
//
|
||||||
// If queried feature is not here because of it under development or for similar reasons, standards suggest answer with
|
// If queried feature is not here because of it under development or for similar reasons, standards suggest to answer with
|
||||||
// this stanza.
|
// this stanza.
|
||||||
func (c *Client) ErrorNotImplemented(v IQ, xmlns, feature string) (string, error) {
|
func (c *Client) ErrorNotImplemented(v IQ, xmlns, feature string) (string, error) {
|
||||||
query := "<error type=\"cancel\">"
|
query := "<error type='cancel'>"
|
||||||
query += "<feature-not-implemented xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
|
query += "<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' />"
|
||||||
query += fmt.Sprintf(
|
query += fmt.Sprintf(
|
||||||
"<unsupported xmlns=\"%s\" feature=\"%s\" />",
|
"<unsupported xmlns='%s' feature='%s' />",
|
||||||
xmlns,
|
xmlns,
|
||||||
feature,
|
feature,
|
||||||
)
|
)
|
||||||
38
vendor/github.com/xmppo/go-xmpp/xmpp_http_upload.go
generated
vendored
Normal file
38
vendor/github.com/xmppo/go-xmpp/xmpp_http_upload.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Slot struct {
|
||||||
|
// TODO: Maybe this doesn't belong here
|
||||||
|
ID string
|
||||||
|
XMLName xml.Name `xml:"slot"`
|
||||||
|
Put Put
|
||||||
|
Get Get
|
||||||
|
}
|
||||||
|
|
||||||
|
type Put struct {
|
||||||
|
XMLName xml.Name `xml:"put"`
|
||||||
|
Url string `xml:"url,attr"`
|
||||||
|
Headers []Header `xml:"header"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Get struct {
|
||||||
|
XMLName xml.Name `xml:"get"`
|
||||||
|
Url string `xml:"url,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
XMLName xml.Name `xml:"header"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Value string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oob is an out-of-band url/description, used in file uploads.
|
||||||
|
// See https://xmpp.org/extensions/xep-0066.html
|
||||||
|
type Oob struct {
|
||||||
|
XMLName xml.Name `xml:"x,xmlns:jabber:x:oob"`
|
||||||
|
Url string `xml:"url"`
|
||||||
|
Desc string `xml:"desc"`
|
||||||
|
}
|
||||||
97
vendor/github.com/xmppo/go-xmpp/xmpp_information_query.go
generated
vendored
Normal file
97
vendor/github.com/xmppo/go-xmpp/xmpp_information_query.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) Discovery() (string, error) {
|
||||||
|
// use UUIDv4 for a pseudo random id.
|
||||||
|
reqID := getUUID()
|
||||||
|
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover information about a node. Empty node queries info about server itself.
|
||||||
|
func (c *Client) DiscoverNodeInfo(node string) (string, error) {
|
||||||
|
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node)
|
||||||
|
return c.RawInformation(c.jid, c.domain, getUUID(), IQTypeGet, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover information about given item from given jid.
|
||||||
|
func (c *Client) DiscoverInfo(to string) (string, error) {
|
||||||
|
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_INFO)
|
||||||
|
return c.RawInformation(c.jid, to, getUUID(), IQTypeGet, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover items that the server exposes
|
||||||
|
func (c *Client) DiscoverServerItems() (string, error) {
|
||||||
|
return c.DiscoverEntityItems(c.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover items that an entity exposes
|
||||||
|
func (c *Client) DiscoverEntityItems(jid string) (string, error) {
|
||||||
|
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
|
||||||
|
return c.RawInformation(c.jid, jid, getUUID(), IQTypeGet, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawInformationQuery sends an information query request to the server.
|
||||||
|
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
||||||
|
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>\n"
|
||||||
|
_, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawInformation send a IQ request with the payload body to the server
|
||||||
|
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
|
||||||
|
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>\n"
|
||||||
|
_, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UrnXMPPTimeResponse implements response to query entity's current time (xep-0202).
|
||||||
|
func (c *Client) UrnXMPPTimeResponse(v IQ, timezoneOffset string) (string, error) {
|
||||||
|
query := fmt.Sprintf(
|
||||||
|
"<time xmlns=\"%s\"><tzo>%s</tzo><utc>%s</utc></time>",
|
||||||
|
XMPPNS_TIME,
|
||||||
|
timezoneOffset,
|
||||||
|
time.Now().UTC().Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
|
||||||
|
return c.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
IQTypeResult,
|
||||||
|
query,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IqVersionResponse responding with software version, according to xep-0092.
|
||||||
|
func (c *Client) IqVersionResponse(v IQ, name string, version string, os string) (string, error) {
|
||||||
|
if name == "" {
|
||||||
|
name = "go-xmpp"
|
||||||
|
version = Version
|
||||||
|
}
|
||||||
|
|
||||||
|
if version == "" {
|
||||||
|
version = "undefined"
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("<query xmlns=\"%s\">", XMPPNS_IQ_VERSION)
|
||||||
|
query += fmt.Sprintf("<name>%s</name>", name)
|
||||||
|
query += fmt.Sprintf("<version>%s</version>", version)
|
||||||
|
|
||||||
|
if os != "" {
|
||||||
|
query += fmt.Sprintf("<os>%s</os>", os)
|
||||||
|
}
|
||||||
|
|
||||||
|
query += "</query>"
|
||||||
|
|
||||||
|
return c.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
IQTypeResult,
|
||||||
|
query,
|
||||||
|
)
|
||||||
|
}
|
||||||
133
vendor/github.com/xmppo/go-xmpp/xmpp_muc.go
generated
vendored
Normal file
133
vendor/github.com/xmppo/go-xmpp/xmpp_muc.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// Copyright 2013 Flo Lauber <dev@qatfy.at>. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// TODO(flo):
|
||||||
|
// - support password protected MUC rooms
|
||||||
|
// - cleanup signatures of join/leave functions
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoHistory = 0
|
||||||
|
CharHistory = 1
|
||||||
|
StanzaHistory = 2
|
||||||
|
SecondsHistory = 3
|
||||||
|
SinceHistory = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send sends room topic wrapped inside an XMPP message stanza body.
|
||||||
|
func (c *Client) SendTopic(chat Chat) (n int, err error) {
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>\n",
|
||||||
|
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = c.jid
|
||||||
|
}
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<history maxchars='0'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xep-0045 7.2
|
||||||
|
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = c.jid
|
||||||
|
}
|
||||||
|
switch history_type {
|
||||||
|
case NoHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s' />"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC)
|
||||||
|
case CharHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<history maxchars='%d'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, history)
|
||||||
|
case StanzaHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<history maxstanzas='%d'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, history)
|
||||||
|
case SecondsHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<history seconds='%d'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, history)
|
||||||
|
case SinceHistory:
|
||||||
|
if history_date != nil {
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<history since='%s'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, history_date.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("unknown history option")
|
||||||
|
}
|
||||||
|
|
||||||
|
// xep-0045 7.2.6
|
||||||
|
func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = c.jid
|
||||||
|
}
|
||||||
|
switch history_type {
|
||||||
|
case NoHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<password>%s</password>"+
|
||||||
|
"</x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, xmlEscape(password))
|
||||||
|
case CharHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<password>%s</password>"+
|
||||||
|
"<history maxchars='%d'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, xmlEscape(password), history)
|
||||||
|
case StanzaHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<password>%s</password>"+
|
||||||
|
"<history maxstanzas='%d'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, xmlEscape(password), history)
|
||||||
|
case SecondsHistory:
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<password>%s</password>"+
|
||||||
|
"<history seconds='%d'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, xmlEscape(password), history)
|
||||||
|
case SinceHistory:
|
||||||
|
if history_date != nil {
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<password>%s</password>"+
|
||||||
|
"<history since='%s'/></x>"+
|
||||||
|
"</presence>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), XMPPNS_MUC, xmlEscape(password), history_date.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("unknown history option")
|
||||||
|
}
|
||||||
|
|
||||||
|
// xep-0045 7.14
|
||||||
|
func (c *Client) LeaveMUC(jid string) (n int, err error) {
|
||||||
|
return fmt.Fprintf(c.stanzaWriter, "<presence from='%s' to='%s' type='unavailable' />\n",
|
||||||
|
c.jid, xmlEscape(jid))
|
||||||
|
}
|
||||||
57
vendor/github.com/xmppo/go-xmpp/xmpp_ping.go
generated
vendored
Normal file
57
vendor/github.com/xmppo/go-xmpp/xmpp_ping.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) PingC2S(jid, server string) error {
|
||||||
|
if jid == "" {
|
||||||
|
jid = c.jid
|
||||||
|
}
|
||||||
|
if server == "" {
|
||||||
|
server = c.domain
|
||||||
|
}
|
||||||
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='%s' type='get'>"+
|
||||||
|
"<ping xmlns='urn:xmpp:ping'/>"+
|
||||||
|
"</iq>\n",
|
||||||
|
xmlEscape(jid), xmlEscape(server), getUUID())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PingS2S(fromServer, toServer string) error {
|
||||||
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='%s' type='get'>"+
|
||||||
|
"<ping xmlns='urn:xmpp:ping'/>"+
|
||||||
|
"</iq>\n",
|
||||||
|
xmlEscape(fromServer), xmlEscape(toServer), getUUID())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendResultPing(id, toServer string) error {
|
||||||
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq type='result' to='%s' id='%s'/>\n",
|
||||||
|
xmlEscape(toServer), xmlEscape(id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendPeriodicPings() {
|
||||||
|
for range c.periodicPingTicker.C {
|
||||||
|
// Reset ticker for periodic pings if configured.
|
||||||
|
if c.periodicPings {
|
||||||
|
c.periodicPingTicker.Reset(c.periodicPingPeriod)
|
||||||
|
}
|
||||||
|
c.periodicPingID = getUUID()
|
||||||
|
c.periodicPingReply = false
|
||||||
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='%s' type='get'>"+
|
||||||
|
"<ping xmlns='urn:xmpp:ping'/></iq>\n",
|
||||||
|
xmlEscape(c.jid), xmlEscape(c.domain), c.periodicPingID)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
time.Sleep(c.periodicPingTimeout)
|
||||||
|
if !c.periodicPingReply {
|
||||||
|
c.shutdown = true
|
||||||
|
fmt.Fprintf(c.stanzaWriter, "</stream:stream>\n")
|
||||||
|
c.conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub" //nolint:revive
|
|
||||||
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event" //nolint:revive
|
|
||||||
)
|
|
||||||
|
|
||||||
type clientPubsubItem struct {
|
type clientPubsubItem struct {
|
||||||
XMLName xml.Name `xml:"item"`
|
XMLName xml.Name `xml:"item"`
|
||||||
ID string `xml:"id,attr"`
|
ID string `xml:"id,attr"`
|
||||||
@@ -22,11 +17,6 @@ type clientPubsubItems struct {
|
|||||||
Items []clientPubsubItem `xml:"item"`
|
Items []clientPubsubItem `xml:"item"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientPubsub struct {
|
|
||||||
XMLName xml.Name `xml:"pubsub"`
|
|
||||||
Items clientPubsubItems `xml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientPubsubEvent struct {
|
type clientPubsubEvent struct {
|
||||||
XMLName xml.Name `xml:"event"`
|
XMLName xml.Name `xml:"event"`
|
||||||
XMLNS string `xml:"xmlns,attr"`
|
XMLNS string `xml:"xmlns,attr"`
|
||||||
@@ -67,7 +57,7 @@ type PubsubItems struct {
|
|||||||
Items []PubsubItem
|
Items []PubsubItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts []clientPubsubItem to []PubsubItem.
|
// Converts []clientPubsubItem to []PubsubItem
|
||||||
func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem {
|
func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem {
|
||||||
var tmp []PubsubItem
|
var tmp []PubsubItem
|
||||||
for _, i := range items {
|
for _, i := range items {
|
||||||
@@ -96,7 +86,6 @@ func pubsubSubscriptionStanza(node, jid string) string {
|
|||||||
body := fmt.Sprintf("<subscribe node='%s' jid='%s'/>",
|
body := fmt.Sprintf("<subscribe node='%s' jid='%s'/>",
|
||||||
xmlEscape(node),
|
xmlEscape(node),
|
||||||
xmlEscape(jid))
|
xmlEscape(jid))
|
||||||
|
|
||||||
return pubsubStanza(body)
|
return pubsubStanza(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,34 +93,43 @@ func pubsubUnsubscriptionStanza(node, jid string) string {
|
|||||||
body := fmt.Sprintf("<unsubscribe node='%s' jid='%s'/>",
|
body := fmt.Sprintf("<unsubscribe node='%s' jid='%s'/>",
|
||||||
xmlEscape(node),
|
xmlEscape(node),
|
||||||
xmlEscape(jid))
|
xmlEscape(jid))
|
||||||
|
|
||||||
return pubsubStanza(body)
|
return pubsubStanza(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PubsubSubscribeNode(node, jid string) (string, error) {
|
func (c *Client) PubsubSubscribeNode(node, jid string) error {
|
||||||
return c.RawInformation(c.jid,
|
id := getUUID()
|
||||||
|
c.subIDs = append(c.subIDs, id)
|
||||||
|
_, err := c.RawInformation(c.jid,
|
||||||
jid,
|
jid,
|
||||||
"sub1",
|
id,
|
||||||
"set",
|
"set",
|
||||||
pubsubSubscriptionStanza(node, c.jid))
|
pubsubSubscriptionStanza(node, c.jid))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PubsubUnsubscribeNode(node, jid string) (string, error) {
|
func (c *Client) PubsubUnsubscribeNode(node, jid string) error {
|
||||||
return c.RawInformation(c.jid,
|
id := getUUID()
|
||||||
|
c.unsubIDs = append(c.unsubIDs, id)
|
||||||
|
_, err := c.RawInformation(c.jid,
|
||||||
jid,
|
jid,
|
||||||
"unsub1",
|
id,
|
||||||
"set",
|
"set",
|
||||||
pubsubUnsubscriptionStanza(node, c.jid))
|
pubsubUnsubscriptionStanza(node, c.jid))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PubsubRequestLastItems(node, jid string) (string, error) {
|
func (c *Client) PubsubRequestLastItems(node, jid string) error {
|
||||||
|
id := getUUID()
|
||||||
|
c.itemsIDs = append(c.itemsIDs, id)
|
||||||
body := fmt.Sprintf("<items node='%s'/>", node)
|
body := fmt.Sprintf("<items node='%s'/>", node)
|
||||||
|
_, err := c.RawInformation(c.jid, jid, id, "get", pubsubStanza(body))
|
||||||
return c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PubsubRequestItem(node, jid, id string) (string, error) {
|
func (c *Client) PubsubRequestItem(node, jid, id string) error {
|
||||||
|
stanzaID := getUUID()
|
||||||
|
c.itemsIDs = append(c.itemsIDs, stanzaID)
|
||||||
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
|
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
|
||||||
|
_, err := c.RawInformation(c.jid, jid, stanzaID, "get", pubsubStanza(body))
|
||||||
return c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
|
return err
|
||||||
}
|
}
|
||||||
30
vendor/github.com/xmppo/go-xmpp/xmpp_subscription.go
generated
vendored
Normal file
30
vendor/github.com/xmppo/go-xmpp/xmpp_subscription.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) ApproveSubscription(jid string) {
|
||||||
|
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribed'/>\n",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RevokeSubscription(jid string) {
|
||||||
|
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='unsubscribed'/>\n",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RevertSubscription instead.
|
||||||
|
func (c *Client) RetrieveSubscription(jid string) {
|
||||||
|
c.RevertSubscription(jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RevertSubscription(jid string) {
|
||||||
|
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>\n",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RequestSubscription(jid string) {
|
||||||
|
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribe'/>\n",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
Copyright 2009 The Go Authors.
|
|
||||||
|
|
||||||
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 LLC 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.
|
|
||||||
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google 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,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
||||||
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
@@ -1,77 +0,0 @@
|
|||||||
// 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 (
|
|
||||||
"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]
|
|
||||||
}
|
|
||||||
70
vendor/golang.org/x/net/context/context.go
generated
vendored
70
vendor/golang.org/x/net/context/context.go
generated
vendored
@@ -2,44 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package context defines the Context type, which carries deadlines,
|
// Package context has been superseded by the standard library [context] package.
|
||||||
// cancellation signals, and other request-scoped values across API boundaries
|
|
||||||
// and between processes.
|
|
||||||
// As of Go 1.7 this package is available in the standard library under the
|
|
||||||
// name [context], and migrating to it can be done automatically with [go fix].
|
|
||||||
//
|
//
|
||||||
// Incoming requests to a server should create a [Context], and outgoing
|
// Deprecated: Use the standard library context package instead.
|
||||||
// calls to servers should accept a Context. The chain of function
|
|
||||||
// calls between them must propagate the Context, optionally replacing
|
|
||||||
// it with a derived Context created using [WithCancel], [WithDeadline],
|
|
||||||
// [WithTimeout], or [WithValue].
|
|
||||||
//
|
|
||||||
// Programs that use Contexts should follow these rules to keep interfaces
|
|
||||||
// consistent across packages and enable static analysis tools to check context
|
|
||||||
// propagation:
|
|
||||||
//
|
|
||||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
|
||||||
// explicitly to each function that needs it. This is discussed further in
|
|
||||||
// https://go.dev/blog/context-and-structs. The Context should be the first
|
|
||||||
// parameter, typically named ctx:
|
|
||||||
//
|
|
||||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
|
||||||
// // ... use ctx ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO]
|
|
||||||
// if you are unsure about which Context to use.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
//
|
|
||||||
// The same Context may be passed to functions running in different goroutines;
|
|
||||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
|
||||||
//
|
|
||||||
// See https://go.dev/blog/context for example code for a server that uses
|
|
||||||
// Contexts.
|
|
||||||
//
|
|
||||||
// [go fix]: https://go.dev/cmd/go#hdr-Update_packages_to_use_new_APIs
|
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -51,36 +16,37 @@ import (
|
|||||||
// API boundaries.
|
// API boundaries.
|
||||||
//
|
//
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
type Context = context.Context
|
type Context = context.Context
|
||||||
|
|
||||||
// Canceled is the error returned by [Context.Err] when the context is canceled
|
// Canceled is the error returned by [Context.Err] when the context is canceled
|
||||||
// for some reason other than its deadline passing.
|
// for some reason other than its deadline passing.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
var Canceled = context.Canceled
|
var Canceled = context.Canceled
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled
|
// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled
|
||||||
// due to its deadline passing.
|
// due to its deadline passing.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
var DeadlineExceeded = context.DeadlineExceeded
|
var DeadlineExceeded = context.DeadlineExceeded
|
||||||
|
|
||||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||||
// values, and has no deadline. It is typically used by the main function,
|
// values, and has no deadline. It is typically used by the main function,
|
||||||
// initialization, and tests, and as the top-level Context for incoming
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
// requests.
|
// requests.
|
||||||
func Background() Context {
|
//
|
||||||
return background
|
//go:fix inline
|
||||||
}
|
func Background() Context { return context.Background() }
|
||||||
|
|
||||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||||
// it's unclear which Context to use or it is not yet available (because the
|
// it's unclear which Context to use or it is not yet available (because the
|
||||||
// surrounding function has not yet been extended to accept a Context
|
// surrounding function has not yet been extended to accept a Context
|
||||||
// parameter).
|
// parameter).
|
||||||
func TODO() Context {
|
//
|
||||||
return todo
|
//go:fix inline
|
||||||
}
|
func TODO() Context { return context.TODO() }
|
||||||
|
|
||||||
var (
|
|
||||||
background = context.Background()
|
|
||||||
todo = context.TODO()
|
|
||||||
)
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
// A CancelFunc does not wait for the work to stop.
|
// A CancelFunc does not wait for the work to stop.
|
||||||
@@ -95,6 +61,8 @@ type CancelFunc = context.CancelFunc
|
|||||||
//
|
//
|
||||||
// Canceling this context releases resources associated with it, so code should
|
// Canceling this context releases resources associated with it, so code should
|
||||||
// call cancel as soon as the operations running in this [Context] complete.
|
// call cancel as soon as the operations running in this [Context] complete.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
return context.WithCancel(parent)
|
return context.WithCancel(parent)
|
||||||
}
|
}
|
||||||
@@ -108,6 +76,8 @@ func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|||||||
//
|
//
|
||||||
// Canceling this context releases resources associated with it, so code should
|
// Canceling this context releases resources associated with it, so code should
|
||||||
// call cancel as soon as the operations running in this [Context] complete.
|
// call cancel as soon as the operations running in this [Context] complete.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
|
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
|
||||||
return context.WithDeadline(parent, d)
|
return context.WithDeadline(parent, d)
|
||||||
}
|
}
|
||||||
@@ -122,6 +92,8 @@ func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
|
|||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
// return slowOperation(ctx)
|
// return slowOperation(ctx)
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
return context.WithTimeout(parent, timeout)
|
return context.WithTimeout(parent, timeout)
|
||||||
}
|
}
|
||||||
@@ -139,6 +111,8 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|||||||
// interface{}, context keys often have concrete type
|
// interface{}, context keys often have concrete type
|
||||||
// struct{}. Alternatively, exported context key variables' static
|
// struct{}. Alternatively, exported context key variables' static
|
||||||
// type should be a pointer or interface.
|
// type should be a pointer or interface.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func WithValue(parent Context, key, val interface{}) Context {
|
func WithValue(parent Context, key, val interface{}) Context {
|
||||||
return context.WithValue(parent, key, val)
|
return context.WithValue(parent, key, val)
|
||||||
}
|
}
|
||||||
|
|||||||
168
vendor/golang.org/x/net/internal/socks/client.go
generated
vendored
Normal file
168
vendor/golang.org/x/net/internal/socks/client.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
// Copyright 2018 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 socks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
noDeadline = time.Time{}
|
||||||
|
aLongTimeAgo = time.Unix(1, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Dialer) connect(ctx context.Context, c net.Conn, address string) (_ net.Addr, ctxErr error) {
|
||||||
|
host, port, err := splitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() {
|
||||||
|
c.SetDeadline(deadline)
|
||||||
|
defer c.SetDeadline(noDeadline)
|
||||||
|
}
|
||||||
|
if ctx != context.Background() {
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer func() {
|
||||||
|
close(done)
|
||||||
|
if ctxErr == nil {
|
||||||
|
ctxErr = <-errCh
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
c.SetDeadline(aLongTimeAgo)
|
||||||
|
errCh <- ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
errCh <- nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 0, 6+len(host)) // the size here is just an estimate
|
||||||
|
b = append(b, Version5)
|
||||||
|
if len(d.AuthMethods) == 0 || d.Authenticate == nil {
|
||||||
|
b = append(b, 1, byte(AuthMethodNotRequired))
|
||||||
|
} else {
|
||||||
|
ams := d.AuthMethods
|
||||||
|
if len(ams) > 255 {
|
||||||
|
return nil, errors.New("too many authentication methods")
|
||||||
|
}
|
||||||
|
b = append(b, byte(len(ams)))
|
||||||
|
for _, am := range ams {
|
||||||
|
b = append(b, byte(am))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ctxErr = c.Write(b); ctxErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ctxErr = io.ReadFull(c, b[:2]); ctxErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b[0] != Version5 {
|
||||||
|
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0])))
|
||||||
|
}
|
||||||
|
am := AuthMethod(b[1])
|
||||||
|
if am == AuthMethodNoAcceptableMethods {
|
||||||
|
return nil, errors.New("no acceptable authentication methods")
|
||||||
|
}
|
||||||
|
if d.Authenticate != nil {
|
||||||
|
if ctxErr = d.Authenticate(ctx, c, am); ctxErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b = b[:0]
|
||||||
|
b = append(b, Version5, byte(d.cmd), 0)
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
b = append(b, AddrTypeIPv4)
|
||||||
|
b = append(b, ip4...)
|
||||||
|
} else if ip6 := ip.To16(); ip6 != nil {
|
||||||
|
b = append(b, AddrTypeIPv6)
|
||||||
|
b = append(b, ip6...)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("unknown address type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return nil, errors.New("FQDN too long")
|
||||||
|
}
|
||||||
|
b = append(b, AddrTypeFQDN)
|
||||||
|
b = append(b, byte(len(host)))
|
||||||
|
b = append(b, host...)
|
||||||
|
}
|
||||||
|
b = append(b, byte(port>>8), byte(port))
|
||||||
|
if _, ctxErr = c.Write(b); ctxErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ctxErr = io.ReadFull(c, b[:4]); ctxErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b[0] != Version5 {
|
||||||
|
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0])))
|
||||||
|
}
|
||||||
|
if cmdErr := Reply(b[1]); cmdErr != StatusSucceeded {
|
||||||
|
return nil, errors.New("unknown error " + cmdErr.String())
|
||||||
|
}
|
||||||
|
if b[2] != 0 {
|
||||||
|
return nil, errors.New("non-zero reserved field")
|
||||||
|
}
|
||||||
|
l := 2
|
||||||
|
var a Addr
|
||||||
|
switch b[3] {
|
||||||
|
case AddrTypeIPv4:
|
||||||
|
l += net.IPv4len
|
||||||
|
a.IP = make(net.IP, net.IPv4len)
|
||||||
|
case AddrTypeIPv6:
|
||||||
|
l += net.IPv6len
|
||||||
|
a.IP = make(net.IP, net.IPv6len)
|
||||||
|
case AddrTypeFQDN:
|
||||||
|
if _, err := io.ReadFull(c, b[:1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l += int(b[0])
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown address type " + strconv.Itoa(int(b[3])))
|
||||||
|
}
|
||||||
|
if cap(b) < l {
|
||||||
|
b = make([]byte, l)
|
||||||
|
} else {
|
||||||
|
b = b[:l]
|
||||||
|
}
|
||||||
|
if _, ctxErr = io.ReadFull(c, b); ctxErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if a.IP != nil {
|
||||||
|
copy(a.IP, b)
|
||||||
|
} else {
|
||||||
|
a.Name = string(b[:len(b)-2])
|
||||||
|
}
|
||||||
|
a.Port = int(b[len(b)-2])<<8 | int(b[len(b)-1])
|
||||||
|
return &a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitHostPort(address string) (string, int, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
portnum, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
if 1 > portnum || portnum > 0xffff {
|
||||||
|
return "", 0, errors.New("port number out of range " + port)
|
||||||
|
}
|
||||||
|
return host, portnum, nil
|
||||||
|
}
|
||||||
317
vendor/golang.org/x/net/internal/socks/socks.go
generated
vendored
Normal file
317
vendor/golang.org/x/net/internal/socks/socks.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
// Copyright 2018 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 socks provides a SOCKS version 5 client implementation.
|
||||||
|
//
|
||||||
|
// SOCKS protocol version 5 is defined in RFC 1928.
|
||||||
|
// Username/Password authentication for SOCKS version 5 is defined in
|
||||||
|
// RFC 1929.
|
||||||
|
package socks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Command represents a SOCKS command.
|
||||||
|
type Command int
|
||||||
|
|
||||||
|
func (cmd Command) String() string {
|
||||||
|
switch cmd {
|
||||||
|
case CmdConnect:
|
||||||
|
return "socks connect"
|
||||||
|
case cmdBind:
|
||||||
|
return "socks bind"
|
||||||
|
default:
|
||||||
|
return "socks " + strconv.Itoa(int(cmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AuthMethod represents a SOCKS authentication method.
|
||||||
|
type AuthMethod int
|
||||||
|
|
||||||
|
// A Reply represents a SOCKS command reply code.
|
||||||
|
type Reply int
|
||||||
|
|
||||||
|
func (code Reply) String() string {
|
||||||
|
switch code {
|
||||||
|
case StatusSucceeded:
|
||||||
|
return "succeeded"
|
||||||
|
case 0x01:
|
||||||
|
return "general SOCKS server failure"
|
||||||
|
case 0x02:
|
||||||
|
return "connection not allowed by ruleset"
|
||||||
|
case 0x03:
|
||||||
|
return "network unreachable"
|
||||||
|
case 0x04:
|
||||||
|
return "host unreachable"
|
||||||
|
case 0x05:
|
||||||
|
return "connection refused"
|
||||||
|
case 0x06:
|
||||||
|
return "TTL expired"
|
||||||
|
case 0x07:
|
||||||
|
return "command not supported"
|
||||||
|
case 0x08:
|
||||||
|
return "address type not supported"
|
||||||
|
default:
|
||||||
|
return "unknown code: " + strconv.Itoa(int(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire protocol constants.
|
||||||
|
const (
|
||||||
|
Version5 = 0x05
|
||||||
|
|
||||||
|
AddrTypeIPv4 = 0x01
|
||||||
|
AddrTypeFQDN = 0x03
|
||||||
|
AddrTypeIPv6 = 0x04
|
||||||
|
|
||||||
|
CmdConnect Command = 0x01 // establishes an active-open forward proxy connection
|
||||||
|
cmdBind Command = 0x02 // establishes a passive-open forward proxy connection
|
||||||
|
|
||||||
|
AuthMethodNotRequired AuthMethod = 0x00 // no authentication required
|
||||||
|
AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password
|
||||||
|
AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods
|
||||||
|
|
||||||
|
StatusSucceeded Reply = 0x00
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Addr represents a SOCKS-specific address.
|
||||||
|
// Either Name or IP is used exclusively.
|
||||||
|
type Addr struct {
|
||||||
|
Name string // fully-qualified domain name
|
||||||
|
IP net.IP
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Addr) Network() string { return "socks" }
|
||||||
|
|
||||||
|
func (a *Addr) String() string {
|
||||||
|
if a == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
port := strconv.Itoa(a.Port)
|
||||||
|
if a.IP == nil {
|
||||||
|
return net.JoinHostPort(a.Name, port)
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(a.IP.String(), port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Conn represents a forward proxy connection.
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
boundAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoundAddr returns the address assigned by the proxy server for
|
||||||
|
// connecting to the command target address from the proxy server.
|
||||||
|
func (c *Conn) BoundAddr() net.Addr {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.boundAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer holds SOCKS-specific options.
|
||||||
|
type Dialer struct {
|
||||||
|
cmd Command // either CmdConnect or cmdBind
|
||||||
|
proxyNetwork string // network between a proxy server and a client
|
||||||
|
proxyAddress string // proxy server address
|
||||||
|
|
||||||
|
// ProxyDial specifies the optional dial function for
|
||||||
|
// establishing the transport connection.
|
||||||
|
ProxyDial func(context.Context, string, string) (net.Conn, error)
|
||||||
|
|
||||||
|
// AuthMethods specifies the list of request authentication
|
||||||
|
// methods.
|
||||||
|
// If empty, SOCKS client requests only AuthMethodNotRequired.
|
||||||
|
AuthMethods []AuthMethod
|
||||||
|
|
||||||
|
// Authenticate specifies the optional authentication
|
||||||
|
// function. It must be non-nil when AuthMethods is not empty.
|
||||||
|
// It must return an error when the authentication is failed.
|
||||||
|
Authenticate func(context.Context, io.ReadWriter, AuthMethod) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext connects to the provided address on the provided
|
||||||
|
// network.
|
||||||
|
//
|
||||||
|
// The returned error value may be a net.OpError. When the Op field of
|
||||||
|
// net.OpError contains "socks", the Source field contains a proxy
|
||||||
|
// server address and the Addr field contains a command target
|
||||||
|
// address.
|
||||||
|
//
|
||||||
|
// See func Dial of the net package of standard library for a
|
||||||
|
// description of the network and address parameters.
|
||||||
|
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
if err := d.validateTarget(network, address); err != nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var c net.Conn
|
||||||
|
if d.ProxyDial != nil {
|
||||||
|
c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)
|
||||||
|
} else {
|
||||||
|
var dd net.Dialer
|
||||||
|
c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
a, err := d.connect(ctx, c, address)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
return &Conn{Conn: c, boundAddr: a}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialWithConn initiates a connection from SOCKS server to the target
|
||||||
|
// network and address using the connection c that is already
|
||||||
|
// connected to the SOCKS server.
|
||||||
|
//
|
||||||
|
// It returns the connection's local address assigned by the SOCKS
|
||||||
|
// server.
|
||||||
|
func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) {
|
||||||
|
if err := d.validateTarget(network, address); err != nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
|
||||||
|
}
|
||||||
|
a, err := d.connect(ctx, c, address)
|
||||||
|
if err != nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the provided address on the provided network.
|
||||||
|
//
|
||||||
|
// Unlike DialContext, it returns a raw transport connection instead
|
||||||
|
// of a forward proxy connection.
|
||||||
|
//
|
||||||
|
// Deprecated: Use DialContext or DialWithConn instead.
|
||||||
|
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
|
||||||
|
if err := d.validateTarget(network, address); err != nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var c net.Conn
|
||||||
|
if d.ProxyDial != nil {
|
||||||
|
c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress)
|
||||||
|
} else {
|
||||||
|
c, err = net.Dial(d.proxyNetwork, d.proxyAddress)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
proxy, dst, _ := d.pathAddrs(address)
|
||||||
|
return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
|
||||||
|
}
|
||||||
|
if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dialer) validateTarget(network, address string) error {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
default:
|
||||||
|
return errors.New("network not implemented")
|
||||||
|
}
|
||||||
|
switch d.cmd {
|
||||||
|
case CmdConnect, cmdBind:
|
||||||
|
default:
|
||||||
|
return errors.New("command not implemented")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
|
||||||
|
for i, s := range []string{d.proxyAddress, address} {
|
||||||
|
host, port, err := splitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
a := &Addr{Port: port}
|
||||||
|
a.IP = net.ParseIP(host)
|
||||||
|
if a.IP == nil {
|
||||||
|
a.Name = host
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
proxy = a
|
||||||
|
} else {
|
||||||
|
dst = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDialer returns a new Dialer that dials through the provided
|
||||||
|
// proxy server's network and address.
|
||||||
|
func NewDialer(network, address string) *Dialer {
|
||||||
|
return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
authUsernamePasswordVersion = 0x01
|
||||||
|
authStatusSucceeded = 0x00
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsernamePassword are the credentials for the username/password
|
||||||
|
// authentication method.
|
||||||
|
type UsernamePassword struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate authenticates a pair of username and password with the
|
||||||
|
// proxy server.
|
||||||
|
func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {
|
||||||
|
switch auth {
|
||||||
|
case AuthMethodNotRequired:
|
||||||
|
return nil
|
||||||
|
case AuthMethodUsernamePassword:
|
||||||
|
if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) > 255 {
|
||||||
|
return errors.New("invalid username/password")
|
||||||
|
}
|
||||||
|
b := []byte{authUsernamePasswordVersion}
|
||||||
|
b = append(b, byte(len(up.Username)))
|
||||||
|
b = append(b, up.Username...)
|
||||||
|
b = append(b, byte(len(up.Password)))
|
||||||
|
b = append(b, up.Password...)
|
||||||
|
// TODO(mikio): handle IO deadlines and cancellation if
|
||||||
|
// necessary
|
||||||
|
if _, err := rw.Write(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(rw, b[:2]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b[0] != authUsernamePasswordVersion {
|
||||||
|
return errors.New("invalid username/password version")
|
||||||
|
}
|
||||||
|
if b[1] != authStatusSucceeded {
|
||||||
|
return errors.New("username/password authentication failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))
|
||||||
|
}
|
||||||
54
vendor/golang.org/x/net/proxy/dial.go
generated
vendored
Normal file
54
vendor/golang.org/x/net/proxy/dial.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2019 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 proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ContextDialer dials using a context.
|
||||||
|
type ContextDialer interface {
|
||||||
|
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial works like DialContext on net.Dialer but using a dialer returned by FromEnvironment.
|
||||||
|
//
|
||||||
|
// The passed ctx is only used for returning the Conn, not the lifetime of the Conn.
|
||||||
|
//
|
||||||
|
// Custom dialers (registered via RegisterDialerType) that do not implement ContextDialer
|
||||||
|
// can leak a goroutine for as long as it takes the underlying Dialer implementation to timeout.
|
||||||
|
//
|
||||||
|
// A Conn returned from a successful Dial after the context has been cancelled will be immediately closed.
|
||||||
|
func Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
d := FromEnvironment()
|
||||||
|
if xd, ok := d.(ContextDialer); ok {
|
||||||
|
return xd.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
return dialContext(ctx, d, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: this can leak a goroutine for as long as the underlying Dialer implementation takes to timeout
|
||||||
|
// A Conn returned from a successful Dial after the context has been cancelled will be immediately closed.
|
||||||
|
func dialContext(ctx context.Context, d Dialer, network, address string) (net.Conn, error) {
|
||||||
|
var (
|
||||||
|
conn net.Conn
|
||||||
|
done = make(chan struct{}, 1)
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
conn, err = d.Dial(network, address)
|
||||||
|
close(done)
|
||||||
|
if conn != nil && ctx.Err() != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err = ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
31
vendor/golang.org/x/net/proxy/direct.go
generated
vendored
Normal file
31
vendor/golang.org/x/net/proxy/direct.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2011 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 proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type direct struct{}
|
||||||
|
|
||||||
|
// Direct implements Dialer by making network connections directly using net.Dial or net.DialContext.
|
||||||
|
var Direct = direct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Dialer = Direct
|
||||||
|
_ ContextDialer = Direct
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dial directly invokes net.Dial with the supplied parameters.
|
||||||
|
func (direct) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext instantiates a net.Dialer and invokes its DialContext receiver with the supplied parameters.
|
||||||
|
func (direct) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
var d net.Dialer
|
||||||
|
return d.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
153
vendor/golang.org/x/net/proxy/per_host.go
generated
vendored
Normal file
153
vendor/golang.org/x/net/proxy/per_host.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2011 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 proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A PerHost directs connections to a default Dialer unless the host name
|
||||||
|
// requested matches one of a number of exceptions.
|
||||||
|
type PerHost struct {
|
||||||
|
def, bypass Dialer
|
||||||
|
|
||||||
|
bypassNetworks []*net.IPNet
|
||||||
|
bypassIPs []net.IP
|
||||||
|
bypassZones []string
|
||||||
|
bypassHosts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||||
|
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||||
|
// the configured rules.
|
||||||
|
func NewPerHost(defaultDialer, bypass Dialer) *PerHost {
|
||||||
|
return &PerHost{
|
||||||
|
def: defaultDialer,
|
||||||
|
bypass: bypass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network through either
|
||||||
|
// defaultDialer or bypass.
|
||||||
|
func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.dialerForRequest(host).Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext connects to the address addr on the given network through either
|
||||||
|
// defaultDialer or bypass.
|
||||||
|
func (p *PerHost) DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := p.dialerForRequest(host)
|
||||||
|
if x, ok := d.(ContextDialer); ok {
|
||||||
|
return x.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
return dialContext(ctx, d, network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PerHost) dialerForRequest(host string) Dialer {
|
||||||
|
if nip, err := netip.ParseAddr(host); err == nil {
|
||||||
|
ip := net.IP(nip.AsSlice())
|
||||||
|
for _, net := range p.bypassNetworks {
|
||||||
|
if net.Contains(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassIP := range p.bypassIPs {
|
||||||
|
if bypassIP.Equal(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range p.bypassZones {
|
||||||
|
if strings.HasSuffix(host, zone) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
if host == zone[1:] {
|
||||||
|
// For a zone ".example.com", we match "example.com"
|
||||||
|
// too.
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassHost := range p.bypassHosts {
|
||||||
|
if bypassHost == host {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromString parses a string that contains comma-separated values
|
||||||
|
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||||
|
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||||
|
// (localhost). A best effort is made to parse the string and errors are
|
||||||
|
// ignored.
|
||||||
|
func (p *PerHost) AddFromString(s string) {
|
||||||
|
hosts := strings.Split(s, ",")
|
||||||
|
for _, host := range hosts {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if len(host) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "/") {
|
||||||
|
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||||
|
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||||
|
p.AddNetwork(net)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nip, err := netip.ParseAddr(host); err == nil {
|
||||||
|
p.AddIP(net.IP(nip.AsSlice()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
p.AddZone(host[1:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.AddHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match an IP.
|
||||||
|
func (p *PerHost) AddIP(ip net.IP) {
|
||||||
|
p.bypassIPs = append(p.bypassIPs, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match.
|
||||||
|
func (p *PerHost) AddNetwork(net *net.IPNet) {
|
||||||
|
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||||
|
// "example.com" matches "example.com" and all of its subdomains.
|
||||||
|
func (p *PerHost) AddZone(zone string) {
|
||||||
|
zone = strings.TrimSuffix(zone, ".")
|
||||||
|
if !strings.HasPrefix(zone, ".") {
|
||||||
|
zone = "." + zone
|
||||||
|
}
|
||||||
|
p.bypassZones = append(p.bypassZones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHost specifies a host name that will use the bypass proxy.
|
||||||
|
func (p *PerHost) AddHost(host string) {
|
||||||
|
host = strings.TrimSuffix(host, ".")
|
||||||
|
p.bypassHosts = append(p.bypassHosts, host)
|
||||||
|
}
|
||||||
149
vendor/golang.org/x/net/proxy/proxy.go
generated
vendored
Normal file
149
vendor/golang.org/x/net/proxy/proxy.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2011 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 proxy provides support for a variety of protocols to proxy network
|
||||||
|
// data.
|
||||||
|
package proxy // import "golang.org/x/net/proxy"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Dialer is a means to establish a connection.
|
||||||
|
// Custom dialers should also implement ContextDialer.
|
||||||
|
type Dialer interface {
|
||||||
|
// Dial connects to the given address via the proxy.
|
||||||
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth contains authentication parameters that specific Dialers may require.
|
||||||
|
type Auth struct {
|
||||||
|
User, Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnvironment returns the dialer specified by the proxy-related
|
||||||
|
// variables in the environment and makes underlying connections
|
||||||
|
// directly.
|
||||||
|
func FromEnvironment() Dialer {
|
||||||
|
return FromEnvironmentUsing(Direct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnvironmentUsing returns the dialer specify by the proxy-related
|
||||||
|
// variables in the environment and makes underlying connections
|
||||||
|
// using the provided forwarding Dialer (for instance, a *net.Dialer
|
||||||
|
// with desired configuration).
|
||||||
|
func FromEnvironmentUsing(forward Dialer) Dialer {
|
||||||
|
allProxy := allProxyEnv.Get()
|
||||||
|
if len(allProxy) == 0 {
|
||||||
|
return forward
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(allProxy)
|
||||||
|
if err != nil {
|
||||||
|
return forward
|
||||||
|
}
|
||||||
|
proxy, err := FromURL(proxyURL, forward)
|
||||||
|
if err != nil {
|
||||||
|
return forward
|
||||||
|
}
|
||||||
|
|
||||||
|
noProxy := noProxyEnv.Get()
|
||||||
|
if len(noProxy) == 0 {
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
perHost := NewPerHost(proxy, forward)
|
||||||
|
perHost.AddFromString(noProxy)
|
||||||
|
return perHost
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||||
|
// from a URL with such a scheme.
|
||||||
|
var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error)
|
||||||
|
|
||||||
|
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||||
|
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||||
|
// by FromURL.
|
||||||
|
func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) {
|
||||||
|
if proxySchemes == nil {
|
||||||
|
proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error))
|
||||||
|
}
|
||||||
|
proxySchemes[scheme] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromURL returns a Dialer given a URL specification and an underlying
|
||||||
|
// Dialer for it to make network requests.
|
||||||
|
func FromURL(u *url.URL, forward Dialer) (Dialer, error) {
|
||||||
|
var auth *Auth
|
||||||
|
if u.User != nil {
|
||||||
|
auth = new(Auth)
|
||||||
|
auth.User = u.User.Username()
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
auth.Password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "socks5", "socks5h":
|
||||||
|
addr := u.Hostname()
|
||||||
|
port := u.Port()
|
||||||
|
if port == "" {
|
||||||
|
port = "1080"
|
||||||
|
}
|
||||||
|
return SOCKS5("tcp", net.JoinHostPort(addr, port), auth, forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||||
|
// was registered by another package.
|
||||||
|
if proxySchemes != nil {
|
||||||
|
if f, ok := proxySchemes[u.Scheme]; ok {
|
||||||
|
return f(u, forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
allProxyEnv = &envOnce{
|
||||||
|
names: []string{"ALL_PROXY", "all_proxy"},
|
||||||
|
}
|
||||||
|
noProxyEnv = &envOnce{
|
||||||
|
names: []string{"NO_PROXY", "no_proxy"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// envOnce looks up an environment variable (optionally by multiple
|
||||||
|
// names) once. It mitigates expensive lookups on some platforms
|
||||||
|
// (e.g. Windows).
|
||||||
|
// (Borrowed from net/http/transport.go)
|
||||||
|
type envOnce struct {
|
||||||
|
names []string
|
||||||
|
once sync.Once
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envOnce) Get() string {
|
||||||
|
e.once.Do(e.init)
|
||||||
|
return e.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envOnce) init() {
|
||||||
|
for _, n := range e.names {
|
||||||
|
e.val = os.Getenv(n)
|
||||||
|
if e.val != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset is used by tests
|
||||||
|
func (e *envOnce) reset() {
|
||||||
|
e.once = sync.Once{}
|
||||||
|
e.val = ""
|
||||||
|
}
|
||||||
42
vendor/golang.org/x/net/proxy/socks5.go
generated
vendored
Normal file
42
vendor/golang.org/x/net/proxy/socks5.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2011 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 proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/socks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given
|
||||||
|
// address with an optional username and password.
|
||||||
|
// See RFC 1928 and RFC 1929.
|
||||||
|
func SOCKS5(network, address string, auth *Auth, forward Dialer) (Dialer, error) {
|
||||||
|
d := socks.NewDialer(network, address)
|
||||||
|
if forward != nil {
|
||||||
|
if f, ok := forward.(ContextDialer); ok {
|
||||||
|
d.ProxyDial = func(ctx context.Context, network string, address string) (net.Conn, error) {
|
||||||
|
return f.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.ProxyDial = func(ctx context.Context, network string, address string) (net.Conn, error) {
|
||||||
|
return dialContext(ctx, forward, network, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if auth != nil {
|
||||||
|
up := socks.UsernamePassword{
|
||||||
|
Username: auth.User,
|
||||||
|
Password: auth.Password,
|
||||||
|
}
|
||||||
|
d.AuthMethods = []socks.AuthMethod{
|
||||||
|
socks.AuthMethodNotRequired,
|
||||||
|
socks.AuthMethodUsernamePassword,
|
||||||
|
}
|
||||||
|
d.Authenticate = up.Authenticate
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
22
vendor/golang.org/x/text/PATENTS
generated
vendored
22
vendor/golang.org/x/text/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google 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,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
||||||
249
vendor/golang.org/x/text/encoding/charmap/charmap.go
generated
vendored
249
vendor/golang.org/x/text/encoding/charmap/charmap.go
generated
vendored
@@ -1,249 +0,0 @@
|
|||||||
// Copyright 2013 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 maketables.go
|
|
||||||
|
|
||||||
// Package charmap provides simple character encodings such as IBM Code Page 437
|
|
||||||
// and Windows 1252.
|
|
||||||
package charmap // import "golang.org/x/text/encoding/charmap"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/encoding/internal"
|
|
||||||
"golang.org/x/text/encoding/internal/identifier"
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These encodings vary only in the way clients should interpret them. Their
|
|
||||||
// coded character set is identical and a single implementation can be shared.
|
|
||||||
var (
|
|
||||||
// ISO8859_6E is the ISO 8859-6E encoding.
|
|
||||||
ISO8859_6E encoding.Encoding = &iso8859_6E
|
|
||||||
|
|
||||||
// ISO8859_6I is the ISO 8859-6I encoding.
|
|
||||||
ISO8859_6I encoding.Encoding = &iso8859_6I
|
|
||||||
|
|
||||||
// ISO8859_8E is the ISO 8859-8E encoding.
|
|
||||||
ISO8859_8E encoding.Encoding = &iso8859_8E
|
|
||||||
|
|
||||||
// ISO8859_8I is the ISO 8859-8I encoding.
|
|
||||||
ISO8859_8I encoding.Encoding = &iso8859_8I
|
|
||||||
|
|
||||||
iso8859_6E = internal.Encoding{
|
|
||||||
Encoding: ISO8859_6,
|
|
||||||
Name: "ISO-8859-6E",
|
|
||||||
MIB: identifier.ISO88596E,
|
|
||||||
}
|
|
||||||
|
|
||||||
iso8859_6I = internal.Encoding{
|
|
||||||
Encoding: ISO8859_6,
|
|
||||||
Name: "ISO-8859-6I",
|
|
||||||
MIB: identifier.ISO88596I,
|
|
||||||
}
|
|
||||||
|
|
||||||
iso8859_8E = internal.Encoding{
|
|
||||||
Encoding: ISO8859_8,
|
|
||||||
Name: "ISO-8859-8E",
|
|
||||||
MIB: identifier.ISO88598E,
|
|
||||||
}
|
|
||||||
|
|
||||||
iso8859_8I = internal.Encoding{
|
|
||||||
Encoding: ISO8859_8,
|
|
||||||
Name: "ISO-8859-8I",
|
|
||||||
MIB: identifier.ISO88598I,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// All is a list of all defined encodings in this package.
|
|
||||||
var All []encoding.Encoding = listAll
|
|
||||||
|
|
||||||
// TODO: implement these encodings, in order of importance.
|
|
||||||
// ASCII, ISO8859_1: Rather common. Close to Windows 1252.
|
|
||||||
// ISO8859_9: Close to Windows 1254.
|
|
||||||
|
|
||||||
// utf8Enc holds a rune's UTF-8 encoding in data[:len].
|
|
||||||
type utf8Enc struct {
|
|
||||||
len uint8
|
|
||||||
data [3]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charmap is an 8-bit character set encoding.
|
|
||||||
type Charmap struct {
|
|
||||||
// name is the encoding's name.
|
|
||||||
name string
|
|
||||||
// mib is the encoding type of this encoder.
|
|
||||||
mib identifier.MIB
|
|
||||||
// asciiSuperset states whether the encoding is a superset of ASCII.
|
|
||||||
asciiSuperset bool
|
|
||||||
// low is the lower bound of the encoded byte for a non-ASCII rune. If
|
|
||||||
// Charmap.asciiSuperset is true then this will be 0x80, otherwise 0x00.
|
|
||||||
low uint8
|
|
||||||
// replacement is the encoded replacement character.
|
|
||||||
replacement byte
|
|
||||||
// decode is the map from encoded byte to UTF-8.
|
|
||||||
decode [256]utf8Enc
|
|
||||||
// encoding is the map from runes to encoded bytes. Each entry is a
|
|
||||||
// uint32: the high 8 bits are the encoded byte and the low 24 bits are
|
|
||||||
// the rune. The table entries are sorted by ascending rune.
|
|
||||||
encode [256]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder implements the encoding.Encoding interface.
|
|
||||||
func (m *Charmap) NewDecoder() *encoding.Decoder {
|
|
||||||
return &encoding.Decoder{Transformer: charmapDecoder{charmap: m}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder implements the encoding.Encoding interface.
|
|
||||||
func (m *Charmap) NewEncoder() *encoding.Encoder {
|
|
||||||
return &encoding.Encoder{Transformer: charmapEncoder{charmap: m}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the Charmap's name.
|
|
||||||
func (m *Charmap) String() string {
|
|
||||||
return m.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID implements an internal interface.
|
|
||||||
func (m *Charmap) ID() (mib identifier.MIB, other string) {
|
|
||||||
return m.mib, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// charmapDecoder implements transform.Transformer by decoding to UTF-8.
|
|
||||||
type charmapDecoder struct {
|
|
||||||
transform.NopResetter
|
|
||||||
charmap *Charmap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m charmapDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
for i, c := range src {
|
|
||||||
if m.charmap.asciiSuperset && c < utf8.RuneSelf {
|
|
||||||
if nDst >= len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dst[nDst] = c
|
|
||||||
nDst++
|
|
||||||
nSrc = i + 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
decode := &m.charmap.decode[c]
|
|
||||||
n := int(decode.len)
|
|
||||||
if nDst+n > len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// It's 15% faster to avoid calling copy for these tiny slices.
|
|
||||||
for j := 0; j < n; j++ {
|
|
||||||
dst[nDst] = decode.data[j]
|
|
||||||
nDst++
|
|
||||||
}
|
|
||||||
nSrc = i + 1
|
|
||||||
}
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeByte returns the Charmap's rune decoding of the byte b.
|
|
||||||
func (m *Charmap) DecodeByte(b byte) rune {
|
|
||||||
switch x := &m.decode[b]; x.len {
|
|
||||||
case 1:
|
|
||||||
return rune(x.data[0])
|
|
||||||
case 2:
|
|
||||||
return rune(x.data[0]&0x1f)<<6 | rune(x.data[1]&0x3f)
|
|
||||||
default:
|
|
||||||
return rune(x.data[0]&0x0f)<<12 | rune(x.data[1]&0x3f)<<6 | rune(x.data[2]&0x3f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// charmapEncoder implements transform.Transformer by encoding from UTF-8.
|
|
||||||
type charmapEncoder struct {
|
|
||||||
transform.NopResetter
|
|
||||||
charmap *Charmap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m charmapEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
r, size := rune(0), 0
|
|
||||||
loop:
|
|
||||||
for nSrc < len(src) {
|
|
||||||
if nDst >= len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r = rune(src[nSrc])
|
|
||||||
|
|
||||||
// Decode a 1-byte rune.
|
|
||||||
if r < utf8.RuneSelf {
|
|
||||||
if m.charmap.asciiSuperset {
|
|
||||||
nSrc++
|
|
||||||
dst[nDst] = uint8(r)
|
|
||||||
nDst++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
size = 1
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Decode a multi-byte rune.
|
|
||||||
r, size = utf8.DecodeRune(src[nSrc:])
|
|
||||||
if size == 1 {
|
|
||||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
|
||||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
|
||||||
// full character yet.
|
|
||||||
if !atEOF && !utf8.FullRune(src[nSrc:]) {
|
|
||||||
err = transform.ErrShortSrc
|
|
||||||
} else {
|
|
||||||
err = internal.RepertoireError(m.charmap.replacement)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binary search in [low, high) for that rune in the m.charmap.encode table.
|
|
||||||
for low, high := int(m.charmap.low), 0x100; ; {
|
|
||||||
if low >= high {
|
|
||||||
err = internal.RepertoireError(m.charmap.replacement)
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
mid := (low + high) / 2
|
|
||||||
got := m.charmap.encode[mid]
|
|
||||||
gotRune := rune(got & (1<<24 - 1))
|
|
||||||
if gotRune < r {
|
|
||||||
low = mid + 1
|
|
||||||
} else if gotRune > r {
|
|
||||||
high = mid
|
|
||||||
} else {
|
|
||||||
dst[nDst] = byte(got >> 24)
|
|
||||||
nDst++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nSrc += size
|
|
||||||
}
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeRune returns the Charmap's byte encoding of the rune r. ok is whether
|
|
||||||
// r is in the Charmap's repertoire. If not, b is set to the Charmap's
|
|
||||||
// replacement byte. This is often the ASCII substitute character '\x1a'.
|
|
||||||
func (m *Charmap) EncodeRune(r rune) (b byte, ok bool) {
|
|
||||||
if r < utf8.RuneSelf && m.asciiSuperset {
|
|
||||||
return byte(r), true
|
|
||||||
}
|
|
||||||
for low, high := int(m.low), 0x100; ; {
|
|
||||||
if low >= high {
|
|
||||||
return m.replacement, false
|
|
||||||
}
|
|
||||||
mid := (low + high) / 2
|
|
||||||
got := m.encode[mid]
|
|
||||||
gotRune := rune(got & (1<<24 - 1))
|
|
||||||
if gotRune < r {
|
|
||||||
low = mid + 1
|
|
||||||
} else if gotRune > r {
|
|
||||||
high = mid
|
|
||||||
} else {
|
|
||||||
return byte(got >> 24), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7410
vendor/golang.org/x/text/encoding/charmap/tables.go
generated
vendored
7410
vendor/golang.org/x/text/encoding/charmap/tables.go
generated
vendored
File diff suppressed because it is too large
Load Diff
335
vendor/golang.org/x/text/encoding/encoding.go
generated
vendored
335
vendor/golang.org/x/text/encoding/encoding.go
generated
vendored
@@ -1,335 +0,0 @@
|
|||||||
// Copyright 2013 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 encoding defines an interface for character encodings, such as Shift
|
|
||||||
// JIS and Windows 1252, that can convert to and from UTF-8.
|
|
||||||
//
|
|
||||||
// Encoding implementations are provided in other packages, such as
|
|
||||||
// golang.org/x/text/encoding/charmap and
|
|
||||||
// golang.org/x/text/encoding/japanese.
|
|
||||||
package encoding // import "golang.org/x/text/encoding"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding/internal/identifier"
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - There seems to be some inconsistency in when decoders return errors
|
|
||||||
// and when not. Also documentation seems to suggest they shouldn't return
|
|
||||||
// errors at all (except for UTF-16).
|
|
||||||
// - Encoders seem to rely on or at least benefit from the input being in NFC
|
|
||||||
// normal form. Perhaps add an example how users could prepare their output.
|
|
||||||
|
|
||||||
// Encoding is a character set encoding that can be transformed to and from
|
|
||||||
// UTF-8.
|
|
||||||
type Encoding interface {
|
|
||||||
// NewDecoder returns a Decoder.
|
|
||||||
NewDecoder() *Decoder
|
|
||||||
|
|
||||||
// NewEncoder returns an Encoder.
|
|
||||||
NewEncoder() *Encoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Decoder converts bytes to UTF-8. It implements transform.Transformer.
|
|
||||||
//
|
|
||||||
// Transforming source bytes that are not of that encoding will not result in an
|
|
||||||
// error per se. Each byte that cannot be transcoded will be represented in the
|
|
||||||
// output by the UTF-8 encoding of '\uFFFD', the replacement rune.
|
|
||||||
type Decoder struct {
|
|
||||||
transform.Transformer
|
|
||||||
|
|
||||||
// This forces external creators of Decoders to use names in struct
|
|
||||||
// initializers, allowing for future extendibility without having to break
|
|
||||||
// code.
|
|
||||||
_ struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes converts the given encoded bytes to UTF-8. It returns the converted
|
|
||||||
// bytes or nil, err if any error occurred.
|
|
||||||
func (d *Decoder) Bytes(b []byte) ([]byte, error) {
|
|
||||||
b, _, err := transform.Bytes(d, b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String converts the given encoded string to UTF-8. It returns the converted
|
|
||||||
// string or "", err if any error occurred.
|
|
||||||
func (d *Decoder) String(s string) (string, error) {
|
|
||||||
s, _, err := transform.String(d, s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader wraps another Reader to decode its bytes.
|
|
||||||
//
|
|
||||||
// The Decoder may not be used for any other operation as long as the returned
|
|
||||||
// Reader is in use.
|
|
||||||
func (d *Decoder) Reader(r io.Reader) io.Reader {
|
|
||||||
return transform.NewReader(r, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Encoder converts bytes from UTF-8. It implements transform.Transformer.
|
|
||||||
//
|
|
||||||
// Each rune that cannot be transcoded will result in an error. In this case,
|
|
||||||
// the transform will consume all source byte up to, not including the offending
|
|
||||||
// rune. Transforming source bytes that are not valid UTF-8 will be replaced by
|
|
||||||
// `\uFFFD`. To return early with an error instead, use transform.Chain to
|
|
||||||
// preprocess the data with a UTF8Validator.
|
|
||||||
type Encoder struct {
|
|
||||||
transform.Transformer
|
|
||||||
|
|
||||||
// This forces external creators of Encoders to use names in struct
|
|
||||||
// initializers, allowing for future extendibility without having to break
|
|
||||||
// code.
|
|
||||||
_ struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes converts bytes from UTF-8. It returns the converted bytes or nil, err if
|
|
||||||
// any error occurred.
|
|
||||||
func (e *Encoder) Bytes(b []byte) ([]byte, error) {
|
|
||||||
b, _, err := transform.Bytes(e, b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String converts a string from UTF-8. It returns the converted string or
|
|
||||||
// "", err if any error occurred.
|
|
||||||
func (e *Encoder) String(s string) (string, error) {
|
|
||||||
s, _, err := transform.String(e, s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer wraps another Writer to encode its UTF-8 output.
|
|
||||||
//
|
|
||||||
// The Encoder may not be used for any other operation as long as the returned
|
|
||||||
// Writer is in use.
|
|
||||||
func (e *Encoder) Writer(w io.Writer) io.Writer {
|
|
||||||
return transform.NewWriter(w, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASCIISub is the ASCII substitute character, as recommended by
|
|
||||||
// https://unicode.org/reports/tr36/#Text_Comparison
|
|
||||||
const ASCIISub = '\x1a'
|
|
||||||
|
|
||||||
// Nop is the nop encoding. Its transformed bytes are the same as the source
|
|
||||||
// bytes; it does not replace invalid UTF-8 sequences.
|
|
||||||
var Nop Encoding = nop{}
|
|
||||||
|
|
||||||
type nop struct{}
|
|
||||||
|
|
||||||
func (nop) NewDecoder() *Decoder {
|
|
||||||
return &Decoder{Transformer: transform.Nop}
|
|
||||||
}
|
|
||||||
func (nop) NewEncoder() *Encoder {
|
|
||||||
return &Encoder{Transformer: transform.Nop}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replacement is the replacement encoding. Decoding from the replacement
|
|
||||||
// encoding yields a single '\uFFFD' replacement rune. Encoding from UTF-8 to
|
|
||||||
// the replacement encoding yields the same as the source bytes except that
|
|
||||||
// invalid UTF-8 is converted to '\uFFFD'.
|
|
||||||
//
|
|
||||||
// It is defined at http://encoding.spec.whatwg.org/#replacement
|
|
||||||
var Replacement Encoding = replacement{}
|
|
||||||
|
|
||||||
type replacement struct{}
|
|
||||||
|
|
||||||
func (replacement) NewDecoder() *Decoder {
|
|
||||||
return &Decoder{Transformer: replacementDecoder{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (replacement) NewEncoder() *Encoder {
|
|
||||||
return &Encoder{Transformer: replacementEncoder{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (replacement) ID() (mib identifier.MIB, other string) {
|
|
||||||
return identifier.Replacement, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type replacementDecoder struct{ transform.NopResetter }
|
|
||||||
|
|
||||||
func (replacementDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
if len(dst) < 3 {
|
|
||||||
return 0, 0, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
if atEOF {
|
|
||||||
const fffd = "\ufffd"
|
|
||||||
dst[0] = fffd[0]
|
|
||||||
dst[1] = fffd[1]
|
|
||||||
dst[2] = fffd[2]
|
|
||||||
nDst = 3
|
|
||||||
}
|
|
||||||
return nDst, len(src), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type replacementEncoder struct{ transform.NopResetter }
|
|
||||||
|
|
||||||
func (replacementEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
r, size := rune(0), 0
|
|
||||||
|
|
||||||
for ; nSrc < len(src); nSrc += size {
|
|
||||||
r = rune(src[nSrc])
|
|
||||||
|
|
||||||
// Decode a 1-byte rune.
|
|
||||||
if r < utf8.RuneSelf {
|
|
||||||
size = 1
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Decode a multi-byte rune.
|
|
||||||
r, size = utf8.DecodeRune(src[nSrc:])
|
|
||||||
if size == 1 {
|
|
||||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
|
||||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
|
||||||
// full character yet.
|
|
||||||
if !atEOF && !utf8.FullRune(src[nSrc:]) {
|
|
||||||
err = transform.ErrShortSrc
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r = '\ufffd'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nDst+utf8.RuneLen(r) > len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nDst += utf8.EncodeRune(dst[nDst:], r)
|
|
||||||
}
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTMLEscapeUnsupported wraps encoders to replace source runes outside the
|
|
||||||
// repertoire of the destination encoding with HTML escape sequences.
|
|
||||||
//
|
|
||||||
// This wrapper exists to comply to URL and HTML forms requiring a
|
|
||||||
// non-terminating legacy encoder. The produced sequences may lead to data
|
|
||||||
// loss as they are indistinguishable from legitimate input. To avoid this
|
|
||||||
// issue, use UTF-8 encodings whenever possible.
|
|
||||||
func HTMLEscapeUnsupported(e *Encoder) *Encoder {
|
|
||||||
return &Encoder{Transformer: &errorHandler{e, errorToHTML}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceUnsupported wraps encoders to replace source runes outside the
|
|
||||||
// repertoire of the destination encoding with an encoding-specific
|
|
||||||
// replacement.
|
|
||||||
//
|
|
||||||
// This wrapper is only provided for backwards compatibility and legacy
|
|
||||||
// handling. Its use is strongly discouraged. Use UTF-8 whenever possible.
|
|
||||||
func ReplaceUnsupported(e *Encoder) *Encoder {
|
|
||||||
return &Encoder{Transformer: &errorHandler{e, errorToReplacement}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorHandler struct {
|
|
||||||
*Encoder
|
|
||||||
handler func(dst []byte, r rune, err repertoireError) (n int, ok bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: consider making this error public in some form.
|
|
||||||
type repertoireError interface {
|
|
||||||
Replacement() byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h errorHandler) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
nDst, nSrc, err = h.Transformer.Transform(dst, src, atEOF)
|
|
||||||
for err != nil {
|
|
||||||
rerr, ok := err.(repertoireError)
|
|
||||||
if !ok {
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
r, sz := utf8.DecodeRune(src[nSrc:])
|
|
||||||
n, ok := h.handler(dst[nDst:], r, rerr)
|
|
||||||
if !ok {
|
|
||||||
return nDst, nSrc, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
nDst += n
|
|
||||||
if nSrc += sz; nSrc < len(src) {
|
|
||||||
var dn, sn int
|
|
||||||
dn, sn, err = h.Transformer.Transform(dst[nDst:], src[nSrc:], atEOF)
|
|
||||||
nDst += dn
|
|
||||||
nSrc += sn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorToHTML(dst []byte, r rune, err repertoireError) (n int, ok bool) {
|
|
||||||
buf := [8]byte{}
|
|
||||||
b := strconv.AppendUint(buf[:0], uint64(r), 10)
|
|
||||||
if n = len(b) + len("&#;"); n >= len(dst) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
dst[0] = '&'
|
|
||||||
dst[1] = '#'
|
|
||||||
dst[copy(dst[2:], b)+2] = ';'
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorToReplacement(dst []byte, r rune, err repertoireError) (n int, ok bool) {
|
|
||||||
if len(dst) == 0 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
dst[0] = err.Replacement()
|
|
||||||
return 1, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrInvalidUTF8 means that a transformer encountered invalid UTF-8.
|
|
||||||
var ErrInvalidUTF8 = errors.New("encoding: invalid UTF-8")
|
|
||||||
|
|
||||||
// UTF8Validator is a transformer that returns ErrInvalidUTF8 on the first
|
|
||||||
// input byte that is not valid UTF-8.
|
|
||||||
var UTF8Validator transform.Transformer = utf8Validator{}
|
|
||||||
|
|
||||||
type utf8Validator struct{ transform.NopResetter }
|
|
||||||
|
|
||||||
func (utf8Validator) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
n := len(src)
|
|
||||||
if n > len(dst) {
|
|
||||||
n = len(dst)
|
|
||||||
}
|
|
||||||
for i := 0; i < n; {
|
|
||||||
if c := src[i]; c < utf8.RuneSelf {
|
|
||||||
dst[i] = c
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, size := utf8.DecodeRune(src[i:])
|
|
||||||
if size == 1 {
|
|
||||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
|
||||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
|
||||||
// full character yet.
|
|
||||||
err = ErrInvalidUTF8
|
|
||||||
if !atEOF && !utf8.FullRune(src[i:]) {
|
|
||||||
err = transform.ErrShortSrc
|
|
||||||
}
|
|
||||||
return i, i, err
|
|
||||||
}
|
|
||||||
if i+size > len(dst) {
|
|
||||||
return i, i, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
for ; size > 0; size-- {
|
|
||||||
dst[i] = src[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(src) > len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
}
|
|
||||||
return n, n, err
|
|
||||||
}
|
|
||||||
81
vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
generated
vendored
81
vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
generated
vendored
@@ -1,81 +0,0 @@
|
|||||||
// Copyright 2015 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 identifier defines the contract between implementations of Encoding
|
|
||||||
// and Index by defining identifiers that uniquely identify standardized coded
|
|
||||||
// character sets (CCS) and character encoding schemes (CES), which we will
|
|
||||||
// together refer to as encodings, for which Encoding implementations provide
|
|
||||||
// converters to and from UTF-8. This package is typically only of concern to
|
|
||||||
// implementers of Indexes and Encodings.
|
|
||||||
//
|
|
||||||
// One part of the identifier is the MIB code, which is defined by IANA and
|
|
||||||
// uniquely identifies a CCS or CES. Each code is associated with data that
|
|
||||||
// references authorities, official documentation as well as aliases and MIME
|
|
||||||
// names.
|
|
||||||
//
|
|
||||||
// Not all CESs are covered by the IANA registry. The "other" string that is
|
|
||||||
// returned by ID can be used to identify other character sets or versions of
|
|
||||||
// existing ones.
|
|
||||||
//
|
|
||||||
// It is recommended that each package that provides a set of Encodings provide
|
|
||||||
// the All and Common variables to reference all supported encodings and
|
|
||||||
// commonly used subset. This allows Index implementations to include all
|
|
||||||
// available encodings without explicitly referencing or knowing about them.
|
|
||||||
package identifier
|
|
||||||
|
|
||||||
// Note: this package is internal, but could be made public if there is a need
|
|
||||||
// for writing third-party Indexes and Encodings.
|
|
||||||
|
|
||||||
// References:
|
|
||||||
// - http://source.icu-project.org/repos/icu/icu/trunk/source/data/mappings/convrtrs.txt
|
|
||||||
// - http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
|
||||||
// - http://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
|
|
||||||
// - http://www.ietf.org/rfc/rfc2978.txt
|
|
||||||
// - https://www.unicode.org/reports/tr22/
|
|
||||||
// - http://www.w3.org/TR/encoding/
|
|
||||||
// - https://encoding.spec.whatwg.org/
|
|
||||||
// - https://encoding.spec.whatwg.org/encodings.json
|
|
||||||
// - https://tools.ietf.org/html/rfc6657#section-5
|
|
||||||
|
|
||||||
// Interface can be implemented by Encodings to define the CCS or CES for which
|
|
||||||
// it implements conversions.
|
|
||||||
type Interface interface {
|
|
||||||
// ID returns an encoding identifier. Exactly one of the mib and other
|
|
||||||
// values should be non-zero.
|
|
||||||
//
|
|
||||||
// In the usual case it is only necessary to indicate the MIB code. The
|
|
||||||
// other string can be used to specify encodings for which there is no MIB,
|
|
||||||
// such as "x-mac-dingbat".
|
|
||||||
//
|
|
||||||
// The other string may only contain the characters a-z, A-Z, 0-9, - and _.
|
|
||||||
ID() (mib MIB, other string)
|
|
||||||
|
|
||||||
// NOTE: the restrictions on the encoding are to allow extending the syntax
|
|
||||||
// with additional information such as versions, vendors and other variants.
|
|
||||||
}
|
|
||||||
|
|
||||||
// A MIB identifies an encoding. It is derived from the IANA MIB codes and adds
|
|
||||||
// some identifiers for some encodings that are not covered by the IANA
|
|
||||||
// standard.
|
|
||||||
//
|
|
||||||
// See http://www.iana.org/assignments/ianacharset-mib.
|
|
||||||
type MIB uint16
|
|
||||||
|
|
||||||
// These additional MIB types are not defined in IANA. They are added because
|
|
||||||
// they are common and defined within the text repo.
|
|
||||||
const (
|
|
||||||
// Unofficial marks the start of encodings not registered by IANA.
|
|
||||||
Unofficial MIB = 10000 + iota
|
|
||||||
|
|
||||||
// Replacement is the WhatWG replacement encoding.
|
|
||||||
Replacement
|
|
||||||
|
|
||||||
// XUserDefined is the code for x-user-defined.
|
|
||||||
XUserDefined
|
|
||||||
|
|
||||||
// MacintoshCyrillic is the code for x-mac-cyrillic.
|
|
||||||
MacintoshCyrillic
|
|
||||||
)
|
|
||||||
1627
vendor/golang.org/x/text/encoding/internal/identifier/mib.go
generated
vendored
1627
vendor/golang.org/x/text/encoding/internal/identifier/mib.go
generated
vendored
File diff suppressed because it is too large
Load Diff
75
vendor/golang.org/x/text/encoding/internal/internal.go
generated
vendored
75
vendor/golang.org/x/text/encoding/internal/internal.go
generated
vendored
@@ -1,75 +0,0 @@
|
|||||||
// Copyright 2015 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 internal contains code that is shared among encoding implementations.
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/encoding/internal/identifier"
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encoding is an implementation of the Encoding interface that adds the String
|
|
||||||
// and ID methods to an existing encoding.
|
|
||||||
type Encoding struct {
|
|
||||||
encoding.Encoding
|
|
||||||
Name string
|
|
||||||
MIB identifier.MIB
|
|
||||||
}
|
|
||||||
|
|
||||||
// _ verifies that Encoding implements identifier.Interface.
|
|
||||||
var _ identifier.Interface = (*Encoding)(nil)
|
|
||||||
|
|
||||||
func (e *Encoding) String() string {
|
|
||||||
return e.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Encoding) ID() (mib identifier.MIB, other string) {
|
|
||||||
return e.MIB, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleEncoding is an Encoding that combines two Transformers.
|
|
||||||
type SimpleEncoding struct {
|
|
||||||
Decoder transform.Transformer
|
|
||||||
Encoder transform.Transformer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SimpleEncoding) NewDecoder() *encoding.Decoder {
|
|
||||||
return &encoding.Decoder{Transformer: e.Decoder}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SimpleEncoding) NewEncoder() *encoding.Encoder {
|
|
||||||
return &encoding.Encoder{Transformer: e.Encoder}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FuncEncoding is an Encoding that combines two functions returning a new
|
|
||||||
// Transformer.
|
|
||||||
type FuncEncoding struct {
|
|
||||||
Decoder func() transform.Transformer
|
|
||||||
Encoder func() transform.Transformer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e FuncEncoding) NewDecoder() *encoding.Decoder {
|
|
||||||
return &encoding.Decoder{Transformer: e.Decoder()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e FuncEncoding) NewEncoder() *encoding.Encoder {
|
|
||||||
return &encoding.Encoder{Transformer: e.Encoder()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RepertoireError indicates a rune is not in the repertoire of a destination
|
|
||||||
// encoding. It is associated with an encoding-specific suggested replacement
|
|
||||||
// byte.
|
|
||||||
type RepertoireError byte
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (r RepertoireError) Error() string {
|
|
||||||
return "encoding: rune not supported by encoding."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replacement returns the replacement string associated with this error.
|
|
||||||
func (r RepertoireError) Replacement() byte { return byte(r) }
|
|
||||||
|
|
||||||
var ErrASCIIReplacement = RepertoireError(encoding.ASCIISub)
|
|
||||||
709
vendor/golang.org/x/text/transform/transform.go
generated
vendored
709
vendor/golang.org/x/text/transform/transform.go
generated
vendored
@@ -1,709 +0,0 @@
|
|||||||
// Copyright 2013 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 transform provides reader and writer wrappers that transform the
|
|
||||||
// bytes passing through as well as various transformations. Example
|
|
||||||
// transformations provided by other packages include normalization and
|
|
||||||
// conversion between character sets.
|
|
||||||
package transform // import "golang.org/x/text/transform"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrShortDst means that the destination buffer was too short to
|
|
||||||
// receive all of the transformed bytes.
|
|
||||||
ErrShortDst = errors.New("transform: short destination buffer")
|
|
||||||
|
|
||||||
// ErrShortSrc means that the source buffer has insufficient data to
|
|
||||||
// complete the transformation.
|
|
||||||
ErrShortSrc = errors.New("transform: short source buffer")
|
|
||||||
|
|
||||||
// ErrEndOfSpan means that the input and output (the transformed input)
|
|
||||||
// are not identical.
|
|
||||||
ErrEndOfSpan = errors.New("transform: input and output are not identical")
|
|
||||||
|
|
||||||
// errInconsistentByteCount means that Transform returned success (nil
|
|
||||||
// error) but also returned nSrc inconsistent with the src argument.
|
|
||||||
errInconsistentByteCount = errors.New("transform: inconsistent byte count returned")
|
|
||||||
|
|
||||||
// errShortInternal means that an internal buffer is not large enough
|
|
||||||
// to make progress and the Transform operation must be aborted.
|
|
||||||
errShortInternal = errors.New("transform: short internal buffer")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Transformer transforms bytes.
|
|
||||||
type Transformer interface {
|
|
||||||
// Transform writes to dst the transformed bytes read from src, and
|
|
||||||
// returns the number of dst bytes written and src bytes read. The
|
|
||||||
// atEOF argument tells whether src represents the last bytes of the
|
|
||||||
// input.
|
|
||||||
//
|
|
||||||
// Callers should always process the nDst bytes produced and account
|
|
||||||
// for the nSrc bytes consumed before considering the error err.
|
|
||||||
//
|
|
||||||
// A nil error means that all of the transformed bytes (whether freshly
|
|
||||||
// transformed from src or left over from previous Transform calls)
|
|
||||||
// were written to dst. A nil error can be returned regardless of
|
|
||||||
// whether atEOF is true. If err is nil then nSrc must equal len(src);
|
|
||||||
// the converse is not necessarily true.
|
|
||||||
//
|
|
||||||
// ErrShortDst means that dst was too short to receive all of the
|
|
||||||
// transformed bytes. ErrShortSrc means that src had insufficient data
|
|
||||||
// to complete the transformation. If both conditions apply, then
|
|
||||||
// either error may be returned. Other than the error conditions listed
|
|
||||||
// here, implementations are free to report other errors that arise.
|
|
||||||
Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
|
||||||
|
|
||||||
// Reset resets the state and allows a Transformer to be reused.
|
|
||||||
Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpanningTransformer extends the Transformer interface with a Span method
|
|
||||||
// that determines how much of the input already conforms to the Transformer.
|
|
||||||
type SpanningTransformer interface {
|
|
||||||
Transformer
|
|
||||||
|
|
||||||
// Span returns a position in src such that transforming src[:n] results in
|
|
||||||
// identical output src[:n] for these bytes. It does not necessarily return
|
|
||||||
// the largest such n. The atEOF argument tells whether src represents the
|
|
||||||
// last bytes of the input.
|
|
||||||
//
|
|
||||||
// Callers should always account for the n bytes consumed before
|
|
||||||
// considering the error err.
|
|
||||||
//
|
|
||||||
// A nil error means that all input bytes are known to be identical to the
|
|
||||||
// output produced by the Transformer. A nil error can be returned
|
|
||||||
// regardless of whether atEOF is true. If err is nil, then n must
|
|
||||||
// equal len(src); the converse is not necessarily true.
|
|
||||||
//
|
|
||||||
// ErrEndOfSpan means that the Transformer output may differ from the
|
|
||||||
// input after n bytes. Note that n may be len(src), meaning that the output
|
|
||||||
// would contain additional bytes after otherwise identical output.
|
|
||||||
// ErrShortSrc means that src had insufficient data to determine whether the
|
|
||||||
// remaining bytes would change. Other than the error conditions listed
|
|
||||||
// here, implementations are free to report other errors that arise.
|
|
||||||
//
|
|
||||||
// Calling Span can modify the Transformer state as a side effect. In
|
|
||||||
// effect, it does the transformation just as calling Transform would, only
|
|
||||||
// without copying to a destination buffer and only up to a point it can
|
|
||||||
// determine the input and output bytes are the same. This is obviously more
|
|
||||||
// limited than calling Transform, but can be more efficient in terms of
|
|
||||||
// copying and allocating buffers. Calls to Span and Transform may be
|
|
||||||
// interleaved.
|
|
||||||
Span(src []byte, atEOF bool) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NopResetter can be embedded by implementations of Transformer to add a nop
|
|
||||||
// Reset method.
|
|
||||||
type NopResetter struct{}
|
|
||||||
|
|
||||||
// Reset implements the Reset method of the Transformer interface.
|
|
||||||
func (NopResetter) Reset() {}
|
|
||||||
|
|
||||||
// Reader wraps another io.Reader by transforming the bytes read.
|
|
||||||
type Reader struct {
|
|
||||||
r io.Reader
|
|
||||||
t Transformer
|
|
||||||
err error
|
|
||||||
|
|
||||||
// dst[dst0:dst1] contains bytes that have been transformed by t but
|
|
||||||
// not yet copied out via Read.
|
|
||||||
dst []byte
|
|
||||||
dst0, dst1 int
|
|
||||||
|
|
||||||
// src[src0:src1] contains bytes that have been read from r but not
|
|
||||||
// yet transformed through t.
|
|
||||||
src []byte
|
|
||||||
src0, src1 int
|
|
||||||
|
|
||||||
// transformComplete is whether the transformation is complete,
|
|
||||||
// regardless of whether or not it was successful.
|
|
||||||
transformComplete bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultBufSize = 4096
|
|
||||||
|
|
||||||
// NewReader returns a new Reader that wraps r by transforming the bytes read
|
|
||||||
// via t. It calls Reset on t.
|
|
||||||
func NewReader(r io.Reader, t Transformer) *Reader {
|
|
||||||
t.Reset()
|
|
||||||
return &Reader{
|
|
||||||
r: r,
|
|
||||||
t: t,
|
|
||||||
dst: make([]byte, defaultBufSize),
|
|
||||||
src: make([]byte, defaultBufSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements the io.Reader interface.
|
|
||||||
func (r *Reader) Read(p []byte) (int, error) {
|
|
||||||
n, err := 0, error(nil)
|
|
||||||
for {
|
|
||||||
// Copy out any transformed bytes and return the final error if we are done.
|
|
||||||
if r.dst0 != r.dst1 {
|
|
||||||
n = copy(p, r.dst[r.dst0:r.dst1])
|
|
||||||
r.dst0 += n
|
|
||||||
if r.dst0 == r.dst1 && r.transformComplete {
|
|
||||||
return n, r.err
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
} else if r.transformComplete {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to transform some source bytes, or to flush the transformer if we
|
|
||||||
// are out of source bytes. We do this even if r.r.Read returned an error.
|
|
||||||
// As the io.Reader documentation says, "process the n > 0 bytes returned
|
|
||||||
// before considering the error".
|
|
||||||
if r.src0 != r.src1 || r.err != nil {
|
|
||||||
r.dst0 = 0
|
|
||||||
r.dst1, n, err = r.t.Transform(r.dst, r.src[r.src0:r.src1], r.err == io.EOF)
|
|
||||||
r.src0 += n
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
if r.src0 != r.src1 {
|
|
||||||
r.err = errInconsistentByteCount
|
|
||||||
}
|
|
||||||
// The Transform call was successful; we are complete if we
|
|
||||||
// cannot read more bytes into src.
|
|
||||||
r.transformComplete = r.err != nil
|
|
||||||
continue
|
|
||||||
case err == ErrShortDst && (r.dst1 != 0 || n != 0):
|
|
||||||
// Make room in dst by copying out, and try again.
|
|
||||||
continue
|
|
||||||
case err == ErrShortSrc && r.src1-r.src0 != len(r.src) && r.err == nil:
|
|
||||||
// Read more bytes into src via the code below, and try again.
|
|
||||||
default:
|
|
||||||
r.transformComplete = true
|
|
||||||
// The reader error (r.err) takes precedence over the
|
|
||||||
// transformer error (err) unless r.err is nil or io.EOF.
|
|
||||||
if r.err == nil || r.err == io.EOF {
|
|
||||||
r.err = err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move any untransformed source bytes to the start of the buffer
|
|
||||||
// and read more bytes.
|
|
||||||
if r.src0 != 0 {
|
|
||||||
r.src0, r.src1 = 0, copy(r.src, r.src[r.src0:r.src1])
|
|
||||||
}
|
|
||||||
n, r.err = r.r.Read(r.src[r.src1:])
|
|
||||||
r.src1 += n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement ReadByte (and ReadRune??).
|
|
||||||
|
|
||||||
// Writer wraps another io.Writer by transforming the bytes read.
|
|
||||||
// The user needs to call Close to flush unwritten bytes that may
|
|
||||||
// be buffered.
|
|
||||||
type Writer struct {
|
|
||||||
w io.Writer
|
|
||||||
t Transformer
|
|
||||||
dst []byte
|
|
||||||
|
|
||||||
// src[:n] contains bytes that have not yet passed through t.
|
|
||||||
src []byte
|
|
||||||
n int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter returns a new Writer that wraps w by transforming the bytes written
|
|
||||||
// via t. It calls Reset on t.
|
|
||||||
func NewWriter(w io.Writer, t Transformer) *Writer {
|
|
||||||
t.Reset()
|
|
||||||
return &Writer{
|
|
||||||
w: w,
|
|
||||||
t: t,
|
|
||||||
dst: make([]byte, defaultBufSize),
|
|
||||||
src: make([]byte, defaultBufSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements the io.Writer interface. If there are not enough
|
|
||||||
// bytes available to complete a Transform, the bytes will be buffered
|
|
||||||
// for the next write. Call Close to convert the remaining bytes.
|
|
||||||
func (w *Writer) Write(data []byte) (n int, err error) {
|
|
||||||
src := data
|
|
||||||
if w.n > 0 {
|
|
||||||
// Append bytes from data to the last remainder.
|
|
||||||
// TODO: limit the amount copied on first try.
|
|
||||||
n = copy(w.src[w.n:], data)
|
|
||||||
w.n += n
|
|
||||||
src = w.src[:w.n]
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nDst, nSrc, err := w.t.Transform(w.dst, src, false)
|
|
||||||
if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
|
|
||||||
return n, werr
|
|
||||||
}
|
|
||||||
src = src[nSrc:]
|
|
||||||
if w.n == 0 {
|
|
||||||
n += nSrc
|
|
||||||
} else if len(src) <= n {
|
|
||||||
// Enough bytes from w.src have been consumed. We make src point
|
|
||||||
// to data instead to reduce the copying.
|
|
||||||
w.n = 0
|
|
||||||
n -= len(src)
|
|
||||||
src = data[n:]
|
|
||||||
if n < len(data) && (err == nil || err == ErrShortSrc) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch err {
|
|
||||||
case ErrShortDst:
|
|
||||||
// This error is okay as long as we are making progress.
|
|
||||||
if nDst > 0 || nSrc > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case ErrShortSrc:
|
|
||||||
if len(src) < len(w.src) {
|
|
||||||
m := copy(w.src, src)
|
|
||||||
// If w.n > 0, bytes from data were already copied to w.src and n
|
|
||||||
// was already set to the number of bytes consumed.
|
|
||||||
if w.n == 0 {
|
|
||||||
n += m
|
|
||||||
}
|
|
||||||
w.n = m
|
|
||||||
err = nil
|
|
||||||
} else if nDst > 0 || nSrc > 0 {
|
|
||||||
// Not enough buffer to store the remainder. Keep processing as
|
|
||||||
// long as there is progress. Without this case, transforms that
|
|
||||||
// require a lookahead larger than the buffer may result in an
|
|
||||||
// error. This is not something one may expect to be common in
|
|
||||||
// practice, but it may occur when buffers are set to small
|
|
||||||
// sizes during testing.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
if w.n > 0 {
|
|
||||||
err = errInconsistentByteCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements the io.Closer interface.
|
|
||||||
func (w *Writer) Close() error {
|
|
||||||
src := w.src[:w.n]
|
|
||||||
for {
|
|
||||||
nDst, nSrc, err := w.t.Transform(w.dst, src, true)
|
|
||||||
if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
|
|
||||||
return werr
|
|
||||||
}
|
|
||||||
if err != ErrShortDst {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
src = src[nSrc:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type nop struct{ NopResetter }
|
|
||||||
|
|
||||||
func (nop) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
n := copy(dst, src)
|
|
||||||
if n < len(src) {
|
|
||||||
err = ErrShortDst
|
|
||||||
}
|
|
||||||
return n, n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nop) Span(src []byte, atEOF bool) (n int, err error) {
|
|
||||||
return len(src), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type discard struct{ NopResetter }
|
|
||||||
|
|
||||||
func (discard) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
return 0, len(src), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Discard is a Transformer for which all Transform calls succeed
|
|
||||||
// by consuming all bytes and writing nothing.
|
|
||||||
Discard Transformer = discard{}
|
|
||||||
|
|
||||||
// Nop is a SpanningTransformer that copies src to dst.
|
|
||||||
Nop SpanningTransformer = nop{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// chain is a sequence of links. A chain with N Transformers has N+1 links and
|
|
||||||
// N+1 buffers. Of those N+1 buffers, the first and last are the src and dst
|
|
||||||
// buffers given to chain.Transform and the middle N-1 buffers are intermediate
|
|
||||||
// buffers owned by the chain. The i'th link transforms bytes from the i'th
|
|
||||||
// buffer chain.link[i].b at read offset chain.link[i].p to the i+1'th buffer
|
|
||||||
// chain.link[i+1].b at write offset chain.link[i+1].n, for i in [0, N).
|
|
||||||
type chain struct {
|
|
||||||
link []link
|
|
||||||
err error
|
|
||||||
// errStart is the index at which the error occurred plus 1. Processing
|
|
||||||
// errStart at this level at the next call to Transform. As long as
|
|
||||||
// errStart > 0, chain will not consume any more source bytes.
|
|
||||||
errStart int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chain) fatalError(errIndex int, err error) {
|
|
||||||
if i := errIndex + 1; i > c.errStart {
|
|
||||||
c.errStart = i
|
|
||||||
c.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type link struct {
|
|
||||||
t Transformer
|
|
||||||
// b[p:n] holds the bytes to be transformed by t.
|
|
||||||
b []byte
|
|
||||||
p int
|
|
||||||
n int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *link) src() []byte {
|
|
||||||
return l.b[l.p:l.n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *link) dst() []byte {
|
|
||||||
return l.b[l.n:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chain returns a Transformer that applies t in sequence.
|
|
||||||
func Chain(t ...Transformer) Transformer {
|
|
||||||
if len(t) == 0 {
|
|
||||||
return nop{}
|
|
||||||
}
|
|
||||||
c := &chain{link: make([]link, len(t)+1)}
|
|
||||||
for i, tt := range t {
|
|
||||||
c.link[i].t = tt
|
|
||||||
}
|
|
||||||
// Allocate intermediate buffers.
|
|
||||||
b := make([][defaultBufSize]byte, len(t)-1)
|
|
||||||
for i := range b {
|
|
||||||
c.link[i+1].b = b[i][:]
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets the state of Chain. It calls Reset on all the Transformers.
|
|
||||||
func (c *chain) Reset() {
|
|
||||||
for i, l := range c.link {
|
|
||||||
if l.t != nil {
|
|
||||||
l.t.Reset()
|
|
||||||
}
|
|
||||||
c.link[i].p, c.link[i].n = 0, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make chain use Span (is going to be fun to implement!)
|
|
||||||
|
|
||||||
// Transform applies the transformers of c in sequence.
|
|
||||||
func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
// Set up src and dst in the chain.
|
|
||||||
srcL := &c.link[0]
|
|
||||||
dstL := &c.link[len(c.link)-1]
|
|
||||||
srcL.b, srcL.p, srcL.n = src, 0, len(src)
|
|
||||||
dstL.b, dstL.n = dst, 0
|
|
||||||
var lastFull, needProgress bool // for detecting progress
|
|
||||||
|
|
||||||
// i is the index of the next Transformer to apply, for i in [low, high].
|
|
||||||
// low is the lowest index for which c.link[low] may still produce bytes.
|
|
||||||
// high is the highest index for which c.link[high] has a Transformer.
|
|
||||||
// The error returned by Transform determines whether to increase or
|
|
||||||
// decrease i. We try to completely fill a buffer before converting it.
|
|
||||||
for low, i, high := c.errStart, c.errStart, len(c.link)-2; low <= i && i <= high; {
|
|
||||||
in, out := &c.link[i], &c.link[i+1]
|
|
||||||
nDst, nSrc, err0 := in.t.Transform(out.dst(), in.src(), atEOF && low == i)
|
|
||||||
out.n += nDst
|
|
||||||
in.p += nSrc
|
|
||||||
if i > 0 && in.p == in.n {
|
|
||||||
in.p, in.n = 0, 0
|
|
||||||
}
|
|
||||||
needProgress, lastFull = lastFull, false
|
|
||||||
switch err0 {
|
|
||||||
case ErrShortDst:
|
|
||||||
// Process the destination buffer next. Return if we are already
|
|
||||||
// at the high index.
|
|
||||||
if i == high {
|
|
||||||
return dstL.n, srcL.p, ErrShortDst
|
|
||||||
}
|
|
||||||
if out.n != 0 {
|
|
||||||
i++
|
|
||||||
// If the Transformer at the next index is not able to process any
|
|
||||||
// source bytes there is nothing that can be done to make progress
|
|
||||||
// and the bytes will remain unprocessed. lastFull is used to
|
|
||||||
// detect this and break out of the loop with a fatal error.
|
|
||||||
lastFull = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The destination buffer was too small, but is completely empty.
|
|
||||||
// Return a fatal error as this transformation can never complete.
|
|
||||||
c.fatalError(i, errShortInternal)
|
|
||||||
case ErrShortSrc:
|
|
||||||
if i == 0 {
|
|
||||||
// Save ErrShortSrc in err. All other errors take precedence.
|
|
||||||
err = ErrShortSrc
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Source bytes were depleted before filling up the destination buffer.
|
|
||||||
// Verify we made some progress, move the remaining bytes to the errStart
|
|
||||||
// and try to get more source bytes.
|
|
||||||
if needProgress && nSrc == 0 || in.n-in.p == len(in.b) {
|
|
||||||
// There were not enough source bytes to proceed while the source
|
|
||||||
// buffer cannot hold any more bytes. Return a fatal error as this
|
|
||||||
// transformation can never complete.
|
|
||||||
c.fatalError(i, errShortInternal)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// in.b is an internal buffer and we can make progress.
|
|
||||||
in.p, in.n = 0, copy(in.b, in.src())
|
|
||||||
fallthrough
|
|
||||||
case nil:
|
|
||||||
// if i == low, we have depleted the bytes at index i or any lower levels.
|
|
||||||
// In that case we increase low and i. In all other cases we decrease i to
|
|
||||||
// fetch more bytes before proceeding to the next index.
|
|
||||||
if i > low {
|
|
||||||
i--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
c.fatalError(i, err0)
|
|
||||||
}
|
|
||||||
// Exhausted level low or fatal error: increase low and continue
|
|
||||||
// to process the bytes accepted so far.
|
|
||||||
i++
|
|
||||||
low = i
|
|
||||||
}
|
|
||||||
|
|
||||||
// If c.errStart > 0, this means we found a fatal error. We will clear
|
|
||||||
// all upstream buffers. At this point, no more progress can be made
|
|
||||||
// downstream, as Transform would have bailed while handling ErrShortDst.
|
|
||||||
if c.errStart > 0 {
|
|
||||||
for i := 1; i < c.errStart; i++ {
|
|
||||||
c.link[i].p, c.link[i].n = 0, 0
|
|
||||||
}
|
|
||||||
err, c.errStart, c.err = c.err, 0, nil
|
|
||||||
}
|
|
||||||
return dstL.n, srcL.p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use runes.Remove instead.
|
|
||||||
func RemoveFunc(f func(r rune) bool) Transformer {
|
|
||||||
return removeF(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
type removeF func(r rune) bool
|
|
||||||
|
|
||||||
func (removeF) Reset() {}
|
|
||||||
|
|
||||||
// Transform implements the Transformer interface.
|
|
||||||
func (t removeF) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
for r, sz := rune(0), 0; len(src) > 0; src = src[sz:] {
|
|
||||||
|
|
||||||
if r = rune(src[0]); r < utf8.RuneSelf {
|
|
||||||
sz = 1
|
|
||||||
} else {
|
|
||||||
r, sz = utf8.DecodeRune(src)
|
|
||||||
|
|
||||||
if sz == 1 {
|
|
||||||
// Invalid rune.
|
|
||||||
if !atEOF && !utf8.FullRune(src) {
|
|
||||||
err = ErrShortSrc
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// We replace illegal bytes with RuneError. Not doing so might
|
|
||||||
// otherwise turn a sequence of invalid UTF-8 into valid UTF-8.
|
|
||||||
// The resulting byte sequence may subsequently contain runes
|
|
||||||
// for which t(r) is true that were passed unnoticed.
|
|
||||||
if !t(r) {
|
|
||||||
if nDst+3 > len(dst) {
|
|
||||||
err = ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nDst += copy(dst[nDst:], "\uFFFD")
|
|
||||||
}
|
|
||||||
nSrc++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !t(r) {
|
|
||||||
if nDst+sz > len(dst) {
|
|
||||||
err = ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nDst += copy(dst[nDst:], src[:sz])
|
|
||||||
}
|
|
||||||
nSrc += sz
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// grow returns a new []byte that is longer than b, and copies the first n bytes
|
|
||||||
// of b to the start of the new slice.
|
|
||||||
func grow(b []byte, n int) []byte {
|
|
||||||
m := len(b)
|
|
||||||
if m <= 32 {
|
|
||||||
m = 64
|
|
||||||
} else if m <= 256 {
|
|
||||||
m *= 2
|
|
||||||
} else {
|
|
||||||
m += m >> 1
|
|
||||||
}
|
|
||||||
buf := make([]byte, m)
|
|
||||||
copy(buf, b[:n])
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialBufSize = 128
|
|
||||||
|
|
||||||
// String returns a string with the result of converting s[:n] using t, where
|
|
||||||
// n <= len(s). If err == nil, n will be len(s). It calls Reset on t.
|
|
||||||
func String(t Transformer, s string) (result string, n int, err error) {
|
|
||||||
t.Reset()
|
|
||||||
if s == "" {
|
|
||||||
// Fast path for the common case for empty input. Results in about a
|
|
||||||
// 86% reduction of running time for BenchmarkStringLowerEmpty.
|
|
||||||
if _, _, err := t.Transform(nil, nil, true); err == nil {
|
|
||||||
return "", 0, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate only once. Note that both dst and src escape when passed to
|
|
||||||
// Transform.
|
|
||||||
buf := [2 * initialBufSize]byte{}
|
|
||||||
dst := buf[:initialBufSize:initialBufSize]
|
|
||||||
src := buf[initialBufSize : 2*initialBufSize]
|
|
||||||
|
|
||||||
// The input string s is transformed in multiple chunks (starting with a
|
|
||||||
// chunk size of initialBufSize). nDst and nSrc are per-chunk (or
|
|
||||||
// per-Transform-call) indexes, pDst and pSrc are overall indexes.
|
|
||||||
nDst, nSrc := 0, 0
|
|
||||||
pDst, pSrc := 0, 0
|
|
||||||
|
|
||||||
// pPrefix is the length of a common prefix: the first pPrefix bytes of the
|
|
||||||
// result will equal the first pPrefix bytes of s. It is not guaranteed to
|
|
||||||
// be the largest such value, but if pPrefix, len(result) and len(s) are
|
|
||||||
// all equal after the final transform (i.e. calling Transform with atEOF
|
|
||||||
// being true returned nil error) then we don't need to allocate a new
|
|
||||||
// result string.
|
|
||||||
pPrefix := 0
|
|
||||||
for {
|
|
||||||
// Invariant: pDst == pPrefix && pSrc == pPrefix.
|
|
||||||
|
|
||||||
n := copy(src, s[pSrc:])
|
|
||||||
nDst, nSrc, err = t.Transform(dst, src[:n], pSrc+n == len(s))
|
|
||||||
pDst += nDst
|
|
||||||
pSrc += nSrc
|
|
||||||
|
|
||||||
// TODO: let transformers implement an optional Spanner interface, akin
|
|
||||||
// to norm's QuickSpan. This would even allow us to avoid any allocation.
|
|
||||||
if !bytes.Equal(dst[:nDst], src[:nSrc]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pPrefix = pSrc
|
|
||||||
if err == ErrShortDst {
|
|
||||||
// A buffer can only be short if a transformer modifies its input.
|
|
||||||
break
|
|
||||||
} else if err == ErrShortSrc {
|
|
||||||
if nSrc == 0 {
|
|
||||||
// No progress was made.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Equal so far and !atEOF, so continue checking.
|
|
||||||
} else if err != nil || pPrefix == len(s) {
|
|
||||||
return string(s[:pPrefix]), pPrefix, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Post-condition: pDst == pPrefix + nDst && pSrc == pPrefix + nSrc.
|
|
||||||
|
|
||||||
// We have transformed the first pSrc bytes of the input s to become pDst
|
|
||||||
// transformed bytes. Those transformed bytes are discontiguous: the first
|
|
||||||
// pPrefix of them equal s[:pPrefix] and the last nDst of them equal
|
|
||||||
// dst[:nDst]. We copy them around, into a new dst buffer if necessary, so
|
|
||||||
// that they become one contiguous slice: dst[:pDst].
|
|
||||||
if pPrefix != 0 {
|
|
||||||
newDst := dst
|
|
||||||
if pDst > len(newDst) {
|
|
||||||
newDst = make([]byte, len(s)+nDst-nSrc)
|
|
||||||
}
|
|
||||||
copy(newDst[pPrefix:pDst], dst[:nDst])
|
|
||||||
copy(newDst[:pPrefix], s[:pPrefix])
|
|
||||||
dst = newDst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent duplicate Transform calls with atEOF being true at the end of
|
|
||||||
// the input. Also return if we have an unrecoverable error.
|
|
||||||
if (err == nil && pSrc == len(s)) ||
|
|
||||||
(err != nil && err != ErrShortDst && err != ErrShortSrc) {
|
|
||||||
return string(dst[:pDst]), pSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform the remaining input, growing dst and src buffers as necessary.
|
|
||||||
for {
|
|
||||||
n := copy(src, s[pSrc:])
|
|
||||||
atEOF := pSrc+n == len(s)
|
|
||||||
nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], atEOF)
|
|
||||||
pDst += nDst
|
|
||||||
pSrc += nSrc
|
|
||||||
|
|
||||||
// If we got ErrShortDst or ErrShortSrc, do not grow as long as we can
|
|
||||||
// make progress. This may avoid excessive allocations.
|
|
||||||
if err == ErrShortDst {
|
|
||||||
if nDst == 0 {
|
|
||||||
dst = grow(dst, pDst)
|
|
||||||
}
|
|
||||||
} else if err == ErrShortSrc {
|
|
||||||
if atEOF {
|
|
||||||
return string(dst[:pDst]), pSrc, err
|
|
||||||
}
|
|
||||||
if nSrc == 0 {
|
|
||||||
src = grow(src, 0)
|
|
||||||
}
|
|
||||||
} else if err != nil || pSrc == len(s) {
|
|
||||||
return string(dst[:pDst]), pSrc, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns a new byte slice with the result of converting b[:n] using t,
|
|
||||||
// where n <= len(b). If err == nil, n will be len(b). It calls Reset on t.
|
|
||||||
func Bytes(t Transformer, b []byte) (result []byte, n int, err error) {
|
|
||||||
return doAppend(t, 0, make([]byte, len(b)), b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append appends the result of converting src[:n] using t to dst, where
|
|
||||||
// n <= len(src), If err == nil, n will be len(src). It calls Reset on t.
|
|
||||||
func Append(t Transformer, dst, src []byte) (result []byte, n int, err error) {
|
|
||||||
if len(dst) == cap(dst) {
|
|
||||||
n := len(src) + len(dst) // It is okay for this to be 0.
|
|
||||||
b := make([]byte, n)
|
|
||||||
dst = b[:copy(b, dst)]
|
|
||||||
}
|
|
||||||
return doAppend(t, len(dst), dst[:cap(dst)], src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doAppend(t Transformer, pDst int, dst, src []byte) (result []byte, n int, err error) {
|
|
||||||
t.Reset()
|
|
||||||
pSrc := 0
|
|
||||||
for {
|
|
||||||
nDst, nSrc, err := t.Transform(dst[pDst:], src[pSrc:], true)
|
|
||||||
pDst += nDst
|
|
||||||
pSrc += nSrc
|
|
||||||
if err != ErrShortDst {
|
|
||||||
return dst[:pDst], pSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grow the destination buffer, but do not grow as long as we can make
|
|
||||||
// progress. This may avoid excessive allocations.
|
|
||||||
if nDst == 0 {
|
|
||||||
dst = grow(dst, pDst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
vendor/modules.txt
vendored
22
vendor/modules.txt
vendored
@@ -1,9 +1,6 @@
|
|||||||
# github.com/davecgh/go-spew v1.1.1
|
# github.com/davecgh/go-spew v1.1.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/davecgh/go-spew/spew
|
github.com/davecgh/go-spew/spew
|
||||||
# github.com/eleksir/go-xmpp v1.6.0
|
|
||||||
## explicit; go 1.20
|
|
||||||
github.com/eleksir/go-xmpp
|
|
||||||
# github.com/google/uuid v1.6.0
|
# github.com/google/uuid v1.6.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/google/uuid
|
github.com/google/uuid
|
||||||
@@ -13,19 +10,14 @@ github.com/hjson/hjson-go
|
|||||||
# github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
# github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
||||||
## explicit
|
## explicit
|
||||||
github.com/jbrukh/bayesian
|
github.com/jbrukh/bayesian
|
||||||
# golang.org/x/crypto v0.38.0
|
# github.com/xmppo/go-xmpp v0.3.1
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.24.0
|
||||||
golang.org/x/crypto/pbkdf2
|
github.com/xmppo/go-xmpp
|
||||||
# golang.org/x/net v0.40.0
|
# golang.org/x/net v0.48.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.24.0
|
||||||
golang.org/x/net/context
|
golang.org/x/net/context
|
||||||
# golang.org/x/text v0.25.0
|
golang.org/x/net/internal/socks
|
||||||
## explicit; go 1.23.0
|
golang.org/x/net/proxy
|
||||||
golang.org/x/text/encoding
|
|
||||||
golang.org/x/text/encoding/charmap
|
|
||||||
golang.org/x/text/encoding/internal
|
|
||||||
golang.org/x/text/encoding/internal/identifier
|
|
||||||
golang.org/x/text/transform
|
|
||||||
# gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
# gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/tomb.v2
|
gopkg.in/tomb.v2
|
||||||
|
|||||||
Reference in New Issue
Block a user