1// Copyright 2013 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 implements the XMPP IM protocol, as specified in RFC 6120 and
6// 6121.
7package xmpp
8
9import (
10	"crypto/sha1"
11	"encoding/base64"
12	"encoding/xml"
13	"errors"
14	"io"
15	"sort"
16
17	"github.com/coyim/coyim/xmpp/data"
18)
19
20// HasSupportTo uses XEP-0030 to checks if an entity supports a feature.
21// The entity is identified by its JID and the feature by its XML namespace.
22// It returns true if the feaure is reported to be supported and false
23// otherwise (including if any error happened).
24func (c *conn) HasSupportTo(entity string, feature string) bool {
25	if res, ok := c.DiscoveryFeatures(entity); ok {
26		return stringArrayContains(res, feature)
27	}
28
29	return false
30}
31
32func stringArrayContains(r []string, a string) bool {
33	for _, f := range r {
34		if f == a {
35			return true
36		}
37	}
38
39	return false
40}
41
42func (c *conn) sendDiscoveryInfo(to string) (reply <-chan data.Stanza, cookie data.Cookie, err error) {
43	return c.SendIQ(to, "get", &data.DiscoveryInfoQuery{})
44}
45
46func parseDiscoveryInfoReply(iq *data.ClientIQ) (*data.DiscoveryInfoQuery, error) {
47	reply := &data.DiscoveryInfoQuery{}
48	err := xml.Unmarshal(iq.Query, reply)
49	return reply, err
50}
51
52func (c *conn) sendDiscoveryItems(to string) (reply <-chan data.Stanza, cookie data.Cookie, err error) {
53	return c.SendIQ(to, "get", &data.DiscoveryItemsQuery{})
54}
55
56func parseDiscoveryItemsReply(iq *data.ClientIQ) (*data.DiscoveryItemsQuery, error) {
57	reply := &data.DiscoveryItemsQuery{}
58	err := xml.Unmarshal(iq.Query, reply)
59	return reply, err
60}
61
62// TODO: at some point we need to cache these features somewhere
63
64// QueryServiceInformation sends a service discovery information ("disco#info") query.
65// See XEP-0030, Section "3. Discovering Information About a Jabber Entity"
66// This method blocks until conn#Next() receives the response to the IQ.
67func (c *conn) QueryServiceInformation(entity string) (*data.DiscoveryInfoQuery, error) {
68	reply, _, err := c.sendDiscoveryInfo(entity)
69	if err != nil {
70		return nil, err
71	}
72
73	stanza, ok := <-reply
74	if !ok {
75		return nil, errors.New("xmpp: failed to receive response")
76	}
77
78	iq, ok := stanza.Value.(*data.ClientIQ)
79	if !ok {
80		return nil, errors.New("xmpp: failed to parse response")
81	}
82
83	return parseDiscoveryInfoReply(iq)
84}
85
86// QueryServiceItems sends a Service Discovery items ("disco#items") query.
87// See XEP-0030, Section "4. Discovering the Items Associated with a Jabber Entity"
88// This method blocks until conn#Next() receives the response to the IQ.
89func (c *conn) QueryServiceItems(entity string) (*data.DiscoveryItemsQuery, error) {
90	reply, _, err := c.sendDiscoveryItems(entity)
91	if err != nil {
92		return nil, err
93	}
94
95	stanza, ok := <-reply
96	if !ok {
97		return nil, errors.New("xmpp: failed to receive response")
98	}
99
100	iq, ok := stanza.Value.(*data.ClientIQ)
101	if !ok {
102		return nil, errors.New("xmpp: failed to parse response")
103	}
104
105	return parseDiscoveryItemsReply(iq)
106}
107
108func (c *conn) DiscoveryFeatures(entity string) ([]string, bool) {
109	discoveryReply, err := c.QueryServiceInformation(entity)
110	if err != nil {
111		return nil, false
112	}
113
114	var result []string
115	for _, f := range discoveryReply.Features {
116		result = append(result, f.Var)
117	}
118
119	return result, true
120}
121
122func (c *conn) DiscoveryFeaturesAndIdentities(entity string) ([]data.DiscoveryIdentity, []string, bool) {
123	discoveryReply, err := c.QueryServiceInformation(entity)
124	if err != nil {
125		return nil, nil, false
126	}
127
128	var result []string
129	for _, f := range discoveryReply.Features {
130		result = append(result, f.Var)
131	}
132
133	return discoveryReply.Identities, result, true
134}
135
136//DiscoveryReply returns a minimum reply to a http://jabber.org/protocol/disco#info query
137func DiscoveryReply(name string) data.DiscoveryInfoQuery {
138	return data.DiscoveryInfoQuery{
139		Identities: []data.DiscoveryIdentity{
140			{
141				Category: "client",
142				Type:     "pc",
143
144				//NOTE: this is optional as per XEP-0030
145				Name: name,
146			},
147		},
148		//TODO: extract constants that document which XEPs are supported
149		Features: []data.DiscoveryFeature{
150			{Var: "http://jabber.org/protocol/disco#info"},                    //XEP-0030
151			{Var: "urn:xmpp:bob"},                                             //XEP-0231
152			{Var: "urn:xmpp:ping"},                                            //XEP-0199
153			{Var: "http://jabber.org/protocol/caps"},                          //XEP-0115
154			{Var: "jabber:iq:version"},                                        //XEP-0092
155			{Var: "vcard-temp"},                                               //XEP-0054
156			{Var: "jabber:x:data"},                                            //XEP-004
157			{Var: "http://jabber.org/protocol/si"},                            //XEP-0096
158			{Var: "http://jabber.org/protocol/si/profile/file-transfer"},      //XEP-0096
159			{Var: "http://jabber.org/protocol/si/profile/directory-transfer"}, //XEP-xxxx: SI Directory Transfer
160			//			{Var: "http://jabber.org/protocol/si/profile/encrypted-data-transfer"}, //XEP-xxxx: SI Encrypted Data Transfer
161			{Var: "http://jabber.org/protocol/bytestreams"}, //XEP-0047
162		},
163	}
164}
165
166// VerificationString returns a SHA-1 verification string as defined in XEP-0115.
167// See http://xmpp.org/extensions/xep-0115.html#ver
168func VerificationString(r *data.DiscoveryInfoQuery) (string, error) {
169	h := sha1.New()
170
171	seen := make(map[string]bool)
172	identitySorter := &xep0115Sorter{}
173	for i := range r.Identities {
174		identitySorter.add(&r.Identities[i])
175	}
176	sort.Sort(identitySorter)
177	for _, id := range identitySorter.s {
178		id := id.(*data.DiscoveryIdentity)
179		c := id.Category + "/" + id.Type + "/" + id.Lang + "/" + id.Name + "<"
180		if seen[c] {
181			return "", errors.New("duplicate discovery identity")
182		}
183		seen[c] = true
184		io.WriteString(h, c)
185	}
186
187	seen = make(map[string]bool)
188	featureSorter := &xep0115Sorter{}
189	for i := range r.Features {
190		featureSorter.add(&r.Features[i])
191	}
192	sort.Sort(featureSorter)
193	for _, f := range featureSorter.s {
194		f := f.(*data.DiscoveryFeature)
195		if seen[f.Var] {
196			return "", errors.New("duplicate discovery feature")
197		}
198		seen[f.Var] = true
199		io.WriteString(h, f.Var+"<")
200	}
201
202	seen = make(map[string]bool)
203	for _, f := range r.Forms {
204		if len(f.Fields) == 0 {
205			continue
206		}
207		fieldSorter := &xep0115Sorter{}
208		for i := range f.Fields {
209			fieldSorter.add(&f.Fields[i])
210		}
211		sort.Sort(fieldSorter)
212		formTypeField := fieldSorter.s[0].(*data.FormFieldX)
213		if formTypeField.Var != "FORM_TYPE" {
214			continue
215		}
216		if seen[formTypeField.Type] {
217			return "", errors.New("multiple forms of the same type")
218		}
219		seen[formTypeField.Type] = true
220		if len(formTypeField.Values) != 1 {
221			return "", errors.New("form does not have a single FORM_TYPE value")
222		}
223		if formTypeField.Type != "hidden" {
224			continue
225		}
226		io.WriteString(h, formTypeField.Values[0]+"<")
227		for _, field := range fieldSorter.s[1:] {
228			field := field.(*data.FormFieldX)
229			io.WriteString(h, field.Var+"<")
230			values := append([]string{}, field.Values...)
231			sort.Strings(values)
232			for _, v := range values {
233				io.WriteString(h, v+"<")
234			}
235		}
236	}
237
238	return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
239}
240