~samwhited/xmpp

ref: a8e3ffb3b023272e5b3010a96da9db9e0103a2b3 xmpp/bind.go -rw-r--r-- 2.9 KiB
a8e3ffb3Sam Whited stream: rename streamerror package to stream 3 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Copyright 2016 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package xmpp

import (
	"context"
	"encoding/xml"
	"fmt"
	"io"

	"mellium.im/xmpp/internal"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stream"
)

const (
	bindIQServerGeneratedRP = `<iq id='%s' type='set'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>`
	bindIQClientRequestedRP = `<iq id='%s' type='set'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>%s</resource></bind></iq>`
)

// BindResource is a stream feature that can be used for binding a resource.
func BindResource() StreamFeature {
	return StreamFeature{
		Name:       xml.Name{Space: ns.Bind, Local: "bind"},
		Necessary:  Authn,
		Prohibited: Ready,
		List: func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
			req = true
			if err = e.EncodeToken(start); err != nil {
				return req, err
			}
			if err = e.EncodeToken(start.End()); err != nil {
				return req, err
			}

			err = e.Flush()
			return req, err
		},
		Parse: func(ctx context.Context, d *xml.Decoder, start *xml.StartElement) (bool, interface{}, error) {
			parsed := struct {
				XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
			}{}
			return true, nil, d.DecodeElement(&parsed, start)
		},
		Negotiate: func(ctx context.Context, session *Session, data interface{}) (mask SessionState, rw io.ReadWriter, err error) {
			if (session.State() & Received) == Received {
				panic("xmpp: bind not yet implemented")
			}

			conn := session.Conn()
			d := session.Decoder()

			reqID := internal.RandomID()
			if resource := session.Config().Origin.Resourcepart(); resource == "" {
				// Send a request for the server to set a resource part.
				_, err = fmt.Fprintf(conn, bindIQServerGeneratedRP, reqID)
			} else {
				// Request the provided resource part.
				_, err = fmt.Fprintf(conn, bindIQClientRequestedRP, reqID, resource)
			}
			if err != nil {
				return mask, nil, err
			}
			tok, err := d.Token()
			if err != nil {
				return mask, nil, err
			}
			start, ok := tok.(xml.StartElement)
			if !ok {
				return mask, nil, stream.BadFormat
			}
			resp := struct {
				IQ
				Bind struct {
					JID *jid.JID `xml:"jid"`
				} `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
				Err StanzaError `xml:"error"`
			}{}
			switch start.Name {
			case xml.Name{Space: ns.Client, Local: "iq"}:
				if err = d.DecodeElement(&resp, &start); err != nil {
					return mask, nil, err
				}
			default:
				return mask, nil, stream.BadFormat
			}

			switch {
			case resp.ID != reqID:
				return mask, nil, stream.UndefinedCondition
			case resp.Type == ResultIQ:
				session.origin = resp.Bind.JID
			case resp.Type == ErrorIQ:
				return mask, nil, resp.Err
			default:
				return mask, nil, StanzaError{Condition: BadRequest}
			}
			return Ready, nil, nil
		},
	}
}