Compare commits

...

10 Commits

Author SHA1 Message Date
Sergei
6c0bb895d9 Add config for glangci-lint, lint, handle "wire" errors fo auth procedure 2023-09-24 14:12:17 -07:00
Sergei
af169899cd Implement IQ responses Service Unavailable and Feature not Implemented 2023-08-18 13:31:49 -07:00
Sergei
3d611c2991 Properly pass errors in xmpp_pubsub.go 2023-08-18 13:29:48 -07:00
Sergei
2696ef2a9d Implement IQ responses to version, last activity, client time
* Implement IqVersionResponse(), XEP-0092
* Implement JabberIqLastResponse(), XEP-0012
* Implement UrnXmppTimeResponse(), XEP-0202
* Fix function descriptions
2023-08-18 13:28:00 -07:00
Sergei
d1f8a0defa Implement ping response according to xep-0199 2023-08-18 13:21:57 -07:00
Sergei
84bd84a051 Fix field name in Presence struct 2023-07-29 22:39:38 -07:00
Sergei
a21cf6d866 Extend Presence struct with real jid, affiliation and role info
This enables client to get full info about jids from MUCs presence
notifications, if this feature enabled in MUC
2023-07-23 15:13:44 -07:00
Sergei
b88ebc5f25 Update README.md, LICENSE, clean up
* Add missing persons who took part in developing of this lib in the the past
* Update README.md with a bits of info
* Remove settngs for travis ci since i don't use it anyway
2023-07-23 15:01:27 -07:00
eleksir
e096bfe29f Do not send node attribute in query tag in DiscoInfo() 2023-06-24 16:44:33 +03:00
eleksir
5a1c0b9e09 Extend DiscoResult type with ID, From and To fields 2023-06-24 15:45:21 +03:00
15 changed files with 869 additions and 245 deletions

66
.golangci.yml Normal file
View File

@@ -0,0 +1,66 @@
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,5 +0,0 @@
language: go
go:
- tip
script:
- go test

View File

@@ -1,4 +1,7 @@
Copyright (c) 2009 The Go Authors. All rights reserved. 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 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

View File

@@ -1,6 +1,14 @@
go-xmpp go-xmpp
======= =======
go xmpp library (original was written by russ cox ) go xmpp library.
[Documentation](https://godoc.org/github.com/mattn/go-xmpp) [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.

5
go.mod
View File

@@ -2,4 +2,7 @@ module github.com/eleksir/go-xmpp
go 1.20 go 1.20
require golang.org/x/crypto v0.10.0 require (
golang.org/x/crypto v0.10.0
golang.org/x/text v0.10.0
)

2
go.sum
View File

@@ -1,2 +1,4 @@
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=

694
xmpp.go

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
package xmpp package xmpp
import ( import (
"crypto/sha1" "crypto/sha1" //nolint:gosec // go fuck yourself, gosec. You don't even fucking read rfc. So shout the fuck up.
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/xml" "encoding/xml"
@@ -10,8 +10,8 @@ import (
) )
const ( const (
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data" XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data" //nocritic:revive
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata" XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata" //nocritic:revive
) )
type clientAvatarData struct { type clientAvatarData struct {
@@ -52,8 +52,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 != nil { if err := xml.Unmarshal(itemsBody, &data); err != nil {
return AvatarData{}, err return AvatarData{}, err
} }
@@ -64,8 +64,9 @@ func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
return AvatarData{}, err return AvatarData{}, err
} }
hash := sha1.Sum(dataRaw) hash := sha1.Sum(dataRaw) //nolint:gosec // go fuck yourself, gosec. This exact mechanics described in standard.
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")
} }
@@ -77,9 +78,12 @@ 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 meta clientAvatarMetadata var (
err := xml.Unmarshal(body, &meta) meta clientAvatarMetadata
if err != nil { err error
)
if err = xml.Unmarshal(body, &meta); err != nil {
return AvatarMetadata{}, err return AvatarMetadata{}, err
} }
@@ -91,10 +95,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,
}, nil }, err
} }
// A wrapper for atoi which just returns -1 if an error occurs // atoiw is 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 {
@@ -104,22 +108,22 @@ func atoiw(str string) int {
return i return i
} }
func (c *Client) AvatarSubscribeMetadata(jid string) { func (c *Client) AvatarSubscribeMetadata(jid string) (string, error) {
c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid) return c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
} }
func (c *Client) AvatarUnsubscribeMetadata(jid string) { func (c *Client) AvatarUnsubscribeMetadata(jid string) (string, error) {
c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid) return c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
} }
func (c *Client) AvatarRequestData(jid string) { func (c *Client) AvatarRequestData(jid string) (string, error) {
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid) return c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
} }
func (c *Client) AvatarRequestDataByID(jid, id string) { func (c *Client) AvatarRequestDataByID(jid, id string) (string, error) {
c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id) return c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
} }
func (c *Client) AvatarRequestMetadata(jid string) { func (c *Client) AvatarRequestMetadata(jid string) (string, error) {
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid) return c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
} }

View File

@@ -5,8 +5,8 @@ import (
) )
const ( const (
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items" XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items" //nocritic:revive
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info" XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info" //nocritic:revive
) )
type clientDiscoFeature struct { type clientDiscoFeature struct {
@@ -52,6 +52,9 @@ type DiscoItem struct {
} }
type DiscoResult struct { type DiscoResult struct {
ID string
From string
To string
Features []string Features []string
Identities []DiscoIdentity Identities []DiscoIdentity
} }
@@ -87,6 +90,7 @@ 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,

57
xmpp_error.go Normal file
View File

@@ -0,0 +1,57 @@
package xmpp
import (
"fmt"
)
// ErrorServiceUnavailable implements error response about feature that is not available. Currently implemented for
// xep-0030.
// 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).
//
// If queried feature is not here on purpose, standards suggest answer with this stanza.
func (c *Client) ErrorServiceUnavailable(v IQ, queryXmlns, node string) (string, error) {
query := fmt.Sprintf("<query xmlns=\"%s\" ", queryXmlns)
if node != "" {
query += fmt.Sprintf("node=\"%s\" />", node)
} else {
query += "/>"
}
query += "<error type=\"cancel\">"
query += "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
query += "</error>"
return c.RawInformation(
v.To,
v.From,
v.ID,
IQTypeError,
query,
)
}
// ErrorNotImplemented implements error response about feature that is not (yet?) implemented.
// 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
// this stanza.
func (c *Client) ErrorNotImplemented(v IQ, xmlns, feature string) (string, error) {
query := "<error type=\"cancel\">"
query += "<feature-not-implemented xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
query += fmt.Sprintf(
"<unsupported xmlns=\"%s\" feature=\"%s\" />",
xmlns,
feature,
)
query += "</error>"
return c.RawInformation(
v.To,
v.From,
v.ID,
IQTypeError,
query,
)
}

View File

@@ -3,6 +3,7 @@ package xmpp
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"time"
) )
const IQTypeGet = "get" const IQTypeGet = "get"
@@ -13,29 +14,33 @@ const IQTypeError = "error"
func (c *Client) Discovery() (string, error) { func (c *Client) Discovery() (string, error) {
// use getCookie for a pseudo random id. // use getCookie for a pseudo random id.
reqID := strconv.FormatUint(uint64(getCookie()), 10) reqID := strconv.FormatUint(uint64(getCookie()), 10)
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "") return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
} }
// Discover information about a node. Empty node queries info about server itself. // DiscoverNodeInfo discovers information about a node. Empty node queries info about server itself.
func (c *Client) DiscoverNodeInfo(node string) (string, error) { func (c *Client) DiscoverNodeInfo(node string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node) query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node)
return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query) return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query)
} }
// Discover information about given item from given jid. // DiscoverInfo discovers information about given item from given jid.
func (c *Client) DiscoverInfo(from string, to string, node string) (string, error) { func (c *Client) DiscoverInfo(from string, to string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node) query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_INFO)
return c.RawInformation(from, to, "info3", IQTypeGet, query) return c.RawInformation(from, to, "info3", IQTypeGet, query)
} }
// Discover items that the server exposes // DiscoverServerItems discover items that the server exposes.
func (c *Client) DiscoverServerItems() (string, error) { func (c *Client) DiscoverServerItems() (string, error) {
return c.DiscoverEntityItems(c.domain) return c.DiscoverEntityItems(c.domain)
} }
// Discover items that an entity exposes // DiscoverEntityItems discovers items that an entity exposes.
func (c *Client) DiscoverEntityItems(jid string) (string, error) { func (c *Client) DiscoverEntityItems(jid string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS) query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query) return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
} }
@@ -43,12 +48,78 @@ func (c *Client) DiscoverEntityItems(jid string) (string, error) {
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) { 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>" 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) _, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
return id, err return id, err
} }
// rawInformation send a IQ request with the payload body to the server // RawInformation sends a IQ request with the payload body to the server.
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) { 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>" 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) _, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
return id, err 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

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// TODO(flo): // TODO(flo):
// - support password protected MUC rooms
// - cleanup signatures of join/leave functions // - cleanup signatures of join/leave functions
package xmpp package xmpp
@@ -23,16 +22,18 @@ const (
SinceHistory = 4 SinceHistory = 4
) )
// Send sends room topic wrapped inside an XMPP message stanza body. // SendTopic sends room topic wrapped inside an XMPP message stanza body.
func (c *Client) SendTopic(chat Chat) (n int, err error) { func (c *Client) SendTopic(chat Chat) (int, error) {
return fmt.Fprintf(StanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>", 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)) xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
} }
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) { // JoinMUCNoHistory joins room and instructs server that no history data required.
func (c *Client) JoinMUCNoHistory(jid, nick string) (int, error) {
if nick == "" { if nick == "" {
nick = c.jid nick = c.jid
} }
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+ return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>"+ "<x xmlns='%s'>"+
"<history maxchars='0'/></x>\n"+ "<history maxchars='0'/></x>\n"+
@@ -40,12 +41,13 @@ func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
xmlEscape(jid), xmlEscape(nick), nsMUC) xmlEscape(jid), xmlEscape(nick), nsMUC)
} }
// xep-0045 7.2 // JoinMUC joins room, xep-0045 7.2.
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) { func (c *Client) JoinMUC(jid, nick string, historyType, history int, historyDate *time.Time) (int, error) {
if nick == "" { if nick == "" {
nick = c.jid nick = c.jid
} }
switch history_type {
switch historyType {
case NoHistory: case NoHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+ return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s' />\n"+ "<x xmlns='%s' />\n"+
@@ -70,23 +72,25 @@ func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_da
"</presence>", "</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history) xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SinceHistory: case SinceHistory:
if history_date != nil { if historyDate != nil {
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+ return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+ "<x xmlns='%s'>\n"+
"<history since='%s'/></x>\n"+ "<history since='%s'/></x>\n"+
"</presence>", "</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339)) xmlEscape(jid), xmlEscape(nick), nsMUC, historyDate.Format(time.RFC3339))
} }
} }
return 0, errors.New("Unknown history option")
return 0, errors.New("unknown history option")
} }
// xep-0045 7.2.6 // JoinProtectedMUC joins password protected room, 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) { func (c *Client) JoinProtectedMUC(jid, nick string, password string, historyType, history int, historyDate *time.Time) (int, error) {
if nick == "" { if nick == "" {
nick = c.jid nick = c.jid
} }
switch history_type {
switch historyType {
case NoHistory: case NoHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+ return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+ "<x xmlns='%s'>\n"+
@@ -116,20 +120,21 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
"</presence>", "</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case SinceHistory: case SinceHistory:
if history_date != nil { if historyDate != nil {
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+ return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+ "<x xmlns='%s'>\n"+
"<password>%s</password>\n"+ "<password>%s</password>\n"+
"<history since='%s'/></x>\n"+ "<history since='%s'/></x>\n"+
"</presence>", "</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339)) xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), historyDate.Format(time.RFC3339))
} }
} }
return 0, errors.New("Unknown history option")
return 0, errors.New("unknown history option")
} }
// xep-0045 7.14 // LeaveMUC quits from room xep-0045 7.14.
func (c *Client) LeaveMUC(jid string) (n int, err error) { func (c *Client) LeaveMUC(jid string) (int, error) {
return fmt.Fprintf(StanzaWriter, "<presence from='%s' to='%s' type='unavailable' />", return fmt.Fprintf(StanzaWriter, "<presence from='%s' to='%s' type='unavailable' />",
c.jid, xmlEscape(jid)) c.jid, xmlEscape(jid))
} }

View File

@@ -8,13 +8,16 @@ func (c *Client) PingC2S(jid, server string) error {
if jid == "" { if jid == "" {
jid = c.jid jid = c.jid
} }
if server == "" { if server == "" {
server = c.domain server = c.domain
} }
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+ _, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
"<ping xmlns='urn:xmpp:ping'/>\n"+ "<ping xmlns='urn:xmpp:ping'/>\n"+
"</iq>", "</iq>",
xmlEscape(jid), xmlEscape(server)) xmlEscape(jid), xmlEscape(server))
return err return err
} }
@@ -23,11 +26,24 @@ func (c *Client) PingS2S(fromServer, toServer string) error {
"<ping xmlns='urn:xmpp:ping'/>\n"+ "<ping xmlns='urn:xmpp:ping'/>\n"+
"</iq>", "</iq>",
xmlEscape(fromServer), xmlEscape(toServer)) xmlEscape(fromServer), xmlEscape(toServer))
return err return err
} }
func (c *Client) SendResultPing(id, toServer string) error { func (c *Client) SendResultPing(id, toServer string) error {
_, err := fmt.Fprintf(StanzaWriter, "<iq type='result' to='%s' id='%s'/>", _, err := fmt.Fprintf(StanzaWriter, "<iq type='result' to='%s' id='%s'/>",
xmlEscape(toServer), xmlEscape(id)) xmlEscape(toServer), xmlEscape(id))
return err 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

@@ -6,8 +6,8 @@ import (
) )
const ( const (
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub" XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub" //nolint:revive
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event" XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event" //nolint:revive
) )
type clientPubsubItem struct { type clientPubsubItem struct {
@@ -67,7 +67,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,6 +96,7 @@ 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)
} }
@@ -103,31 +104,34 @@ 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) { func (c *Client) PubsubSubscribeNode(node, jid string) (string, error) {
c.RawInformation(c.jid, return c.RawInformation(c.jid,
jid, jid,
"sub1", "sub1",
"set", "set",
pubsubSubscriptionStanza(node, c.jid)) pubsubSubscriptionStanza(node, c.jid))
} }
func (c *Client) PubsubUnsubscribeNode(node, jid string) { func (c *Client) PubsubUnsubscribeNode(node, jid string) (string, error) {
c.RawInformation(c.jid, return c.RawInformation(c.jid,
jid, jid,
"unsub1", "unsub1",
"set", "set",
pubsubUnsubscriptionStanza(node, c.jid)) pubsubUnsubscriptionStanza(node, c.jid))
} }
func (c *Client) PubsubRequestLastItems(node, jid string) { func (c *Client) PubsubRequestLastItems(node, jid string) (string, error) {
body := fmt.Sprintf("<items node='%s'/>", node) body := fmt.Sprintf("<items node='%s'/>", node)
c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
return c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
} }
func (c *Client) PubsubRequestItem(node, jid, id string) { func (c *Client) PubsubRequestItem(node, jid, id string) (string, error) {
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id) body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
return c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
} }

View File

@@ -4,22 +4,30 @@ import (
"fmt" "fmt"
) )
func (c *Client) ApproveSubscription(jid string) { func (c *Client) ApproveSubscription(jid string) error {
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribed'/>", _, err := fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribed'/>",
xmlEscape(jid)) xmlEscape(jid))
return err
} }
func (c *Client) RevokeSubscription(jid string) { func (c *Client) RevokeSubscription(jid string) error {
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='unsubscribed'/>", _, err := fmt.Fprintf(StanzaWriter, "<presence to='%s' type='unsubscribed'/>",
xmlEscape(jid)) xmlEscape(jid))
return err
} }
func (c *Client) RetrieveSubscription(jid string) { func (c *Client) RetrieveSubscription(jid string) error {
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>", _, err := fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>",
xmlEscape(jid)) xmlEscape(jid))
return err
} }
func (c *Client) RequestSubscription(jid string) { func (c *Client) RequestSubscription(jid string) error {
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribe'/>", _, err := fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribe'/>",
xmlEscape(jid)) xmlEscape(jid))
return err
} }