Compare commits

...

128 Commits

Author SHA1 Message Date
370c500a5e
xmpp: Add reply and stanza ID serialization/deserialization 2024-11-04 04:46:30 +01:00
Wim
7154bfeb76
Update module 2024-05-24 01:01:55 +02:00
ValdikSS
243a438354
Implement XMPP message ID and message correction functionality 2024-05-24 00:56:19 +02:00
Martin Dosch
e9123cc4b3 Make use of FAST configurable. 2024-05-11 15:05:41 +02:00
Martin Dosch
4be597a84a Add commit that Client.Mechanism is the SCRAM mechanism used. 2024-05-11 14:55:32 +02:00
Martin Dosch
464fbe04ef Update go modules. 2024-05-06 21:16:36 +02:00
Martin Dosch
e223dcf94b Only set from if connection is encrypted. 2024-04-23 21:24:34 +02:00
Martin Dosch
321c2b14a5 FAST: Add option to invalidate current fast token. 2024-04-13 12:08:36 +02:00
Martin Dosch
9161feef4d FAST: Check that connection is encrypted. 2024-04-12 17:27:33 +02:00
Martin Dosch
fc3ed9a0b8 Update go modules. 2024-04-12 12:56:37 +02:00
Martin Dosch
d9df620fa4 Revert "FAST success: New token OR reduced expiry not AND."
This reverts commit f067814851.
2024-04-12 11:51:56 +02:00
Martin Dosch
f067814851 FAST success: New token OR reduced expiry not AND. 2024-04-12 10:53:15 +02:00
Martin Dosch
961b7e435e FAST: Check for changed token after sasl2Success. 2024-04-12 10:51:09 +02:00
Martin Dosch
12a04e0950 Set shutdown earlier to further reduce probability of races. 2024-04-11 09:50:28 +02:00
Martin
2f331ed19c
Merge pull request #188 from mdosch/fast
Basic FAST support.
2024-04-10 15:14:39 +02:00
Martin Dosch
b0f55a8f7f Basic FAST support. 2024-04-10 15:13:20 +02:00
Martin
7486b7a363
Add support for SASL2 and BIND2 (#187)
* Add basic support for SASL2 (XEP-0388) and Bind2 (XEP-0386).
2024-04-09 10:53:38 +02:00
Martin Dosch
da2377ecb0 Recv: Return error on stream error. 2024-04-07 11:25:16 +02:00
Martin Dosch
44095406a2 Update go modules. 2024-04-07 00:59:08 +02:00
Martin Dosch
d7aee6b636 Harmonize newlines. 2024-04-05 11:57:09 +02:00
Martin Dosch
0324b31f56 Update go modules. 2024-04-04 11:05:14 +02:00
Martin Dosch
ca4e49201e Do not try to read from the xml stream if it's going to be closed. 2024-04-02 16:32:30 +02:00
Martin Dosch
6e5d6e449e Remove checking for xml.Endelement in nextStart().
This seems to have negative side effects on parsing in next()
2024-04-02 15:48:34 +02:00
Martin Dosch
0ae62a33a2 Further reduce possible data races. 2024-04-02 13:39:45 +02:00
Martin Dosch
ce687243c1 NextEnd: Use Token() instead of RawToken()
I assumed RawToken would be better suited here, but there were errors
parsing messages that could be fixed by reverting to Token().
2024-03-31 15:50:04 +02:00
Martin Dosch
78d07e9eee XEP-0478: Further improve error message. 2024-03-28 18:17:45 +01:00
Martin Dosch
416bb6e7b7 XEP-0478: Be more verbose in error if max stanza size is exceeded. 2024-03-28 18:07:23 +01:00
Martin Dosch
aef1257ed1 Fix timeout when server doesn't reply with closing stream element. 2024-03-28 17:22:02 +01:00
Martin Dosch
da17a46e6f Also read stream limits after authentication
The [business rules](https://xmpp.org/extensions/xep-0478.html#rules)
mention the following:

> It is acceptable for the limits on a stream to change whenever new stream features are announced - such as before and after authentication of the connecting entity.

The first detection of the stream limits is not deleted as there is also
ANONYMOUS authentication.
2024-03-28 15:57:56 +01:00
Martin Dosch
eedd7259cb Generate new ID for session (fixes #67)
Co-authored-by: https://github.com/ikq
2024-03-28 15:53:09 +01:00
Martin Dosch
07196efcf3 Add support for XEP-0478: Stream Limits Advertisement. 2024-03-28 15:49:08 +01:00
Martin Dosch
bbd90cc04b Copy token in nextStart function.
See
bc81053dbc (commitcomment-140301890)
and bc81053dbc (commitcomment-140303962)
2024-03-27 21:12:42 +01:00
Martin Dosch
0c7ee22452 Revert "Don't copy token."
This reverts commit bc81053dbc.
2024-03-27 21:09:43 +01:00
Martin Dosch
862c21f845 Make XEP-0474 configurable (default off).
As it is still experimental we should not enable it per default.
2024-03-26 21:36:15 +01:00
Martin Dosch
bc81053dbc Don't copy token.
This should be unnecessary and is probably a
leftover of previous experiments to fix some races
when closing the connection.
2024-03-26 19:26:19 +01:00
Martin
94ab540b80
Merge pull request #186 from vcabbage/move-mutex
move nextMutex to Client to prevent blocking separate Clients
2024-03-26 19:18:05 +01:00
Kale Blankenship
f6a9836fdf move nextMutex to Client to prevent blocking separate Clients
Avoids a global mutex which could end up unexpectedly blocking a
separate client. For example, if there were a client with few messages
and a client with many messages, the client with few could hold the lock
waiting for a token blocking the client with many from receiving.
2024-03-26 11:02:05 -07:00
Martin Dosch
8ab32d885f Fix race condition for nextStart and nextEnd. 2024-03-16 19:04:09 +01:00
Martin Dosch
73f06c9f3d Catch stream error after bind request. 2024-03-10 13:44:33 +01:00
Martin Dosch
9c5e758356 Use RawToken() instead of Token() for finding nextEnd.
This should improve stability as RawToken() does not
verify that start and end elements match.
2024-03-10 11:30:39 +01:00
Martin Dosch
ea4874e8c9 SCRAM: Check for SASL failure after sending auth message. 2024-03-09 19:51:47 +01:00
Martin Dosch
dab6865bd2 Update go modules. 2024-03-06 21:01:03 +01:00
Martin Dosch
c051d69509 Improve closing the stream. 2024-03-03 12:10:45 +01:00
Martin Dosch
aed021cf3e Wait for the server closing the stream before closing the connection. 2024-03-02 14:17:47 +01:00
Martin Dosch
2c4708e724
[gofumpt]: Remove empty line. 2024-02-27 22:26:58 +01:00
Martin Dosch
746409f074
Update dependencies. 2024-02-26 23:04:17 +01:00
Martin Dosch
9684a8ff69 Close stream before closing connection. 2024-02-25 12:25:57 +01:00
Martin Dosch
b7ea9f4be1 Update dependencies. 2024-02-23 13:55:02 +01:00
Martin Dosch
e2bc7bf6d7 Remove unused type clientPubsub. 2024-02-22 18:59:22 +01:00
Martin Dosch
0bcc057225 Remove unused types. 2024-02-22 17:58:48 +01:00
Martin Dosch
49054ca9e9 Remove unused function saslDigestResponse. 2024-02-22 17:55:38 +01:00
Martin
b369b7df10
Merge pull request #183 from mdosch/update-docs
Update import path in examples.
2024-02-01 13:58:52 +08:00
Martin Dosch
cc481e54e7 Update import path in examples. 2024-02-01 13:58:04 +08:00
Martin
88855eac82
Merge pull request #182 from mdosch/gofumpt
[gofumpt] Improve formatting.
2024-02-01 13:56:27 +08:00
Martin Dosch
0cc0a72c15 [gofumpt] Improve formatting. 2024-02-01 13:54:58 +08:00
Martin
d6e9a15f29
Merge pull request #181 from mdosch/scram-improvement
Improve RFC5802 compatibility.
2024-02-01 13:52:47 +08:00
Martin Dosch
6ffd595a06 Improve RFC5802 compatibility.
According to RFC 5802 5.1 only the "m" attribute should
cause an authentication error.
2024-02-01 13:35:55 +08:00
Martin
62928b3483
Merge pull request #180 from mdosch/fix-134
Filter invalid UTF8 from message body.
2024-01-18 20:04:09 +01:00
Martin Dosch
d67787ca0f Filter invalid UTF8 from message body.
Closes #134
2024-01-18 19:46:18 +01:00
Martin Dosch
f8a24505f4 Update dependencies. 2024-01-14 00:15:36 +01:00
Martin
685570cbd8
Merge pull request #179 from mdosch/socks
Add support for SOCKS5 proxies.
2024-01-13 14:58:10 +01:00
Martin Dosch
7bfa331758 Add support for SOCKS5 proxies. 2024-01-13 14:05:35 +01:00
Martin
3f0cbac307
Merge pull request #178 from mdosch/server-end-point
Tls-server-end-point improvements.
2024-01-12 12:12:52 +01:00
Martin Dosch
7ccad52e63 (Indirectly) check that TLS was not renegotioated when using "tls-server-end-point". 2024-01-12 12:10:06 +01:00
Martin Dosch
705f68d1a5 Simplify tls-server-end-point channel binding code. 2024-01-12 11:56:32 +01:00
Martin Dosch
b49bdce100 Merge branch 'master' into server-end-point 2024-01-12 11:54:03 +01:00
Martin
f4c732fdc7
SCRAM: Add support for tls-server-end-point channel binding. (#177) 2024-01-11 13:33:32 +01:00
Martin Dosch
d3d16d5db9 SCRAM: Add support for tls-server-end-point channel binding. 2024-01-11 13:29:59 +01:00
Eleksir
34d683d25a
Extend SendPresence() stub func to allow send useful statuses (#150) 2024-01-10 23:24:28 +01:00
Martin
dffa92c129
Remove DIGEST-MD5 (#171)
As mentioned in https://github.com/xmppo/go-xmpp/issues/166#issuecomment-1884898526
DIGEST-MD5 is obsolete for a long time now.
2024-01-10 22:41:08 +01:00
Martin
8531e2e36a
improve no more auth err msg (#176)
Improve error message when no viable authentication method is available
2024-01-10 16:17:02 +01:00
Martin
e7d5b17113
Readability improvements. (#175)
* Improve readability of switch statement for auth mechanism choice

We have enough space in the width here, so it is not
necessary to span the cases over two lines.
2024-01-10 16:04:40 +01:00
Martin
c1b9689e75
Change import path to xmppo/go-xmpp (#174)
* Change import path to xmppo/go-xmpp
2024-01-10 15:52:41 +01:00
Martin
424970d23c
Fix manual choice of auth mechanism. (#173) 2024-01-10 15:49:08 +01:00
Martin
5fdcf18a81
Simplify authentication choice code. (#169)
* Simplify authentication choice code.

Should be a lot easier to read and understand now.
2024-01-10 14:53:36 +01:00
Martin
794ed98f9f
Provide access to xml:lang information. (#168) 2024-01-10 13:57:27 +01:00
Martin
2f9bd427e8
Merge pull request #167 from mdosch/master
Update go.mod
2024-01-10 13:18:48 +01:00
Martin Dosch
70c2fe6900 Update dependencies. 2024-01-10 13:17:25 +01:00
Martin
39f5b80375
Authentication improvements. (#165)
* Add XEP-0474 support.
* Add missing error handling.
2024-01-09 10:24:56 +01:00
Martin Dosch
2449f4192b Remove debugging stuff.
Remove previously overlooked println.
2024-01-08 19:48:13 +01:00
Martin Dosch
3462085098 Add missing error handling. 2024-01-08 19:32:08 +01:00
Martin Dosch
6c9243326e Add XEP-0474 support. 2024-01-08 19:30:17 +01:00
Martin
31c7eb6919
Merge pull request #155 from mdosch/rework-newlines
Harmonize newlines
2023-11-11 15:10:53 +01:00
Martin Dosch
9dcf67c0ad Merge branch 'master' into rework-newlines 2023-11-11 14:37:59 +01:00
Martin
4c385a334c
Add SCRAM PLUS variants. (#163) 2023-11-11 21:08:17 +09:00
Yasuhiro Matsumoto
24e0f536cb
add go.mod 2023-11-11 21:04:41 +09:00
Martin
a6b124c9b2
Fix typo. (#158) 2023-09-24 23:18:19 +09:00
Martin Dosch
6138e9dbe5 Harmonize newlines
Now there should be no more newlines in between any stanza and a
newline after every stanza.
This should not affect functionality but is looking better if
stanzas are printed for debugging.
2023-08-14 10:28:33 +02:00
Martin
98ff0d4df7
Rework printing of sent stanzas when debug is enabled (#148)
* Rework printing of sent stanzas when debug is enabled

This got reworked to also work with multiple connections
as pointed out by @vcabbage in
https://github.com/mattn/go-xmpp/pull/141#issuecomment-1557334066

* Remove StanzaWriter.
2023-07-28 23:42:12 +09:00
Martin
bef3e549f7
add scram auth (#147)
* Fix syntax errors.

* gofmt

* Add SCRAM-SHA-1, SCRAM-SHA-256 and SCRAM-SHA-512 auth
2023-05-21 16:26:59 +09:00
Martin
9129a110df
fix syntax errors (#145)
* Fix syntax errors.

* gofmt
2023-03-03 00:20:58 +09:00
PapaTutuWawa
d72a0f3154
Implement Disco queries against other entities (#124)
* Improve support for XEP-0030

This commit allows the user to query information about the server
or a node belonging to the server as per XEP-0030.

* Fix broken PubSub functionality
2023-03-02 13:23:29 +09:00
Martin
9fc0b1236c
Print sent stanzas in debug mode. (#141)
* Print sent stanzas in debug mode.

* Remove unnecessary newline.
2023-03-02 13:22:44 +09:00
Martin
05cd75074a
success msg (#144)
* Remove unnecessary newline.

* Make success content available.

Closes #142.
2023-03-02 13:20:49 +09:00
vakalmikov
369824c83a
Update xmpp_subscription.go (#131)
https://www.rfc-editor.org/rfc/rfc6121.html#section-3.3.1
2023-03-02 10:32:52 +09:00
Martin
2eb234970c
Remove unnecessary newline. (#140) 2022-07-13 07:17:24 +09:00
Martin
3b26f73300
[codespell] Fix typo. (#139) 2022-07-11 02:58:21 +09:00
milampi
1411b9cc8b
Add xml attribute support for XMLElement (#136)
* Save attributes of the xml element

* Update unittest to check xml attributes
2022-05-13 17:24:06 +09:00
Martin
99ddfc1aa4
Return all pubsub IQs. (#137)
* Return all pubsub IQs.

This makes other pubsub requests accessible via
client.Recv().

* Fix formatting (gofmt).
2022-04-10 14:46:12 +09:00
Martin
e773596ea0
Provide error replies for IQs. (#135)
This should fix #125.
2022-03-19 22:58:56 +09:00
Polynomdivision
912ba61489
Prevent crash in avatar code (#133)
* Prevent crash on empty urn:xmpp:avatar:* nodes

* Fix issue with errors

* Add a test for empty avatar pubsub items
2021-10-30 00:14:15 +09:00
Josh Martin
3871461df9
Update xmpp_information_query.go (#130)
Fix a typo in the code comments.
2021-07-23 11:55:38 +09:00
tytan652
db1339b3a5
Fix host with anonymous connection (#129) 2021-07-22 23:17:14 +09:00
marzzzello
b40e129499
use ServerName to verify tls hostname (#127) 2021-01-21 17:27:23 +09:00
Steven Santos Erenst
42ee290fc5
Add the ability to customize the connection timeout (#122)
Fixes #116
2021-01-21 17:26:29 +09:00
Steven Santos Erenst
da2b7586cd
Avoid creating copies of locks (#121)
tls.Config contains fields of type sync.Once and sync.RWMutex. My understanding
is that if the copy happens to occur while the lock is in a locked state, the
lock will remain locked indefinitely and cause a deadlock. Instead use
tls.Config.Clone() to create a shallow copy.

Also the lock copy made `go vet` upset:

$ go vet ./...
./xmpp.go:242:17: assignment copies lock value to newconfig: crypto/tls.Config contains sync.Once contains sync.Mutex
./xmpp.go:530:9: assignment copies lock value to *tc: crypto/tls.Config contains sync.Once contains sync.Mutex
2021-01-21 17:25:57 +09:00
Alexander
37fa6ef92f
Implement XEP-0084 (User Avatar) (#120)
* Implement XEP-0084 (User Avatar)

* Fix style with gofmt
2021-01-21 17:24:39 +09:00
Alexander
899ef71e80
Implement a bit of XEP-0060 (PubSub) (#119)
This squashed series of commits implements basic
PubSub functionality like requesting data or
subscribing to a PubSub node.
2020-03-09 18:10:41 +09:00
Qais Patankar
3e4868bd3e
Implement OOB in Send() and add SendOOB() function for messages without body (#117)
Co-authored-by: Qais Patankar <qaisjp@gmail.com>

Co-authored-by: ValdikSS <iam@valdikss.org.ru>
2020-03-09 18:10:06 +09:00
harald-mueller
a86b6abcb3 Proxy handling / additional send method (#95)
* respect enviroment var no_proxy

* add method to send IQ messages without <query> element

* check also for uppercase NO_PROXY  env

* Uppercase NO_PROXY takes precedence over no_proxy as in HTTP_PROXY

* add comments

* revert copyright to the original one
2020-01-29 00:58:07 +09:00
mattn
ac4c216a42
Merge pull request #108 from kjx98/master
implement DNS SRV lookup for NewClient method
2020-01-29 00:56:32 +09:00
mattn
6093f50721
Merge pull request #110 from eaglerayp/feature/noTLS
Fix client no StartTLS & server no required
2019-01-24 18:32:44 +09:00
rayshih
1f614e5b8d Fix client no StartTLS & server no required 2019-01-24 15:48:01 +08:00
Jesse Kuang
ef6a1a617c keep IQ struct unchange 2019-01-15 10:53:08 +08:00
Jesse Kuang
65fd08aee2 mv xmpp_get_info to other repo 2019-01-12 14:46:18 +08:00
Jesse Kuang
a79a0e59ef remove GNUmakefile .gitignore
mv new example.go to other repo
2019-01-11 23:22:15 +08:00
Jesse Kuang
5709ddefa8 move IQ stuff to xmpp_get_info and example 2019-01-11 11:20:54 +08:00
Jesse Kuang
51b558cd2c add urn:xmpp:time; now response jabber GetInfo 2019-01-10 23:36:50 +08:00
Jesse Kuang
66c008d798 add iq:version, iq:last 2019-01-10 22:53:01 +08:00
Jesse Kuang
224305b3ef test with local prosody without conferenc 2019-01-10 20:43:44 +08:00
Jesse Kuang
1e7b50b41c add conference support 2019-01-10 14:46:50 +08:00
Jesse Kuang
c18873b880 fix query roster
process subscription="remove" roster
improve roster process
2019-01-10 10:12:39 +08:00
Jesse Kuang
2c5079ea28 fix param of tlsconn.VerifyHostname 2019-01-09 15:35:32 +08:00
Jesse Kuang
113d9c0420 implement DNS SRV lookup 2019-01-09 13:52:43 +08:00
Yasuhiro Matsumoto
e543ad3fcd
go fmt 2018-05-05 20:33:05 +09:00
Richard Phillips
4fdbee9ac5
Add 'id' to outgoing message using cnonce 2018-05-05 20:32:25 +09:00
mattn
8a5843171f
Merge pull request #97 from frankbraun/debug
introduce DebugWriter
2018-04-23 11:14:11 +09:00
Frank Braun
04ea54f191 introduce DebugWriter
This allows to use a different writer than os.Stderr to write debugging
output to.
2018-04-22 21:35:30 +00:00
14 changed files with 1827 additions and 316 deletions

View File

@ -3,4 +3,4 @@ go-xmpp
go xmpp library (original was written by russ cox ) go xmpp library (original was written by russ cox )
[Documentation](https://godoc.org/github.com/mattn/go-xmpp) [Documentation](https://godoc.org/github.com/xmppo/go-xmpp)

View File

@ -2,11 +2,12 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"github.com/mattn/go-gtk/gtk"
"github.com/mattn/go-xmpp"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/matterbridge/go-xmpp"
"github.com/mattn/go-gtk/gtk"
) )
func main() { func main() {

View File

@ -5,20 +5,23 @@ import (
"crypto/tls" "crypto/tls"
"flag" "flag"
"fmt" "fmt"
"github.com/mattn/go-xmpp"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/matterbridge/go-xmpp"
) )
var server = flag.String("server", "talk.google.com:443", "server") var (
var username = flag.String("username", "", "username") server = flag.String("server", "talk.google.com:443", "server")
var password = flag.String("password", "", "password") username = flag.String("username", "", "username")
var status = flag.String("status", "xa", "status") password = flag.String("password", "", "password")
var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message") status = flag.String("status", "xa", "status")
var notls = flag.Bool("notls", false, "No TLS") statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message")
var debug = flag.Bool("debug", false, "debug output") notls = flag.Bool("notls", false, "No TLS")
var session = flag.Bool("session", false, "use server session") debug = flag.Bool("debug", false, "debug output")
session = flag.Bool("session", false, "use server session")
)
func serverName(host string) string { func serverName(host string) string {
return strings.Split(host, ":")[0] return strings.Split(host, ":")[0]
@ -48,7 +51,8 @@ func main() {
var talk *xmpp.Client var talk *xmpp.Client
var err error var err error
options := xmpp.Options{Host: *server, options := xmpp.Options{
Host: *server,
User: *username, User: *username,
Password: *password, Password: *password,
NoTLS: *notls, NoTLS: *notls,
@ -59,7 +63,6 @@ func main() {
} }
talk, err = options.NewClient() talk, err = options.NewClient()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/matterbridge/go-xmpp
go 1.21.5
require (
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
)

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=

1415
xmpp.go

File diff suppressed because it is too large Load Diff

125
xmpp_avatar.go Normal file
View File

@ -0,0 +1,125 @@
package xmpp
import (
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"errors"
"strconv"
)
const (
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data"
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata"
)
type clientAvatarData struct {
XMLName xml.Name `xml:"data"`
Data []byte `xml:",innerxml"`
}
type clientAvatarInfo struct {
XMLName xml.Name `xml:"info"`
Bytes string `xml:"bytes,attr"`
Width string `xml:"width,attr"`
Height string `xml:"height,attr"`
ID string `xml:"id,attr"`
Type string `xml:"type,attr"`
URL string `xml:"url,attr"`
}
type clientAvatarMetadata struct {
XMLName xml.Name `xml:"metadata"`
XMLNS string `xml:"xmlns,attr"`
Info clientAvatarInfo `xml:"info"`
}
type AvatarData struct {
Data []byte
From string
}
type AvatarMetadata struct {
From string
Bytes int
Width int
Height int
ID string
Type string
URL string
}
func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
var data clientAvatarData
err := xml.Unmarshal(itemsBody, &data)
if err != nil {
return AvatarData{}, err
}
// Base64-decode the avatar data to check its SHA1 hash
dataRaw, err := base64.StdEncoding.DecodeString(
string(data.Data))
if err != nil {
return AvatarData{}, err
}
hash := sha1.Sum(dataRaw)
hashStr := hex.EncodeToString(hash[:])
if hashStr != id {
return AvatarData{}, errors.New("SHA1 hashes do not match")
}
return AvatarData{
Data: dataRaw,
From: from,
}, nil
}
func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
var meta clientAvatarMetadata
err := xml.Unmarshal(body, &meta)
if err != nil {
return AvatarMetadata{}, err
}
return AvatarMetadata{
From: from,
Bytes: atoiw(meta.Info.Bytes),
Width: atoiw(meta.Info.Width),
Height: atoiw(meta.Info.Height),
ID: meta.Info.ID,
Type: meta.Info.Type,
URL: meta.Info.URL,
}, nil
}
// A wrapper for atoi which just returns -1 if an error occurs
func atoiw(str string) int {
i, err := strconv.Atoi(str)
if err != nil {
return -1
}
return i
}
func (c *Client) AvatarSubscribeMetadata(jid string) {
c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
}
func (c *Client) AvatarUnsubscribeMetadata(jid string) {
c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
}
func (c *Client) AvatarRequestData(jid string) {
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
}
func (c *Client) AvatarRequestDataByID(jid, id string) {
c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
}
func (c *Client) AvatarRequestMetadata(jid string) {
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
}

99
xmpp_disco.go Normal file
View File

@ -0,0 +1,99 @@
package xmpp
import (
"encoding/xml"
)
const (
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
)
type clientDiscoFeature struct {
XMLName xml.Name `xml:"feature"`
Var string `xml:"var,attr"`
}
type clientDiscoIdentity struct {
XMLName xml.Name `xml:"identity"`
Category string `xml:"category,attr"`
Type string `xml:"type,attr"`
Name string `xml:"name,attr"`
}
type clientDiscoQuery struct {
XMLName xml.Name `xml:"query"`
Features []clientDiscoFeature `xml:"feature"`
Identities []clientDiscoIdentity `xml:"identity"`
}
type clientDiscoItem struct {
XMLName xml.Name `xml:"item"`
Jid string `xml:"jid,attr"`
Node string `xml:"node,attr"`
Name string `xml:"name,attr"`
}
type clientDiscoItemsQuery struct {
XMLName xml.Name `xml:"query"`
Items []clientDiscoItem `xml:"item"`
}
type DiscoIdentity struct {
Category string
Type string
Name string
}
type DiscoItem struct {
Jid string
Name string
Node string
}
type DiscoResult struct {
Features []string
Identities []DiscoIdentity
}
type DiscoItems struct {
Jid string
Items []DiscoItem
}
func clientFeaturesToReturn(features []clientDiscoFeature) []string {
var ret []string
for _, feature := range features {
ret = append(ret, feature.Var)
}
return ret
}
func clientIdentitiesToReturn(identities []clientDiscoIdentity) []DiscoIdentity {
var ret []DiscoIdentity
for _, id := range identities {
ret = append(ret, DiscoIdentity{
Category: id.Category,
Type: id.Type,
Name: id.Name,
})
}
return ret
}
func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
var ret []DiscoItem
for _, item := range items {
ret = append(ret, DiscoItem{
Jid: item.Jid,
Name: item.Name,
Node: item.Node,
})
}
return ret
}

View File

@ -5,27 +5,45 @@ import (
"strconv" "strconv"
) )
const IQTypeGet = "get" const (
const IQTypeSet = "set" IQTypeGet = "get"
const IQTypeResult = "result" IQTypeSet = "set"
IQTypeResult = "result"
)
func (c *Client) Discovery() (string, error) { func (c *Client) Discovery() (string, error) {
const namespace = "http://jabber.org/protocol/disco#items" // use UUIDv4 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, namespace, "") return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
}
// Discover information about a node
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)
}
// 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, "info1", IQTypeGet, query)
} }
// RawInformationQuery sends an information query request to the server. // RawInformationQuery sends an information query request to the server.
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>\n"
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body) _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
return id, err return id, err
} }
// rawInformation send a IQ request with the the payload body to the server // rawInformation send 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>\n"
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body) _, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
return id, err return id, err
} }

View File

@ -8,9 +8,9 @@
package xmpp package xmpp
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
"errors"
) )
const ( const (
@ -25,7 +25,7 @@ const (
// Send sends room topic wrapped inside an XMPP message stanza body. // Send 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) (n int, err error) {
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>", 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)) xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
} }
@ -33,10 +33,10 @@ func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
if nick == "" { if nick == "" {
nick = c.jid nick = c.jid
} }
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+ return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>"+ "<x xmlns='%s'>"+
"<history maxchars='0'/></x>\n"+ "<history maxchars='0'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC) xmlEscape(jid), xmlEscape(nick), nsMUC)
} }
@ -47,34 +47,34 @@ func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_da
} }
switch history_type { switch history_type {
case NoHistory: case NoHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s' />\n" + "<x xmlns='%s' />"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC) xmlEscape(jid), xmlEscape(nick), nsMUC)
case CharHistory: case CharHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<history maxchars='%d'/></x>\n"+ "<history maxchars='%d'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, history) xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case StanzaHistory: case StanzaHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<history maxstanzas='%d'/></x>\n"+ "<history maxstanzas='%d'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, history) xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SecondsHistory: case SecondsHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<history seconds='%d'/></x>\n"+ "<history seconds='%d'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, history) xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SinceHistory: case SinceHistory:
if history_date != nil { if history_date != nil {
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<history since='%s'/></x>\n" + "<history since='%s'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339)) xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
} }
} }
@ -88,40 +88,40 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
} }
switch history_type { switch history_type {
case NoHistory: case NoHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<password>%s</password>" + "<password>%s</password>"+
"</x>\n" + "</x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password)) xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
case CharHistory: case CharHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<password>%s</password>\n"+ "<password>%s</password>"+
"<history maxchars='%d'/></x>\n"+ "<history maxchars='%d'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case StanzaHistory: case StanzaHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<password>%s</password>\n"+ "<password>%s</password>"+
"<history maxstanzas='%d'/></x>\n"+ "<history maxstanzas='%d'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case SecondsHistory: case SecondsHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<password>%s</password>\n"+ "<password>%s</password>"+
"<history seconds='%d'/></x>\n"+ "<history seconds='%d'/></x>"+
"</presence>", "</presence>\n",
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 history_date != nil {
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
"<x xmlns='%s'>\n" + "<x xmlns='%s'>"+
"<password>%s</password>\n"+ "<password>%s</password>"+
"<history since='%s'/></x>\n" + "<history since='%s'/></x>"+
"</presence>", "</presence>\n",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339)) xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
} }
} }
@ -130,6 +130,6 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
// xep-0045 7.14 // xep-0045 7.14
func (c *Client) LeaveMUC(jid string) (n int, err error) { func (c *Client) LeaveMUC(jid string) (n int, err error) {
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />", return fmt.Fprintf(c.stanzaWriter, "<presence from='%s' to='%s' type='unavailable' />\n",
c.jid, xmlEscape(jid)) c.jid, xmlEscape(jid))
} }

View File

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

128
xmpp_pubsub.go Normal file
View File

@ -0,0 +1,128 @@
package xmpp
import (
"encoding/xml"
"fmt"
)
const (
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub"
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event"
)
type clientPubsubItem struct {
XMLName xml.Name `xml:"item"`
ID string `xml:"id,attr"`
Body []byte `xml:",innerxml"`
}
type clientPubsubItems struct {
XMLName xml.Name `xml:"items"`
Node string `xml:"node,attr"`
Items []clientPubsubItem `xml:"item"`
}
type clientPubsubEvent struct {
XMLName xml.Name `xml:"event"`
XMLNS string `xml:"xmlns,attr"`
Items clientPubsubItems `xml:"items"`
}
type clientPubsubError struct {
XMLName xml.Name
}
type clientPubsubSubscription struct {
XMLName xml.Name `xml:"subscription"`
Node string `xml:"node,attr"`
JID string `xml:"jid,attr"`
SubID string `xml:"subid,attr"`
}
type PubsubEvent struct {
Node string
Items []PubsubItem
}
type PubsubSubscription struct {
SubID string
JID string
Node string
Errors []string
}
type PubsubUnsubscription PubsubSubscription
type PubsubItem struct {
ID string
InnerXML []byte
}
type PubsubItems struct {
Node string
Items []PubsubItem
}
// Converts []clientPubsubItem to []PubsubItem
func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem {
var tmp []PubsubItem
for _, i := range items {
tmp = append(tmp, PubsubItem{
ID: i.ID,
InnerXML: i.Body,
})
}
return tmp
}
func pubsubClientToReturn(event clientPubsubEvent) PubsubEvent {
return PubsubEvent{
Node: event.Items.Node,
Items: pubsubItemsToReturn(event.Items.Items),
}
}
func pubsubStanza(body string) string {
return fmt.Sprintf("<pubsub xmlns='%s'>%s</pubsub>",
XMPPNS_PUBSUB, body)
}
func pubsubSubscriptionStanza(node, jid string) string {
body := fmt.Sprintf("<subscribe node='%s' jid='%s'/>",
xmlEscape(node),
xmlEscape(jid))
return pubsubStanza(body)
}
func pubsubUnsubscriptionStanza(node, jid string) string {
body := fmt.Sprintf("<unsubscribe node='%s' jid='%s'/>",
xmlEscape(node),
xmlEscape(jid))
return pubsubStanza(body)
}
func (c *Client) PubsubSubscribeNode(node, jid string) {
c.RawInformation(c.jid,
jid,
"sub1",
"set",
pubsubSubscriptionStanza(node, c.jid))
}
func (c *Client) PubsubUnsubscribeNode(node, jid string) {
c.RawInformation(c.jid,
jid,
"unsub1",
"set",
pubsubUnsubscriptionStanza(node, c.jid))
}
func (c *Client) PubsubRequestLastItems(node, jid string) {
body := fmt.Sprintf("<items node='%s'/>", node)
c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
}
func (c *Client) PubsubRequestItem(node, jid, id string) {
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
}

View File

@ -5,16 +5,21 @@ import (
) )
func (c *Client) ApproveSubscription(jid string) { func (c *Client) ApproveSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>", fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribed'/>\n",
xmlEscape(jid)) xmlEscape(jid))
} }
func (c *Client) RevokeSubscription(jid string) { func (c *Client) RevokeSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>", fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='unsubscribed'/>\n",
xmlEscape(jid))
}
func (c *Client) RetrieveSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>\n",
xmlEscape(jid)) xmlEscape(jid))
} }
func (c *Client) RequestSubscription(jid string) { func (c *Client) RequestSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>", fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribe'/>\n",
xmlEscape(jid)) xmlEscape(jid))
} }

View File

@ -85,12 +85,14 @@ func TestStanzaError(t *testing.T) {
"\n\t\t\n\t\t\n\t", "\n\t\t\n\t\t\n\t",
}, },
OtherElem: []XMLElement{ OtherElem: []XMLElement{
XMLElement{ {
XMLName: xml.Name{Space: "google:mobile:data", Local: "gcm"}, XMLName: xml.Name{Space: "google:mobile:data", Local: "gcm"},
Attr: []xml.Attr{{Name: xml.Name{Space: "", Local: "xmlns"}, Value: "google:mobile:data"}},
InnerXML: "\n\t\t{\"random\": \"&lt;text&gt;\"}\n\t", InnerXML: "\n\t\t{\"random\": \"&lt;text&gt;\"}\n\t",
}, },
XMLElement{ {
XMLName: xml.Name{Space: "jabber:client", Local: "error"}, XMLName: xml.Name{Space: "jabber:client", Local: "error"},
Attr: []xml.Attr{{Name: xml.Name{Space: "", Local: "code"}, Value: "400"}, {Name: xml.Name{Space: "", Local: "type"}, Value: "modify"}},
InnerXML: ` InnerXML: `
<bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/> <bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
@ -114,3 +116,28 @@ func TestEOFError(t *testing.T) {
t.Errorf("Recv() did not return io.EOF on end of input stream") t.Errorf("Recv() did not return io.EOF on end of input stream")
} }
} }
var emptyPubSub = strings.TrimSpace(`
<iq xmlns="jabber:client" type='result' from='juliet@capulet.lit' id='items3'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='urn:xmpp:avatar:data'></items>
</pubsub>
</iq>
`)
func TestEmptyPubsub(t *testing.T) {
var c Client
c.conn = tConnect(emptyPubSub)
c.p = xml.NewDecoder(c.conn)
m, err := c.Recv()
switch m.(type) {
case AvatarData:
if err == nil {
t.Errorf("Expected an error to be returned")
}
default:
t.Errorf("Recv() = %v", m)
t.Errorf("Expected a return value of AvatarData")
}
}