Vendor go-xmpp

This commit is contained in:
Wim 2016-07-13 21:04:28 +02:00
parent 30f30364d5
commit 46faad8b57
7 changed files with 1121 additions and 0 deletions

27
vendor/github.com/mattn/go-xmpp/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

881
vendor/github.com/mattn/go-xmpp/xmpp.go generated vendored Normal file
View File

@ -0,0 +1,881 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO(rsc):
// More precise error handling.
// Presence functionality.
// TODO(mattn):
// Add proxy authentication.
// Package xmpp implements a simple Google Talk client
// using the XMPP protocol described in RFC 3920 and RFC 3921.
package xmpp
import (
"bufio"
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"encoding/binary"
"encoding/xml"
"errors"
"fmt"
"io"
"math/big"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
)
const (
nsStream = "http://etherx.jabber.org/streams"
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
nsClient = "jabber:client"
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
)
// Default TLS configuration options
var DefaultConfig tls.Config
// Cookie is a unique XMPP session identifier
type Cookie uint64
func getCookie() Cookie {
var buf [8]byte
if _, err := rand.Reader.Read(buf[:]); err != nil {
panic("Failed to read random bytes: " + err.Error())
}
return Cookie(binary.LittleEndian.Uint64(buf[:]))
}
// Client holds XMPP connection opitons
type Client struct {
conn net.Conn // connection to server
jid string // Jabber ID for our connection
domain string
p *xml.Decoder
}
func (c *Client) JID() string {
return c.jid
}
func connect(host, user, passwd string) (net.Conn, error) {
addr := host
if strings.TrimSpace(host) == "" {
a := strings.SplitN(user, "@", 2)
if len(a) == 2 {
addr = a[1]
}
}
a := strings.SplitN(host, ":", 2)
if len(a) == 1 {
addr += ":5222"
}
proxy := os.Getenv("HTTP_PROXY")
if proxy == "" {
proxy = os.Getenv("http_proxy")
}
if proxy != "" {
url, err := url.Parse(proxy)
if err == nil {
addr = url.Host
}
}
c, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
if proxy != "" {
fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host)
fmt.Fprintf(c, "Host: %s\r\n", host)
fmt.Fprintf(c, "\r\n")
br := bufio.NewReader(c)
req, _ := http.NewRequest("CONNECT", host, nil)
resp, err := http.ReadResponse(br, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
f := strings.SplitN(resp.Status, " ", 2)
return nil, errors.New(f[1])
}
}
return c, nil
}
// Options are used to specify additional options for new clients, such as a Resource.
type Options struct {
// Host specifies what host to connect to, as either "hostname" or "hostname:port"
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
// Default the port to 5222.
Host string
// User specifies what user to authenticate to the remote server.
User string
// Password supplies the password to use for authentication with the remote server.
Password string
// Resource specifies an XMPP client resource, like "bot", instead of accepting one
// from the server. Use "" to let the server generate one for your client.
Resource string
// OAuthScope provides go-xmpp the required scope for OAuth2 authentication.
OAuthScope string
// OAuthToken provides go-xmpp with the required OAuth2 token used to authenticate
OAuthToken string
// OAuthXmlNs provides go-xmpp with the required namespaced used for OAuth2 authentication. This is
// provided to the server as the xmlns:auth attribute of the OAuth2 authentication request.
OAuthXmlNs string
// TLS Config
TLSConfig *tls.Config
// InsecureAllowUnencryptedAuth permits authentication over a TCP connection that has not been promoted to
// TLS by STARTTLS; this could leak authentication information over the network, or permit man in the middle
// attacks.
InsecureAllowUnencryptedAuth bool
// NoTLS directs go-xmpp to not use TLS initially to contact the server; instead, a plain old unencrypted
// TCP connection should be used. (Can be combined with StartTLS to support STARTTLS-based servers.)
NoTLS bool
// StartTLS directs go-xmpp to STARTTLS if the server supports it; go-xmpp will automatically STARTTLS
// if the server requires it regardless of this option.
StartTLS bool
// Debug output
Debug bool
// Use server sessions
Session bool
// Presence Status
Status string
// Status message
StatusMessage string
}
// NewClient establishes a new Client connection based on a set of Options.
func (o Options) NewClient() (*Client, error) {
host := o.Host
c, err := connect(host, o.User, o.Password)
if err != nil {
return nil, err
}
if strings.LastIndex(o.Host, ":") > 0 {
host = host[:strings.LastIndex(o.Host, ":")]
}
client := new(Client)
if o.NoTLS {
client.conn = c
} else {
var tlsconn *tls.Conn
if o.TLSConfig != nil {
tlsconn = tls.Client(c, o.TLSConfig)
} else {
DefaultConfig.ServerName = host
tlsconn = tls.Client(c, &DefaultConfig)
}
if err = tlsconn.Handshake(); err != nil {
return nil, err
}
insecureSkipVerify := DefaultConfig.InsecureSkipVerify
if o.TLSConfig != nil {
insecureSkipVerify = o.TLSConfig.InsecureSkipVerify
}
if !insecureSkipVerify {
if err = tlsconn.VerifyHostname(host); err != nil {
return nil, err
}
}
client.conn = tlsconn
}
if err := client.init(&o); err != nil {
client.Close()
return nil, err
}
return client, nil
}
// NewClient creates a new connection to a host given as "hostname" or "hostname:port".
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
// Default the port to 5222.
func NewClient(host, user, passwd string, debug bool) (*Client, error) {
opts := Options{
Host: host,
User: user,
Password: passwd,
Debug: debug,
Session: false,
}
return opts.NewClient()
}
// NewClientNoTLS creates a new client without TLS
func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
opts := Options{
Host: host,
User: user,
Password: passwd,
NoTLS: true,
Debug: debug,
Session: false,
}
return opts.NewClient()
}
// Close closes the XMPP connection
func (c *Client) Close() error {
if c.conn != (*tls.Conn)(nil) {
return c.conn.Close()
}
return nil
}
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string {
h := func(text string) []byte {
h := md5.New()
h.Write([]byte(text))
return h.Sum(nil)
}
hex := func(bytes []byte) string {
return fmt.Sprintf("%x", bytes)
}
kd := func(secret, data string) []byte {
return h(secret + ":" + data)
}
a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr
a2 := authenticate + ":" + digestURI
response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))
return response
}
func cnonce() string {
randSize := big.NewInt(0)
randSize.Lsh(big.NewInt(1), 64)
cn, err := rand.Int(rand.Reader, randSize)
if err != nil {
return ""
}
return fmt.Sprintf("%016x", cn)
}
func (c *Client) init(o *Options) error {
var domain string
var user string
a := strings.SplitN(o.User, "@", 2)
if len(o.User) > 0 {
if len(a) != 2 {
return errors.New("xmpp: invalid username (want user@domain): " + o.User)
}
user = a[0]
domain = a[1]
} // Otherwise, we'll be attempting ANONYMOUS
// Declare intent to be a jabber client and gather stream features.
f, err := c.startStream(o, domain)
if err != nil {
return err
}
// If the server requires we STARTTLS, attempt to do so.
if f, err = c.startTLSIfRequired(f, o, domain); err != nil {
return err
}
if o.User == "" && o.Password == "" {
foundAnonymous := false
for _, m := range f.Mechanisms.Mechanism {
if m == "ANONYMOUS" {
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
foundAnonymous = true
break
}
}
if !foundAnonymous {
return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified")
}
} else {
// Even digest forms of authentication are unsafe if we do not know that the host
// we are talking to is the actual server, and not a man in the middle playing
// proxy.
if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth {
return errors.New("refusing to authenticate over unencrypted TCP connection")
}
mechanism := ""
for _, m := range f.Mechanisms.Mechanism {
if m == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" {
mechanism = m
// Oauth authentication: send base64-encoded \x00 user \x00 token.
raw := "\x00" + user + "\x00" + o.OAuthToken
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+
"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc)
break
}
if m == "PLAIN" {
mechanism = m
// Plain authentication: send base64-encoded \x00 user \x00 password.
raw := "\x00" + user + "\x00" + o.Password
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc)
break
}
if m == "DIGEST-MD5" {
mechanism = m
// Digest-MD5 authentication
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
var ch saslChallenge
if err = c.p.DecodeElement(&ch, nil); err != nil {
return errors.New("unmarshal <challenge>: " + err.Error())
}
b, err := base64.StdEncoding.DecodeString(string(ch))
if err != nil {
return err
}
tokens := map[string]string{}
for _, token := range strings.Split(string(b), ",") {
kv := strings.SplitN(strings.TrimSpace(token), "=", 2)
if len(kv) == 2 {
if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' {
kv[1] = kv[1][1 : len(kv[1])-1]
}
tokens[kv[0]] = kv[1]
}
}
realm, _ := tokens["realm"]
nonce, _ := tokens["nonce"]
qop, _ := tokens["qop"]
charset, _ := tokens["charset"]
cnonceStr := cnonce()
digestURI := "xmpp/" + domain
nonceCount := fmt.Sprintf("%08x", 1)
digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount)
message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
var rspauth saslRspAuth
if err = c.p.DecodeElement(&rspauth, nil); err != nil {
return errors.New("unmarshal <challenge>: " + err.Error())
}
b, err = base64.StdEncoding.DecodeString(string(rspauth))
if err != nil {
return err
}
fmt.Fprintf(c.conn, "<response xmlns='%s'/>\n", nsSASL)
break
}
}
if mechanism == "" {
return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)
}
}
// Next message should be either success or failure.
name, val, err := next(c.p)
if err != nil {
return err
}
switch v := val.(type) {
case *saslSuccess:
case *saslFailure:
// v.Any is type of sub-element in failure,
// which gives a description of what failed.
return errors.New("auth failure: " + v.Any.Local)
default:
return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space)
}
// Now that we're authenticated, we're supposed to start the stream over again.
// Declare intent to be a jabber client.
if f, err = c.startStream(o, domain); err != nil {
return err
}
// Generate a unique cookie
cookie := getCookie()
// Send IQ message asking to bind to the local user name.
if o.Resource == "" {
fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
} else {
fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
}
var iq clientIQ
if err = c.p.DecodeElement(&iq, nil); err != nil {
return errors.New("unmarshal <iq>: " + err.Error())
}
if &iq.Bind == nil {
return errors.New("<iq> result missing <bind>")
}
c.jid = iq.Bind.Jid // our local id
c.domain = domain
if o.Session {
//if server support session, open it
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession)
}
// We're connected and can now receive and send messages.
fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)
return nil
}
// startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake.
// f will be updated if the handshake completes, as the new stream's features are typically different from the original.
func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) {
// whether we start tls is a matter of opinion: the server's and the user's.
switch {
case f.StartTLS == nil:
// the server does not support STARTTLS
return f, nil
case f.StartTLS.Required != nil:
// the server requires STARTTLS.
case !o.StartTLS:
// the user wants STARTTLS and the server supports it.
}
var err error
fmt.Fprintf(c.conn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n")
var k tlsProceed
if err = c.p.DecodeElement(&k, nil); err != nil {
return f, errors.New("unmarshal <proceed>: " + err.Error())
}
tc := o.TLSConfig
if tc == nil {
tc = new(tls.Config)
*tc = DefaultConfig
//TODO(scott): we should consider using the server's address or reverse lookup
tc.ServerName = domain
}
t := tls.Client(c.conn, tc)
if err = t.Handshake(); err != nil {
return f, errors.New("starttls handshake: " + err.Error())
}
c.conn = t
// restart our declaration of XMPP stream intentions.
tf, err := c.startStream(o, domain)
if err != nil {
return f, err
}
return tf, nil
}
// startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has
// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr. The features advertised by the server
// will be returned.
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
if o.Debug {
c.p = xml.NewDecoder(tee{c.conn, os.Stderr})
} else {
c.p = xml.NewDecoder(c.conn)
}
_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+
"<stream:stream to='%s' xmlns='%s'\n"+
" xmlns:stream='%s' version='1.0'>\n",
xmlEscape(domain), nsClient, nsStream)
if err != nil {
return nil, err
}
// We expect the server to start a <stream>.
se, err := nextStart(c.p)
if err != nil {
return nil, err
}
if se.Name.Space != nsStream || se.Name.Local != "stream" {
return nil, fmt.Errorf("expected <stream> but got <%v> in %v", se.Name.Local, se.Name.Space)
}
// Now we're in the stream and can use Unmarshal.
// Next message should be <features> to tell us authentication options.
// See section 4.6 in RFC 3920.
f := new(streamFeatures)
if err = c.p.DecodeElement(f, nil); err != nil {
return f, errors.New("unmarshal <features>: " + err.Error())
}
return f, nil
}
// IsEncrypted will return true if the client is connected using a TLS transport, either because it used.
// TLS to connect from the outset, or because it successfully used STARTTLS to promote a TCP connection to TLS.
func (c *Client) IsEncrypted() bool {
_, ok := c.conn.(*tls.Conn)
return ok
}
// Chat is an incoming or outgoing XMPP chat message.
type Chat struct {
Remote string
Type string
Text string
Roster Roster
Other []string
Stamp time.Time
}
type Roster []Contact
type Contact struct {
Remote string
Name string
Group []string
}
// Presence is an XMPP presence notification.
type Presence struct {
From string
To string
Type string
Show string
Status string
}
type IQ struct {
ID string
From string
To string
Type string
Query []byte
}
// Recv waits to receive the next XMPP stanza.
// Return type is either a presence notification or a chat message.
func (c *Client) Recv() (stanza interface{}, err error) {
for {
_, val, err := next(c.p)
if err != nil {
return Chat{}, err
}
switch v := val.(type) {
case *clientMessage:
stamp, _ := time.Parse(
"2006-01-02T15:04:05Z",
v.Delay.Stamp,
)
chat := Chat{
Remote: v.From,
Type: v.Type,
Text: v.Body,
Other: v.Other,
Stamp: stamp,
}
return chat, nil
case *clientQuery:
var r Roster
for _, item := range v.Item {
r = append(r, Contact{item.Jid, item.Name, item.Group})
}
return Chat{Type: "roster", Roster: r}, nil
case *clientPresence:
return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil
case *clientIQ:
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil
}
}
}
// Send sends the message wrapped inside an XMPP message stanza body.
func (c *Client) Send(chat Chat) (n int, err error) {
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<body>%s</body></message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}
// SendOrg sends the original text without being wrapped in an XMPP message stanza.
func (c *Client) SendOrg(org string) (n int, err error) {
return fmt.Fprint(c.conn, org)
}
func (c *Client) SendPresence(presence Presence) (n int, err error) {
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To))
}
// SendHtml sends the message as HTML as defined by XEP-0071
func (c *Client) SendHtml(chat Chat) (n int, err error) {
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+
"<body>%s</body>"+
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text)
}
// Roster asks for the chat roster.
func (c *Client) Roster() error {
fmt.Fprintf(c.conn, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
return nil
}
// RFC 3920 C.1 Streams name space
type streamFeatures struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
StartTLS *tlsStartTLS
Mechanisms saslMechanisms
Bind bindBind
Session bool
}
type streamError struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
Any xml.Name
Text string
}
// RFC 3920 C.3 TLS name space
type tlsStartTLS struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
Required *string `xml:"required"`
}
type tlsProceed struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
}
type tlsFailure struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"`
}
// RFC 3920 C.4 SASL name space
type saslMechanisms struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
Mechanism []string `xml:"mechanism"`
}
type saslAuth struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
Mechanism string `xml:",attr"`
}
type saslChallenge string
type saslRspAuth string
type saslResponse string
type saslAbort struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"`
}
type saslSuccess struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
}
type saslFailure struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
Any xml.Name `xml:",any"`
}
// RFC 3920 C.5 Resource binding name space
type bindBind struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
Resource string
Jid string `xml:"jid"`
}
// RFC 3921 B.1 jabber:client
type clientMessage struct {
XMLName xml.Name `xml:"jabber:client message"`
From string `xml:"from,attr"`
ID string `xml:"id,attr"`
To string `xml:"to,attr"`
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal
// These should technically be []clientText, but string is much more convenient.
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
// Any hasn't matched element
Other []string `xml:",any"`
Delay Delay `xml:"delay"`
}
type Delay struct {
Stamp string `xml:"stamp,attr"`
}
type clientText struct {
Lang string `xml:",attr"`
Body string `xml:"chardata"`
}
type clientPresence struct {
XMLName xml.Name `xml:"jabber:client presence"`
From string `xml:"from,attr"`
ID string `xml:"id,attr"`
To string `xml:"to,attr"`
Type string `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed
Lang string `xml:"lang,attr"`
Show string `xml:"show"` // away, chat, dnd, xa
Status string `xml:"status"` // sb []clientText
Priority string `xml:"priority,attr"`
Error *clientError
}
type clientIQ struct { // info/query
XMLName xml.Name `xml:"jabber:client iq"`
From string `xml:"from,attr"`
ID string `xml:"id,attr"`
To string `xml:"to,attr"`
Type string `xml:"type,attr"` // error, get, result, set
Query []byte `xml:",innerxml"`
Error clientError
Bind bindBind
}
type clientError struct {
XMLName xml.Name `xml:"jabber:client error"`
Code string `xml:",attr"`
Type string `xml:",attr"`
Any xml.Name
Text string
}
type clientQuery struct {
Item []rosterItem
}
type rosterItem struct {
XMLName xml.Name `xml:"jabber:iq:roster item"`
Jid string `xml:",attr"`
Name string `xml:",attr"`
Subscription string `xml:",attr"`
Group []string
}
// Scan XML token stream to find next StartElement.
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
for {
t, err := p.Token()
if err != nil && err != io.EOF || t == nil {
return xml.StartElement{}, err
}
switch t := t.(type) {
case xml.StartElement:
return t, nil
}
}
}
// Scan XML token stream for next element and save into val.
// If val == nil, allocate new element based on proto map.
// Either way, return val.
func next(p *xml.Decoder) (xml.Name, interface{}, error) {
// Read start element to find out what type we want.
se, err := nextStart(p)
if err != nil {
return xml.Name{}, nil, err
}
// Put it in an interface and allocate one.
var nv interface{}
switch se.Name.Space + " " + se.Name.Local {
case nsStream + " features":
nv = &streamFeatures{}
case nsStream + " error":
nv = &streamError{}
case nsTLS + " starttls":
nv = &tlsStartTLS{}
case nsTLS + " proceed":
nv = &tlsProceed{}
case nsTLS + " failure":
nv = &tlsFailure{}
case nsSASL + " mechanisms":
nv = &saslMechanisms{}
case nsSASL + " challenge":
nv = ""
case nsSASL + " response":
nv = ""
case nsSASL + " abort":
nv = &saslAbort{}
case nsSASL + " success":
nv = &saslSuccess{}
case nsSASL + " failure":
nv = &saslFailure{}
case nsBind + " bind":
nv = &bindBind{}
case nsClient + " message":
nv = &clientMessage{}
case nsClient + " presence":
nv = &clientPresence{}
case nsClient + " iq":
nv = &clientIQ{}
case nsClient + " error":
nv = &clientError{}
default:
return xml.Name{}, nil, errors.New("unexpected XMPP message " +
se.Name.Space + " <" + se.Name.Local + "/>")
}
// Unmarshal into that storage.
if err = p.DecodeElement(nv, &se); err != nil {
return xml.Name{}, nil, err
}
return se.Name, nv, err
}
var xmlSpecial = map[byte]string{
'<': "&lt;",
'>': "&gt;",
'"': "&quot;",
'\'': "&apos;",
'&': "&amp;",
}
func xmlEscape(s string) string {
var b bytes.Buffer
for i := 0; i < len(s); i++ {
c := s[i]
if s, ok := xmlSpecial[c]; ok {
b.WriteString(s)
} else {
b.WriteByte(c)
}
}
return b.String()
}
type tee struct {
r io.Reader
w io.Writer
}
func (t tee) Read(p []byte) (n int, err error) {
n, err = t.r.Read(p)
if n > 0 {
t.w.Write(p[0:n])
t.w.Write([]byte("\n"))
}
return
}

View File

@ -0,0 +1,24 @@
package xmpp
import (
"fmt"
"strconv"
)
const IQTypeGet = "get"
const IQTypeSet = "set"
const IQTypeResult = "result"
func (c *Client) Discovery() (string, error) {
const namespace = "http://jabber.org/protocol/disco#items"
// use getCookie for a pseudo random id.
reqID := strconv.FormatUint(uint64(getCookie()), 10)
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "")
}
// 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)
return id, err
}

134
vendor/github.com/mattn/go-xmpp/xmpp_muc.go generated vendored Normal file
View File

@ -0,0 +1,134 @@
// Copyright 2013 Flo Lauber <dev@qatfy.at>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO(flo):
// - support password protected MUC rooms
// - cleanup signatures of join/leave functions
package xmpp
import (
"fmt"
"time"
"errors"
)
const (
nsMUC = "http://jabber.org/protocol/muc"
nsMUCUser = "http://jabber.org/protocol/muc#user"
NoHistory = 0
CharHistory = 1
StanzaHistory = 2
SecondsHistory = 3
SinceHistory = 4
)
// Send sends room topic wrapped inside an XMPP message stanza body.
func (c *Client) SendTopic(chat Chat) (n int, err error) {
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
if nick == "" {
nick = c.jid
}
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>"+
"<history maxchars='0'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC)
}
// xep-0045 7.2
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) {
if nick == "" {
nick = c.jid
}
switch history_type {
case NoHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s' />\n" +
"</presence>",
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>",
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>",
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>",
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>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
}
}
return 0, errors.New("Unknown history option")
}
// xep-0045 7.2.6
func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) {
if nick == "" {
nick = c.jid
}
switch history_type {
case NoHistory:
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
"<x xmlns='%s'>\n" +
"<password>%s</password>\n"+
"</presence>",
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>",
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>",
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>",
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>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
}
}
return 0, errors.New("Unknown history option")
}
// xep-0045 7.14
func (c *Client) LeaveMUC(jid string) (n int, err error) {
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />",
c.jid, xmlEscape(jid))
}

27
vendor/github.com/mattn/go-xmpp/xmpp_ping.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package xmpp
import (
"fmt"
)
func (c *Client) PingC2S(jid, server string) error {
if jid == "" {
jid = c.jid
}
if server == "" {
server = c.domain
}
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
"<ping xmlns='urn:xmpp:ping'/>\n"+
"</iq>",
xmlEscape(jid), xmlEscape(server))
return err
}
func (c *Client) PingS2S(fromServer, toServer string) error {
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
"<ping xmlns='urn:xmpp:ping'/>\n"+
"</iq>",
xmlEscape(fromServer), xmlEscape(toServer))
return err
}

20
vendor/github.com/mattn/go-xmpp/xmpp_subscription.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
package xmpp
import (
"fmt"
)
func (c *Client) ApproveSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>",
xmlEscape(jid))
}
func (c *Client) RevokeSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>",
xmlEscape(jid))
}
func (c *Client) RequestSubscription(jid string) {
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>",
xmlEscape(jid))
}

8
vendor/manifest vendored
View File

@ -77,6 +77,14 @@
"path": "/model", "path": "/model",
"notests": true "notests": true
}, },
{
"importpath": "github.com/mattn/go-xmpp",
"repository": "https://github.com/mattn/go-xmpp",
"vcs": "git",
"revision": "e44d1877bb457f5c3991903e9934a31e55c3a2ad",
"branch": "master",
"notests": true
},
{ {
"importpath": "github.com/nicksnyder/go-i18n/i18n", "importpath": "github.com/nicksnyder/go-i18n/i18n",
"repository": "https://github.com/nicksnyder/go-i18n", "repository": "https://github.com/nicksnyder/go-i18n",