forked from jshiffer/go-xmpp
		
	Compare commits
	
		
			128 Commits
		
	
	
		
			7ec2b8b7de
			...
			370c500a5e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 370c500a5e | |||
|   | 7154bfeb76 | ||
|   | 243a438354 | ||
|   | e9123cc4b3 | ||
|   | 4be597a84a | ||
|   | 464fbe04ef | ||
|   | e223dcf94b | ||
|   | 321c2b14a5 | ||
|   | 9161feef4d | ||
|   | fc3ed9a0b8 | ||
|   | d9df620fa4 | ||
|   | f067814851 | ||
|   | 961b7e435e | ||
|   | 12a04e0950 | ||
|   | 2f331ed19c | ||
|   | b0f55a8f7f | ||
|   | 7486b7a363 | ||
|   | da2377ecb0 | ||
|   | 44095406a2 | ||
|   | d7aee6b636 | ||
|   | 0324b31f56 | ||
|   | ca4e49201e | ||
|   | 6e5d6e449e | ||
|   | 0ae62a33a2 | ||
|   | ce687243c1 | ||
|   | 78d07e9eee | ||
|   | 416bb6e7b7 | ||
|   | aef1257ed1 | ||
|   | da17a46e6f | ||
|   | eedd7259cb | ||
|   | 07196efcf3 | ||
|   | bbd90cc04b | ||
|   | 0c7ee22452 | ||
|   | 862c21f845 | ||
|   | bc81053dbc | ||
|   | 94ab540b80 | ||
|   | f6a9836fdf | ||
|   | 8ab32d885f | ||
|   | 73f06c9f3d | ||
|   | 9c5e758356 | ||
|   | ea4874e8c9 | ||
|   | dab6865bd2 | ||
|   | c051d69509 | ||
|   | aed021cf3e | ||
|   | 2c4708e724 | ||
|   | 746409f074 | ||
|   | 9684a8ff69 | ||
|   | b7ea9f4be1 | ||
|   | e2bc7bf6d7 | ||
|   | 0bcc057225 | ||
|   | 49054ca9e9 | ||
|   | b369b7df10 | ||
|   | cc481e54e7 | ||
|   | 88855eac82 | ||
|   | 0cc0a72c15 | ||
|   | d6e9a15f29 | ||
|   | 6ffd595a06 | ||
|   | 62928b3483 | ||
|   | d67787ca0f | ||
|   | f8a24505f4 | ||
|   | 685570cbd8 | ||
|   | 7bfa331758 | ||
|   | 3f0cbac307 | ||
|   | 7ccad52e63 | ||
|   | 705f68d1a5 | ||
|   | b49bdce100 | ||
|   | f4c732fdc7 | ||
|   | d3d16d5db9 | ||
|   | 34d683d25a | ||
|   | dffa92c129 | ||
|   | 8531e2e36a | ||
|   | e7d5b17113 | ||
|   | c1b9689e75 | ||
|   | 424970d23c | ||
|   | 5fdcf18a81 | ||
|   | 794ed98f9f | ||
|   | 2f9bd427e8 | ||
|   | 70c2fe6900 | ||
|   | 39f5b80375 | ||
|   | 2449f4192b | ||
|   | 3462085098 | ||
|   | 6c9243326e | ||
|   | 31c7eb6919 | ||
|   | 9dcf67c0ad | ||
|   | 4c385a334c | ||
|   | 24e0f536cb | ||
|   | a6b124c9b2 | ||
|   | 6138e9dbe5 | ||
|   | 98ff0d4df7 | ||
|   | bef3e549f7 | ||
|   | 9129a110df | ||
|   | d72a0f3154 | ||
|   | 9fc0b1236c | ||
|   | 05cd75074a | ||
|   | 369824c83a | ||
|   | 2eb234970c | ||
|   | 3b26f73300 | ||
|   | 1411b9cc8b | ||
|   | 99ddfc1aa4 | ||
|   | e773596ea0 | ||
|   | 912ba61489 | ||
|   | 3871461df9 | ||
|   | db1339b3a5 | ||
|   | b40e129499 | ||
|   | 42ee290fc5 | ||
|   | da2b7586cd | ||
|   | 37fa6ef92f | ||
|   | 899ef71e80 | ||
|   | 3e4868bd3e | ||
|   | a86b6abcb3 | ||
|   | ac4c216a42 | ||
|   | 6093f50721 | ||
|   | 1f614e5b8d | ||
|   | ef6a1a617c | ||
|   | 65fd08aee2 | ||
|   | a79a0e59ef | ||
|   | 5709ddefa8 | ||
|   | 51b558cd2c | ||
|   | 66c008d798 | ||
|   | 224305b3ef | ||
|   | 1e7b50b41c | ||
|   | c18873b880 | ||
|   | 2c5079ea28 | ||
|   | 113d9c0420 | ||
|   | e543ad3fcd | ||
|   | 4fdbee9ac5 | ||
|   | 8a5843171f | ||
|   | 04ea54f191 | 
| @@ -3,4 +3,4 @@ go-xmpp | ||||
|  | ||||
| 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) | ||||
|   | ||||
| @@ -2,11 +2,12 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"github.com/mattn/go-gtk/gtk" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/matterbridge/go-xmpp" | ||||
| 	"github.com/mattn/go-gtk/gtk" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|   | ||||
| @@ -5,20 +5,23 @@ import ( | ||||
| 	"crypto/tls" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/matterbridge/go-xmpp" | ||||
| ) | ||||
|  | ||||
| var server = flag.String("server", "talk.google.com:443", "server") | ||||
| var username = flag.String("username", "", "username") | ||||
| var password = flag.String("password", "", "password") | ||||
| var status = flag.String("status", "xa", "status") | ||||
| var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message") | ||||
| var notls = flag.Bool("notls", false, "No TLS") | ||||
| var debug = flag.Bool("debug", false, "debug output") | ||||
| var session = flag.Bool("session", false, "use server session") | ||||
| var ( | ||||
| 	server        = flag.String("server", "talk.google.com:443", "server") | ||||
| 	username      = flag.String("username", "", "username") | ||||
| 	password      = flag.String("password", "", "password") | ||||
| 	status        = flag.String("status", "xa", "status") | ||||
| 	statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message") | ||||
| 	notls         = flag.Bool("notls", false, "No TLS") | ||||
| 	debug         = flag.Bool("debug", false, "debug output") | ||||
| 	session       = flag.Bool("session", false, "use server session") | ||||
| ) | ||||
|  | ||||
| func serverName(host string) string { | ||||
| 	return strings.Split(host, ":")[0] | ||||
| @@ -48,7 +51,8 @@ func main() { | ||||
|  | ||||
| 	var talk *xmpp.Client | ||||
| 	var err error | ||||
| 	options := xmpp.Options{Host: *server, | ||||
| 	options := xmpp.Options{ | ||||
| 		Host:          *server, | ||||
| 		User:          *username, | ||||
| 		Password:      *password, | ||||
| 		NoTLS:         *notls, | ||||
| @@ -59,7 +63,6 @@ func main() { | ||||
| 	} | ||||
|  | ||||
| 	talk, err = options.NewClient() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
										Normal 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= | ||||
							
								
								
									
										125
									
								
								xmpp_avatar.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								xmpp_avatar.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										99
									
								
								xmpp_disco.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -5,27 +5,45 @@ import ( | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| const IQTypeGet = "get" | ||||
| const IQTypeSet = "set" | ||||
| const IQTypeResult = "result" | ||||
| const ( | ||||
| 	IQTypeGet    = "get" | ||||
| 	IQTypeSet    = "set" | ||||
| 	IQTypeResult = "result" | ||||
| ) | ||||
|  | ||||
| func (c *Client) Discovery() (string, error) { | ||||
| 	const namespace = "http://jabber.org/protocol/disco#items" | ||||
| 	// use getCookie for a pseudo random id. | ||||
| 	// use UUIDv4 for a pseudo random id. | ||||
| 	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. | ||||
| 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(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body) | ||||
| 	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 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) { | ||||
| 	const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>" | ||||
| 	_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body) | ||||
| 	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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										100
									
								
								xmpp_muc.go
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								xmpp_muc.go
									
									
									
									
									
								
							| @@ -8,9 +8,9 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -25,7 +25,7 @@ const ( | ||||
|  | ||||
| // 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.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)) | ||||
| } | ||||
|  | ||||
| @@ -33,10 +33,10 @@ func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) { | ||||
| 	if nick == "" { | ||||
| 		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'>"+ | ||||
| 		"<history maxchars='0'/></x>\n"+ | ||||
| 		"</presence>", | ||||
| 		"<history maxchars='0'/></x>"+ | ||||
| 		"</presence>\n", | ||||
| 		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 { | ||||
| 	case NoHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s' />\n" + | ||||
| 			"</presence>", | ||||
| 		return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+ | ||||
| 			"<x xmlns='%s' />"+ | ||||
| 			"</presence>\n", | ||||
| 			xmlEscape(jid), xmlEscape(nick), nsMUC) | ||||
| 	case CharHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<history maxchars='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 		return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+ | ||||
| 			"<x xmlns='%s'>"+ | ||||
| 			"<history maxchars='%d'/></x>"+ | ||||
| 			"</presence>\n", | ||||
| 			xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||
| 	case StanzaHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<history maxstanzas='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 		return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+ | ||||
| 			"<x xmlns='%s'>"+ | ||||
| 			"<history maxstanzas='%d'/></x>"+ | ||||
| 			"</presence>\n", | ||||
| 			xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||
| 	case SecondsHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<history seconds='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 		return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+ | ||||
| 			"<x xmlns='%s'>"+ | ||||
| 			"<history seconds='%d'/></x>"+ | ||||
| 			"</presence>\n", | ||||
| 			xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||
| 	case SinceHistory: | ||||
| 		if history_date != nil { | ||||
| 			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 				"<x xmlns='%s'>\n" + | ||||
| 				"<history since='%s'/></x>\n" + | ||||
| 				"</presence>", | ||||
| 			return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+ | ||||
| 				"<x xmlns='%s'>"+ | ||||
| 				"<history since='%s'/></x>"+ | ||||
| 				"</presence>\n", | ||||
| 				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 { | ||||
| 	case NoHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>" + | ||||
| 			"</x>\n" + | ||||
| 			"</presence>", | ||||
| 		return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+ | ||||
| 			"<x xmlns='%s'>"+ | ||||
| 			"<password>%s</password>"+ | ||||
| 			"</x>"+ | ||||
| 			"</presence>\n", | ||||
| 			xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password)) | ||||
| 	case CharHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"<history maxchars='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 		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), nsMUC, xmlEscape(password), history) | ||||
| 	case StanzaHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"<history maxstanzas='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 		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), nsMUC, xmlEscape(password), history) | ||||
| 	case SecondsHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"<history seconds='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 		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), nsMUC, xmlEscape(password), history) | ||||
| 	case SinceHistory: | ||||
| 		if history_date != nil { | ||||
| 			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 				"<x xmlns='%s'>\n" + | ||||
| 				"<password>%s</password>\n"+ | ||||
| 				"<history since='%s'/></x>\n" + | ||||
| 				"</presence>", | ||||
| 			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), 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 | ||||
| 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)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										14
									
								
								xmpp_ping.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								xmpp_ping.go
									
									
									
									
									
								
							| @@ -11,23 +11,23 @@ func (c *Client) PingC2S(jid, server string) error { | ||||
| 	if server == "" { | ||||
| 		server = c.domain | ||||
| 	} | ||||
| 	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+ | ||||
| 		"<ping xmlns='urn:xmpp:ping'/>\n"+ | ||||
| 		"</iq>", | ||||
| 	_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>"+ | ||||
| 		"<ping xmlns='urn:xmpp:ping'/>"+ | ||||
| 		"</iq>\n", | ||||
| 		xmlEscape(jid), xmlEscape(server)) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *Client) PingS2S(fromServer, toServer string) error { | ||||
| 	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+ | ||||
| 		"<ping xmlns='urn:xmpp:ping'/>\n"+ | ||||
| 		"</iq>", | ||||
| 	_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='s2s1' type='get'>"+ | ||||
| 		"<ping xmlns='urn:xmpp:ping'/>"+ | ||||
| 		"</iq>\n", | ||||
| 		xmlEscape(fromServer), xmlEscape(toServer)) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| 	return err | ||||
| } | ||||
|   | ||||
							
								
								
									
										128
									
								
								xmpp_pubsub.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								xmpp_pubsub.go
									
									
									
									
									
										Normal 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)) | ||||
| } | ||||
| @@ -5,16 +5,21 @@ import ( | ||||
| ) | ||||
|  | ||||
| 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)) | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										31
									
								
								xmpp_test.go
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								xmpp_test.go
									
									
									
									
									
								
							| @@ -85,12 +85,14 @@ func TestStanzaError(t *testing.T) { | ||||
| 			"\n\t\t\n\t\t\n\t", | ||||
| 		}, | ||||
| 		OtherElem: []XMLElement{ | ||||
| 			XMLElement{ | ||||
| 			{ | ||||
| 				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\": \"<text>\"}\n\t", | ||||
| 			}, | ||||
| 			XMLElement{ | ||||
| 			{ | ||||
| 				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: ` | ||||
| 		<bad-request 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") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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") | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user