[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:
eleksir
2025-11-30 14:19:01 +03:00
parent a128b473ff
commit 3d85dbcb58
55 changed files with 4165 additions and 13190 deletions

View File

@@ -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,7 +81,9 @@ func main() {
verboseClient := false verboseClient := false
if log.LogLevel == slog.LevelDebug { if log.LogLevel == slog.LevelDebug {
verboseClient = true if j.C.Debug.Lib {
verboseClient = true
}
} }
// Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог. // Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог.
@@ -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 и некоторые

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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,32 +423,42 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
_ = json.Unmarshal([]byte(presenceJSONString), &p) _ = json.Unmarshal([]byte(presenceJSONString), &p)
if p.Affiliation == "admin" || p.Affiliation == "owner" {
log.Infof("Skipping useragent blacklist check for %s because his affiliation is %s", v.From, p.Affiliation)
continue
}
// На всякий случай.
if p.Role == "moderator" {
log.Infof("Skipping useragent blacklist check for %s because his role is %s", v.From, p.Role)
continue
}
if v.From == p.From { if v.From == p.From {
log.Errorf("Found jid of %s in presence db: %s", v.From, p.JID) log.Infof("Found jid of %s in presence db: %s", v.From, p.JID)
if p.Affiliation == "admin" || p.Affiliation == "owner" {
log.Infof("Skipping useragent blacklist check for %s because his affiliation is %s", v.From, p.Affiliation)
continue
}
// На всякий случай.
if p.Role == "moderator" {
log.Infof("Skipping useragent blacklist check for %s because his role is %s", v.From, p.Role)
continue
}
log.Debug("Useragent is not owner or moderator, proceeding with check...")
// 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 {

View File

@@ -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)
}
} }

View File

@@ -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)
}
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"syscall" "syscall"
"github.com/eleksir/go-xmpp" "github.com/xmppo/go-xmpp"
) )
// SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем. // SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем.

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/eleksir/go-xmpp" "github.com/xmppo/go-xmpp"
) )
// Squash банит указанный jid в указанной комнате. // Squash банит указанный jid в указанной комнате.

View File

@@ -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,9 +14,13 @@ 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"`
Version string `json:"version,omitempty"` Debug struct {
ExeName string `json:"exe_name,omitempty"` Lib bool `json:"lib,omitempty"`
Jabber struct { Ping bool `json:"ping,omitempty"`
} `json:"debug"`
Version string `json:"version,omitempty"`
ExeName string `json:"exe_name,omitempty"`
Jabber struct {
Server string `json:"server,omitempty"` Server string `json:"server,omitempty"`
Nick string `json:"nick,omitempty"` Nick string `json:"nick,omitempty"`
Resource string `json:"resource,omitempty"` Resource string `json:"resource,omitempty"`
@@ -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"`

View File

@@ -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:
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server) if j.C.Debug.Ping {
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 { // Первая пуля пока не вылетела, отправляем.
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server) if j.C.Debug.Ping {
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:
log.Debug("Sending keepalive whitespace ping") if j.C.Debug.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.
log.Debug("Sending keepalive whitespace ping") if j.C.Debug.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)
log.Debugf("Sending MUC ping from %s to %s", j.Talk.JID(), room) if j.C.Debug.Ping {
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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

File diff suppressed because it is too large Load Diff

View File

@@ -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,
)
}

View File

@@ -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))
}

View File

@@ -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,
"",
)
}

View File

@@ -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
View File

@@ -0,0 +1,5 @@
language: go
go:
- tip
script:
- go test

View File

@@ -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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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)
} }

View File

@@ -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,

View File

@@ -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
View 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"`
}

View 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
View 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
View 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()
}
}
}

View File

@@ -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
View 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
View File

@@ -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
View File

@@ -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.

View File

@@ -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]
}

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -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.

View File

@@ -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
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
}

View File

@@ -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
)

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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
View File

@@ -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