|
1 // Copyright 2011 The Go Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style |
|
3 // license that can be found in the LICENSE file. |
|
4 |
|
5 package xmpp |
|
6 |
|
7 // This file contains data structures. |
|
8 |
|
9 import ( |
|
10 "bytes" |
|
11 "encoding/xml" |
|
12 "flag" |
|
13 "fmt" |
|
14 // BUG(cjyar): Doesn't use stringprep. Could try the implementation at |
|
15 // "code.google.com/p/go-idn/src/stringprep" |
|
16 "regexp" |
|
17 "strings" |
|
18 ) |
|
19 |
|
20 // JID represents an entity that can communicate with other |
|
21 // entities. It looks like node@domain/resource. Node and resource are |
|
22 // sometimes optional. |
|
23 type JID struct { |
|
24 Node string |
|
25 Domain string |
|
26 Resource string |
|
27 } |
|
28 |
|
29 var _ fmt.Stringer = &JID{} |
|
30 var _ flag.Value = &JID{} |
|
31 |
|
32 // XMPP's <stream:stream> XML element |
|
33 type stream struct { |
|
34 XMLName xml.Name `xml:"stream=http://etherx.jabber.org/streams stream"` |
|
35 To string `xml:"to,attr"` |
|
36 From string `xml:"from,attr"` |
|
37 Id string `xml:"id,attr"` |
|
38 Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr"` |
|
39 Version string `xml:"version,attr"` |
|
40 } |
|
41 |
|
42 var _ fmt.Stringer = &stream{} |
|
43 |
|
44 // <stream:error> |
|
45 type streamError struct { |
|
46 XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` |
|
47 Any Generic `xml:",any"` |
|
48 Text *errText |
|
49 } |
|
50 |
|
51 type errText struct { |
|
52 XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-streams text"` |
|
53 Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr"` |
|
54 Text string `xml:",chardata"` |
|
55 } |
|
56 |
|
57 type Features struct { |
|
58 Starttls *starttls `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` |
|
59 Mechanisms mechs `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` |
|
60 Bind *bindIq |
|
61 Session *Generic |
|
62 Any *Generic |
|
63 } |
|
64 |
|
65 type starttls struct { |
|
66 XMLName xml.Name |
|
67 Required *string |
|
68 } |
|
69 |
|
70 type mechs struct { |
|
71 Mechanism []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanism"` |
|
72 } |
|
73 |
|
74 type auth struct { |
|
75 XMLName xml.Name |
|
76 Chardata string `xml:",chardata"` |
|
77 Mechanism string `xml:"mechanism,attr,omitempty"` |
|
78 Any *Generic |
|
79 } |
|
80 |
|
81 type Stanza interface { |
|
82 GetHeader() *Header |
|
83 } |
|
84 |
|
85 // One of the three core XMPP stanza types: iq, message, presence. See |
|
86 // RFC3920, section 9. |
|
87 type Header struct { |
|
88 To string `xml:"to,attr,omitempty"` |
|
89 From string `xml:"from,attr,omitempty"` |
|
90 Id string `xml:"id,attr,omitempty"` |
|
91 Type string `xml:"type,attr,omitempty"` |
|
92 Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"` |
|
93 Innerxml string `xml:",innerxml"` |
|
94 Error *Error |
|
95 Nested []interface{} |
|
96 } |
|
97 |
|
98 // message stanza |
|
99 type Message struct { |
|
100 XMLName xml.Name `xml:"jabber:client message"` |
|
101 Header |
|
102 Subject *Generic `xml:"jabber:client subject"` |
|
103 Body *Generic `xml:"jabber:client body"` |
|
104 Thread *Generic `xml:"jabber:client thread"` |
|
105 } |
|
106 |
|
107 var _ Stanza = &Message{} |
|
108 |
|
109 // presence stanza |
|
110 type Presence struct { |
|
111 XMLName xml.Name `xml:"presence"` |
|
112 Header |
|
113 Show *Generic `xml:"jabber:client show"` |
|
114 Status *Generic `xml:"jabber:client status"` |
|
115 Priority *Generic `xml:"jabber:client priority"` |
|
116 } |
|
117 |
|
118 var _ Stanza = &Presence{} |
|
119 |
|
120 // iq stanza |
|
121 type Iq struct { |
|
122 XMLName xml.Name `xml:"iq"` |
|
123 Header |
|
124 } |
|
125 |
|
126 var _ Stanza = &Iq{} |
|
127 |
|
128 // Describes an XMPP stanza error. See RFC 3920, Section 9.3. |
|
129 type Error struct { |
|
130 XMLName xml.Name `xml:"error"` |
|
131 // The error type attribute. |
|
132 Type string `xml:"type,attr"` |
|
133 // Any nested element, if present. |
|
134 Any *Generic |
|
135 } |
|
136 |
|
137 var _ error = &Error{} |
|
138 |
|
139 // Used for resource binding as a nested element inside <iq/>. |
|
140 type bindIq struct { |
|
141 XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` |
|
142 Resource *string `xml:"resource"` |
|
143 Jid *string `xml:"jid"` |
|
144 } |
|
145 |
|
146 // Holds an XML element not described by the more specific types. |
|
147 type Generic struct { |
|
148 XMLName xml.Name |
|
149 Any *Generic `xml:",any"` |
|
150 Chardata string `xml:",chardata"` |
|
151 } |
|
152 |
|
153 var _ fmt.Stringer = &Generic{} |
|
154 |
|
155 func (jid *JID) String() string { |
|
156 result := jid.Domain |
|
157 if jid.Node != "" { |
|
158 result = jid.Node + "@" + result |
|
159 } |
|
160 if jid.Resource != "" { |
|
161 result = result + "/" + jid.Resource |
|
162 } |
|
163 return result |
|
164 } |
|
165 |
|
166 // Set implements flag.Value. It returns true if it successfully |
|
167 // parses the string. |
|
168 func (jid *JID) Set(val string) error { |
|
169 r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$") |
|
170 parts := r.FindStringSubmatch(val) |
|
171 if parts == nil { |
|
172 return fmt.Errorf("%s doesn't match user@domain/resource", val) |
|
173 } |
|
174 // jid.Node = stringprep.Nodeprep(parts[2]) |
|
175 // jid.Domain = stringprep.Nodeprep(parts[3]) |
|
176 // jid.Resource = stringprep.Resourceprep(parts[5]) |
|
177 jid.Node = parts[2] |
|
178 jid.Domain = parts[3] |
|
179 jid.Resource = parts[5] |
|
180 return nil |
|
181 } |
|
182 |
|
183 func (s *stream) String() string { |
|
184 var buf bytes.Buffer |
|
185 buf.WriteString(`<stream:stream xmlns="`) |
|
186 buf.WriteString(NsClient) |
|
187 buf.WriteString(`" xmlns:stream="`) |
|
188 buf.WriteString(NsStream) |
|
189 buf.WriteString(`"`) |
|
190 if s.To != "" { |
|
191 buf.WriteString(` to="`) |
|
192 xml.Escape(&buf, []byte(s.To)) |
|
193 buf.WriteString(`"`) |
|
194 } |
|
195 if s.From != "" { |
|
196 buf.WriteString(` from="`) |
|
197 xml.Escape(&buf, []byte(s.From)) |
|
198 buf.WriteString(`"`) |
|
199 } |
|
200 if s.Id != "" { |
|
201 buf.WriteString(` id="`) |
|
202 xml.Escape(&buf, []byte(s.Id)) |
|
203 buf.WriteString(`"`) |
|
204 } |
|
205 if s.Lang != "" { |
|
206 buf.WriteString(` xml:lang="`) |
|
207 xml.Escape(&buf, []byte(s.Lang)) |
|
208 buf.WriteString(`"`) |
|
209 } |
|
210 if s.Version != "" { |
|
211 buf.WriteString(` version="`) |
|
212 xml.Escape(&buf, []byte(s.Version)) |
|
213 buf.WriteString(`"`) |
|
214 } |
|
215 buf.WriteString(">") |
|
216 return buf.String() |
|
217 } |
|
218 |
|
219 func parseStream(se xml.StartElement) (*stream, error) { |
|
220 s := &stream{} |
|
221 for _, attr := range se.Attr { |
|
222 switch strings.ToLower(attr.Name.Local) { |
|
223 case "to": |
|
224 s.To = attr.Value |
|
225 case "from": |
|
226 s.From = attr.Value |
|
227 case "id": |
|
228 s.Id = attr.Value |
|
229 case "lang": |
|
230 s.Lang = attr.Value |
|
231 case "version": |
|
232 s.Version = attr.Value |
|
233 } |
|
234 } |
|
235 return s, nil |
|
236 } |
|
237 |
|
238 func (iq *Iq) GetHeader() *Header { |
|
239 return &iq.Header |
|
240 } |
|
241 |
|
242 func (m *Message) GetHeader() *Header { |
|
243 return &m.Header |
|
244 } |
|
245 |
|
246 func (p *Presence) GetHeader() *Header { |
|
247 return &p.Header |
|
248 } |
|
249 |
|
250 func (u *Generic) String() string { |
|
251 if u == nil { |
|
252 return "nil" |
|
253 } |
|
254 var sub string |
|
255 if u.Any != nil { |
|
256 sub = u.Any.String() |
|
257 } |
|
258 return fmt.Sprintf("<%s %s>%s%s</%s %s>", u.XMLName.Space, |
|
259 u.XMLName.Local, sub, u.Chardata, u.XMLName.Space, |
|
260 u.XMLName.Local) |
|
261 } |
|
262 |
|
263 func (er *Error) Error() string { |
|
264 buf, err := xml.Marshal(er) |
|
265 if err != nil { |
|
266 Warn.Log("double bad error: couldn't marshal error") |
|
267 return "unreadable error" |
|
268 } |
|
269 return string(buf) |
|
270 } |
|
271 |
|
272 var bindExt Extension = Extension{StanzaHandlers: map[string]func(*xml.Name) interface{}{NsBind: newBind}} |
|
273 |
|
274 func newBind(name *xml.Name) interface{} { |
|
275 return &bindIq{} |
|
276 } |