-
-
Notifications
You must be signed in to change notification settings - Fork 21
/
carbons.go
136 lines (121 loc) · 4.09 KB
/
carbons.go
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
// 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.
//go:generate go run ../internal/genfeature -receiver "h Handler"
// Package carbons implements carbon copying messages to all interested clients.
package carbons // import "mellium.im/xmpp/carbons"
import (
"context"
"encoding/xml"
"fmt"
"mellium.im/xmlstream"
"mellium.im/xmpp"
"mellium.im/xmpp/delay"
"mellium.im/xmpp/forward"
"mellium.im/xmpp/stanza"
)
// Namespaces used by this package, provided as a convenience.
const (
NS = `urn:xmpp:carbons:2`
NSRules = `urn:xmpp:carbons:rules:0`
)
// Enable instructs the server to start carbon copying messages on the given
// session.
func Enable(ctx context.Context, s *xmpp.Session) error {
return EnableIQ(ctx, s, stanza.IQ{})
}
// EnableIQ is like Enable but it allows you to customize the IQ stanza being
// sent.
// Changing the type of the IQ has no effect.
func EnableIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ) error {
iq.Type = stanza.SetIQ
v := struct {
XMLName xml.Name
}{}
err := s.UnmarshalIQ(ctx, iq.Wrap(xmlstream.Wrap(
nil,
xml.StartElement{Name: xml.Name{Space: NS, Local: "enable"}},
)), &v)
return err
}
// Disable instructs the server to stop carbon copying messages on the given
// session.
func Disable(ctx context.Context, s *xmpp.Session) error {
return DisableIQ(ctx, s, stanza.IQ{})
}
// DisableIQ is like Disable but it allows you to customize the IQ stanza being
// sent.
// Changing the type of the IQ has no effect.
func DisableIQ(ctx context.Context, s *xmpp.Session, iq stanza.IQ) error {
iq.Type = stanza.SetIQ
v := struct {
XMLName xml.Name
}{}
err := s.UnmarshalIQ(ctx, iq.Wrap(xmlstream.Wrap(
nil,
xml.StartElement{Name: xml.Name{Space: NS, Local: "disable"}},
)), &v)
return err
}
// WrapReceived wraps the provided token reader (which should be a message
// stanza, but this is not enforced) in a received element.
func WrapReceived(delay delay.Delay, r xml.TokenReader) xml.TokenReader {
return xmlstream.Wrap(
forward.Forwarded{
Delay: delay,
}.Wrap(r),
xml.StartElement{Name: xml.Name{Space: NS, Local: "received"}},
)
}
// WrapSent wraps the provided token reader (which should be a message stanza,
// but this is not enforced) in a sent element.
func WrapSent(delay delay.Delay, r xml.TokenReader) xml.TokenReader {
return xmlstream.Wrap(
forward.Forwarded{
Delay: delay,
}.Wrap(r),
xml.StartElement{Name: xml.Name{Space: NS, Local: "sent"}},
)
}
// Unwrap unwraps a carbon copied message, unmarshals the forwarding delay into
// the provided delay, and returns a start element set to either sent or
// received.
// If the provided delay is nil, unmarshaling is skipped.
func Unwrap(del *delay.Delay, r xml.TokenReader) (xml.TokenReader, xml.StartElement, error) {
token, err := r.Token()
if err != nil {
return nil, xml.StartElement{}, err
}
se, ok := token.(xml.StartElement)
if !ok {
return nil, se, fmt.Errorf("expected a startElement, found %T", token)
}
if se.Name.Local != "sent" && se.Name.Local != "received" || se.Name.Space != NS {
return nil, se, fmt.Errorf("unexpected name for the sent/received element: %+v", se.Name)
}
out, err := forward.Unwrap(del, xmlstream.Inner(r))
return out, se, err
}
// Private is an xmlstream.Transformer that excludes all top level <message/> elements from
// being forwarded to other Carbons-enabled resources, by adding a <private/> element
// and a <no-copy/> hint.
func Private(r xml.TokenReader) xml.TokenReader {
return xmlstream.InsertFunc(
func(start xml.StartElement, level uint64, w xmlstream.TokenWriter) error {
if level == 1 &&
start.Name.Local == "message" &&
(start.Name.Space == stanza.NSClient || start.Name.Space == stanza.NSServer) {
_, err := xmlstream.Copy(w, xmlstream.MultiReader(
xmlstream.Wrap(nil, xml.StartElement{
Name: xml.Name{Space: NS, Local: "private"},
}),
xmlstream.Wrap(nil, xml.StartElement{
Name: xml.Name{Space: "urn:xmpp:hints", Local: "no-copy"},
}),
))
return err
}
return nil
},
)(r)
}