1 /*
2  * xmpp_discoitem.cpp
3  * Copyright (C) 2003  Justin Karneges
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  *
19  */
20 
21 #include <QtXml>
22 
23 #include "xmpp_discoitem.h"
24 
25 using namespace XMPP;
26 
27 class XMPP::DiscoItemPrivate : public QSharedData
28 {
29 public:
DiscoItemPrivate()30 	DiscoItemPrivate()
31 	{
32 		action = DiscoItem::None;
33 	}
34 
35 	Jid jid;
36 	QString name;
37 	QString node;
38 	DiscoItem::Action action;
39 
40 	Features features;
41 	DiscoItem::Identities identities;
42 	QList<XData> exts;
43 };
44 
DiscoItem()45 DiscoItem::DiscoItem()
46 {
47 	d = new DiscoItemPrivate;
48 }
49 
DiscoItem(const DiscoItem & from)50 DiscoItem::DiscoItem(const DiscoItem &from)
51 {
52 	d = new DiscoItemPrivate;
53 	*this = from;
54 }
55 
operator =(const DiscoItem & from)56 DiscoItem & DiscoItem::operator= (const DiscoItem &from)
57 {
58 	d->jid = from.d->jid;
59 	d->name = from.d->name;
60 	d->node = from.d->node;
61 	d->action = from.d->action;
62 	d->features = from.d->features;
63 	d->identities = from.d->identities;
64 	d->exts = from.d->exts;
65 
66 	return *this;
67 }
68 
~DiscoItem()69 DiscoItem::~DiscoItem()
70 {
71 
72 }
73 
toAgentItem() const74 AgentItem DiscoItem::toAgentItem() const
75 {
76 	AgentItem ai;
77 
78 	ai.setJid( jid() );
79 	ai.setName( name() );
80 
81 	Identity id;
82 	if ( !identities().isEmpty() )
83 		id = identities().first();
84 
85 	ai.setCategory( id.category );
86 	ai.setType( id.type );
87 
88 	ai.setFeatures( d->features );
89 
90 	return ai;
91 }
92 
fromAgentItem(const AgentItem & ai)93 void DiscoItem::fromAgentItem(const AgentItem &ai)
94 {
95 	setJid( ai.jid() );
96 	setName( ai.name() );
97 
98 	Identity id;
99 	id.category = ai.category();
100 	id.type = ai.type();
101 	id.name = ai.name();
102 
103 	Identities idList;
104 	idList << id;
105 
106 	setIdentities( idList );
107 
108 	setFeatures( ai.features() );
109 }
110 
capsHash(QCryptographicHash::Algorithm algo) const111 QString DiscoItem::capsHash(QCryptographicHash::Algorithm algo) const
112 {
113 	QStringList prep;
114 	DiscoItem::Identities idents = d->identities;
115 	qSort(idents);
116 
117 	foreach (const DiscoItem::Identity &id, idents) {
118 		prep << QString("%1/%2/%3/%4").arg(id.category, id.type, id.lang, id.name);
119 	}
120 
121 	QStringList fl = d->features.list();
122 	qSort(fl);
123 	prep += fl;
124 
125 	QMap<QString,XData> forms;
126 	foreach (const XData &xd, d->exts) {
127 		if (xd.registrarType().isEmpty()) {
128 			continue;
129 		}
130 		if (forms.contains(xd.registrarType())) {
131 			return QString(); // ill-formed
132 		}
133 		forms.insert(xd.registrarType(), xd);
134 	}
135 	foreach (const XData &xd, forms.values()) {
136 		prep << xd.registrarType();
137 		QMap <QString, QStringList> values;
138 		foreach (const XData::Field &f, xd.fields()) {
139 			if (f.var() == QLatin1String("FORM_TYPE")) {
140 				continue;
141 			}
142 			if (values.contains(f.var())) {
143 				return QString(); // ill-formed
144 			}
145 			QStringList v = f.value();
146 			if (v.isEmpty()) {
147 				continue; // maybe it's media-element but xep-115 (1.5) and xep-232 (0.3) are not clear about that.
148 			}
149 			qSort(v);
150 			values[f.var()] = v;
151 		}
152 		QMap <QString, QStringList>::ConstIterator it = values.constBegin();
153 		for (; it != values.constEnd(); ++it) {
154 			prep += it.key();
155 			prep += it.value();
156 		}
157 	}
158 
159 	QByteArray ba = QString(prep.join(QLatin1String("<")) + QLatin1Char('<')).toUtf8();
160 	//qDebug() << "Server caps ver: " << (prep.join(QLatin1String("<")) + QLatin1Char('<'))
161 	//         << "Hash:" << QString::fromLatin1(QCryptographicHash::hash(ba, algo).toBase64());
162 	return QString::fromLatin1(QCryptographicHash::hash(ba, algo).toBase64());
163 }
164 
fromDiscoInfoResult(const QDomElement & q)165 DiscoItem DiscoItem::fromDiscoInfoResult(const QDomElement &q)
166 {
167 	DiscoItem item;
168 
169 	item.setNode( q.attribute("node") );
170 
171 	QStringList features;
172 	DiscoItem::Identities identities;
173 	QList<XData> extList;
174 
175 	for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) {
176 		QDomElement e = n.toElement();
177 		if( e.isNull() )
178 			continue;
179 
180 		if ( e.tagName() == "feature" ) {
181 			features << e.attribute("var");
182 		}
183 		else if ( e.tagName() == "identity" ) {
184 			DiscoItem::Identity id;
185 
186 			id.category = e.attribute("category");
187 			id.type     = e.attribute("type");
188 			id.lang     = e.attribute("lang");
189 			id.name     = e.attribute("name");
190 
191 			identities.append( id );
192 		}
193 		else if (e.tagName() == QLatin1String("x") && e.attribute("xmlns") == QLatin1String("jabber:x:data")) {
194 			XData form;
195 			form.fromXml(e);
196 			extList.append(form);
197 		}
198 	}
199 
200 	item.setFeatures( features );
201 	item.setIdentities( identities );
202 	item.setExtensions( extList );
203 
204 	return item;
205 }
206 
toDiscoInfoResult(QDomDocument * doc) const207 QDomElement DiscoItem::toDiscoInfoResult(QDomDocument *doc) const
208 {
209 	QDomElement q = doc->createElementNS(QLatin1String("http://jabber.org/protocol/disco#info"), QLatin1String("query"));
210 	q.setAttribute("node", d->node);
211 
212 	foreach (const Identity &id, d->identities) {
213 		QDomElement idel = q.appendChild(doc->createElement(QLatin1String("identity"))).toElement();
214 		idel.setAttribute("category", id.category);
215 		idel.setAttribute("type", id.type);
216 		if (!id.lang.isEmpty()) {
217 			idel.setAttribute("lang", id.lang);
218 		}
219 		if (!id.name.isEmpty()) {
220 			idel.setAttribute("name", id.name);
221 		}
222 	}
223 
224 	foreach (const QString &f, d->features.list()) {
225 		QDomElement fel = q.appendChild(doc->createElement(QLatin1String("feature"))).toElement();
226 		fel.setAttribute("var", f);
227 	}
228 
229 	foreach (const XData &f, d->exts) {
230 		q.appendChild(f.toXml(doc));
231 	}
232 
233 	return q;
234 }
235 
jid() const236 const Jid &DiscoItem::jid() const
237 {
238 	return d->jid;
239 }
240 
setJid(const Jid & j)241 void DiscoItem::setJid(const Jid &j)
242 {
243 	d->jid = j;
244 }
245 
name() const246 const QString &DiscoItem::name() const
247 {
248 	return d->name;
249 }
250 
setName(const QString & n)251 void DiscoItem::setName(const QString &n)
252 {
253 	d->name = n;
254 }
255 
node() const256 const QString &DiscoItem::node() const
257 {
258 	return d->node;
259 }
260 
setNode(const QString & n)261 void DiscoItem::setNode(const QString &n)
262 {
263 	d->node = n;
264 }
265 
action() const266 DiscoItem::Action DiscoItem::action() const
267 {
268 	return d->action;
269 }
270 
setAction(Action a)271 void DiscoItem::setAction(Action a)
272 {
273 	d->action = a;
274 }
275 
features() const276 const Features &DiscoItem::features() const
277 {
278 	return d->features;
279 }
280 
setFeatures(const Features & f)281 void DiscoItem::setFeatures(const Features &f)
282 {
283 	d->features = f;
284 }
285 
identities() const286 const DiscoItem::Identities &DiscoItem::identities() const
287 {
288 	return d->identities;
289 }
290 
setIdentities(const Identities & i)291 void DiscoItem::setIdentities(const Identities &i)
292 {
293 	d->identities = i;
294 
295 	if ( name().isEmpty() && i.count() )
296 		setName( i.first().name );
297 }
298 
extensions() const299 const QList<XData> &DiscoItem::extensions() const
300 {
301 	return d->exts;
302 }
303 
setExtensions(const QList<XData> & extlist)304 void DiscoItem::setExtensions(const QList<XData> &extlist)
305 {
306 	d->exts = extlist;
307 }
308 
registeredExtension(const QString & ns) const309 XData DiscoItem::registeredExtension(const QString &ns) const
310 {
311 	foreach (const XData &xd, d->exts) {
312 		if (xd.registrarType() == ns) {
313 			return xd;
314 		}
315 	}
316 	return XData();
317 }
318 
string2action(QString s)319 DiscoItem::Action DiscoItem::string2action(QString s)
320 {
321 	Action a;
322 
323 	if ( s == "update" )
324 		a = Update;
325 	else if ( s == "remove" )
326 		a = Remove;
327 	else
328 		a = None;
329 
330 	return a;
331 }
332 
action2string(Action a)333 QString DiscoItem::action2string(Action a)
334 {
335 	QString s;
336 
337 	if ( a == Update )
338 		s = "update";
339 	else if ( a == Remove )
340 		s = "remove";
341 	else
342 		s = QString();
343 
344 	return s;
345 }
346 
347 
348 
operator <(const DiscoItem::Identity & a,const DiscoItem::Identity & b)349 bool XMPP::operator<(const DiscoItem::Identity &a, const DiscoItem::Identity &b)
350 {
351 	int r = a.category.compare(b.category);
352 	if (!r) {
353 		r = a.type.compare(b.type);
354 		if (!r) {
355 			r = a.lang.compare(b.lang);
356 			if (!r) {
357 				r = a.name.compare(b.name);
358 			}
359 		}
360 	}
361 
362 	return r < 0;
363 }
364 
operator ==(const DiscoItem::Identity & other) const365 bool DiscoItem::Identity::operator==(const DiscoItem::Identity &other) const
366 {
367 	return category == other.category && type == other.type &&
368 	        lang == other.lang && name == other.name;
369 }
370