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