~samwhited/xmpp

xmpp/blocklist/blocking.go -rw-r--r-- 4.2 KiB
c9743d9fSam Whited docs: update changelog to include deprecations 8 days 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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2021 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

// Package blocklist implements blocking and unblocking of contacts.
package blocklist // import "mellium.im/xmpp/blocklist"

import (
	"context"
	"encoding/xml"

	"mellium.im/xmlstream"
	"mellium.im/xmpp"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stanza"
)

// NS is the namespace used by this package, provided as a convenience.
const NS = `urn:xmpp:blocklist`

// Match checks j1 aginst a JID in the blocklist (j2) and returns true if they
// are a match.
//
// The JID matches the blocklist JID if any of the following compare to the
// blocklist JID (falling back in this order):
//
//  - Full JID (user@domain/resource)
//  - Bare JID (user@domain)
//  - Full domain (domain/resource)
//  - Bare domain
func Match(j1, j2 jid.JID) bool {
	return j1.Equal(j2) ||
		j1.Bare().Equal(j2) ||
		jid.NewUnsafe("", j1.Domainpart(), j1.Resourcepart()).JID.Equal(j2) ||
		j1.Domain().Equal(j2)
}

// Iter is an iterator over blocklist JIDs.
type Iter struct {
	iter    *xmlstream.Iter
	current jid.JID
	err     error
}

// Next returns true if there are more items to decode.
func (i *Iter) Next() bool {
	if i.err != nil || !i.iter.Next() {
		return false
	}
	start, _ := i.iter.Current()
	// If we encounter a lone token that doesn't begin with a start element (eg.
	// a comment) skip it. This should never happen with XMPP, but we don't want
	// to panic in case this somehow happens so just skip it.
	if start == nil {
		return i.Next()
	}
	for _, attr := range start.Attr {
		if attr.Name.Local == "jid" {
			i.current, i.err = jid.Parse(attr.Value)
			break
		}
	}
	return true
}

// Err returns the last error encountered by the iterator (if any).
func (i *Iter) Err() error {
	if i.err != nil {
		return i.err
	}

	return i.iter.Err()
}

// JID returns the last blocked JID parsed by the iterator.
func (i *Iter) JID() jid.JID {
	return i.current
}

// Close indicates that we are finished with the given iterator and processing
// the stream may continue.
// Calling it multiple times has no effect.
func (i *Iter) Close() error {
	if i.iter == nil {
		return nil
	}
	return i.iter.Close()
}

// Fetch sends a request to the JID asking for the blocklist.
func Fetch(ctx context.Context, s *xmpp.Session) *Iter {
	return FetchIQ(ctx, stanza.IQ{}, s)
}

// FetchIQ is like Fetch except that it lets you customize the IQ.
// Changing the type of the provided IQ has no effect.
func FetchIQ(ctx context.Context, iq stanza.IQ, s *xmpp.Session) *Iter {
	if iq.Type != stanza.GetIQ {
		iq.Type = stanza.GetIQ
	}
	iter, _, err := s.IterIQ(ctx, iq.Wrap(xmlstream.Wrap(nil, xml.StartElement{
		Name: xml.Name{Space: NS, Local: "blocklist"},
	})))
	if err != nil {
		return &Iter{err: err}
	}
	return &Iter{
		iter: iter,
	}
}

// Add adds JIDs to the blocklist.
func Add(ctx context.Context, s *xmpp.Session, j ...jid.JID) error {
	return AddIQ(ctx, stanza.IQ{}, s, j...)
}

// AddIQ is like Add except that it lets you customize the IQ.
// Changing the type of the provided IQ has no effect.
func AddIQ(ctx context.Context, iq stanza.IQ, s *xmpp.Session, j ...jid.JID) error {
	return doIQ(ctx, "block", iq, s, j...)
}

// Remove removes JIDs from the blocklist.
// If no JIDs are provided the entire blocklist is cleared.
func Remove(ctx context.Context, s *xmpp.Session, j ...jid.JID) error {
	return RemoveIQ(ctx, stanza.IQ{}, s, j...)
}

// RemoveIQ is like Remove except that it lets you customize the IQ.
// Changing the type of the provided IQ has no effect.
func RemoveIQ(ctx context.Context, iq stanza.IQ, s *xmpp.Session, j ...jid.JID) error {
	return doIQ(ctx, "unblock", iq, s, j...)
}

func doIQ(ctx context.Context, local string, iq stanza.IQ, s *xmpp.Session, j ...jid.JID) error {
	if iq.Type != stanza.SetIQ {
		iq.Type = stanza.SetIQ
	}
	var jids []xml.TokenReader
	for _, jj := range j {
		jids = append(jids, xmlstream.Wrap(nil, xml.StartElement{
			Name: xml.Name{Local: "item"},
			Attr: []xml.Attr{{Name: xml.Name{Local: "jid"}, Value: jj.String()}},
		}))
	}
	r, err := s.SendIQ(ctx, iq.Wrap(xmlstream.Wrap(
		xmlstream.MultiReader(jids...),
		xml.StartElement{
			Name: xml.Name{Space: NS, Local: local},
		},
	)))
	if err != nil {
		return err
	}
	return r.Close()
}