1 /**
2  * This file is part of TelepathyQt
3  *
4  * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
5  * @copyright Copyright (C) 2008-2010 Nokia Corporation
6  * @license LGPL 2.1
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "TelepathyQt/manager-file.h"
24 
25 #include "TelepathyQt/debug-internal.h"
26 #include "TelepathyQt/key-file.h"
27 
28 #include <TelepathyQt/Constants>
29 #include <TelepathyQt/Utils>
30 
31 #include <QtCore/QDir>
32 #include <QtCore/QHash>
33 #include <QtCore/QString>
34 #include <QtCore/QStringList>
35 #include <QtDBus/QDBusVariant>
36 
37 namespace Tp
38 {
39 
40 struct TP_QT_NO_EXPORT ManagerFile::Private
41 {
42     Private();
43     Private(const QString &cnName);
44 
45     void init();
46     bool parse(const QString &fileName);
47     bool isValid() const;
48 
49     bool hasParameter(const QString &protocol, const QString &paramName) const;
50     ParamSpec *getParameter(const QString &protocol, const QString &paramName);
51     QStringList protocols() const;
52     ParamSpecList parameters(const QString &protocol) const;
53 
54     QVariant valueForKey(const QString &param, const QString &dbusSignature);
55 
56     struct ProtocolInfo
57     {
ProtocolInfoTp::ManagerFile::Private::ProtocolInfo58         ProtocolInfo() {}
ProtocolInfoTp::ManagerFile::Private::ProtocolInfo59         ProtocolInfo(const ParamSpecList &params, const PresenceSpecList &statuses)
60             : params(params),
61               statuses(statuses)
62         {
63         }
64 
65         ParamSpecList params;
66         QString vcardField;
67         QString englishName;
68         QString iconName;
69         RequestableChannelClassList rccs;
70         PresenceSpecList statuses;
71         AvatarSpec avatarRequirements;
72         QStringList addressableVCardFields;
73         QStringList addressableUriSchemes;
74     };
75 
76     QString cmName;
77     KeyFile keyFile;
78     QHash<QString, ProtocolInfo> protocolsMap;
79     bool valid;
80 };
81 
Private()82 ManagerFile::Private::Private()
83     : valid(false)
84 {
85 }
86 
Private(const QString & cmName)87 ManagerFile::Private::Private(const QString &cmName)
88     : cmName(cmName),
89       valid(false)
90 {
91     init();
92 }
93 
init()94 void ManagerFile::Private::init()
95 {
96     // TODO: should we cache the configDirs anywhere?
97     QStringList configDirs;
98 
99     QString xdgDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"));
100     if (xdgDataHome.isEmpty()) {
101         configDirs << QDir::homePath() + QLatin1String("/.local/share/data/telepathy/managers/");
102     }
103     else {
104         configDirs << xdgDataHome + QLatin1String("/telepathy/managers/");
105     }
106 
107     QString xdgDataDirsEnv = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS"));
108     if (xdgDataDirsEnv.isEmpty()) {
109         configDirs << QLatin1String("/usr/local/share/telepathy/managers/");
110         configDirs << QLatin1String("/usr/share/telepathy/managers/");
111     }
112     else {
113         QStringList xdgDataDirs = xdgDataDirsEnv.split(QLatin1Char(':'));
114         foreach (const QString xdgDataDir, xdgDataDirs) {
115             configDirs << xdgDataDir + QLatin1String("/telepathy/managers/");
116         }
117     }
118 
119     foreach (const QString configDir, configDirs) {
120         QString fileName = configDir + cmName + QLatin1String(".manager");
121         if (QFile::exists(fileName)) {
122             debug() << "parsing manager file" << fileName;
123             protocolsMap.clear();
124             if (!parse(fileName)) {
125                 warning() << "error parsing manager file" << fileName;
126                 continue;
127             }
128             valid = true;
129             return;
130         }
131     }
132 }
133 
parse(const QString & fileName)134 bool ManagerFile::Private::parse(const QString &fileName)
135 {
136     keyFile.setFileName(fileName);
137     if (keyFile.status() != KeyFile::NoError) {
138         return false;
139     }
140 
141     /* read supported protocols and parameters */
142     QString protocol;
143     QStringList groups = keyFile.allGroups();
144     foreach (const QString group, groups) {
145         if (group.startsWith(QLatin1String("Protocol "))) {
146             protocol = group.right(group.length() - 9);
147             keyFile.setGroup(group);
148 
149             ParamSpecList paramSpecList;
150             SimpleStatusSpecMap statuses;
151             QString param;
152             QStringList params = keyFile.keys();
153             foreach (param, params) {
154                 ParamSpec spec;
155                 SimpleStatusSpec status;
156                 spec.flags = 0;
157 
158                 QStringList values = keyFile.value(param).split(QLatin1String(" "));
159 
160                 if (param.startsWith(QLatin1String("param-"))) {
161                     spec.name = param.right(param.length() - 6);
162 
163                     if (values.length() == 0) {
164                         warning() << "param" << spec.name << "set but no signature defined";
165                         return false;
166                     }
167 
168                     if (spec.name.endsWith(QLatin1String("password"))) {
169                         spec.flags |= ConnMgrParamFlagSecret;
170                     }
171 
172                     spec.signature = values[0];
173 
174                     if (values.contains(QLatin1String("secret"))) {
175                         spec.flags |= ConnMgrParamFlagSecret;
176                     }
177 
178                     if (values.contains(QLatin1String("dbus-property"))) {
179                         spec.flags |= ConnMgrParamFlagDBusProperty;
180                     }
181 
182                     if (values.contains(QLatin1String("required"))) {
183                         spec.flags |= ConnMgrParamFlagRequired;
184                     }
185 
186                     if (values.contains(QLatin1String("register"))) {
187                         spec.flags |= ConnMgrParamFlagRegister;
188                     }
189 
190                     paramSpecList.append(spec);
191                 } else if (param.startsWith(QLatin1String("status-"))) {
192                     QString statusName = param.right(param.length() - 7);
193 
194                     if (values.length() == 0) {
195                         warning() << "status" << statusName << "set but no type defined";
196                         return false;
197                     }
198 
199                     bool ok;
200                     status.type = values[0].toUInt(&ok);
201                     if (!ok) {
202                         warning() << "status" << statusName << "set but type is not an uint";
203                         return false;
204                     }
205 
206                     if (values.contains(QLatin1String("settable"))) {
207                         status.maySetOnSelf = true;
208                     } else {
209                         status.maySetOnSelf = false;
210                     }
211 
212                     if (values.contains(QLatin1String("message"))) {
213                         status.canHaveMessage = true;
214                     } else {
215                         status.canHaveMessage = false;
216                     }
217 
218                     if (statuses.contains(statusName)) {
219                         warning() << "status" << statusName << "defined more than once, "
220                             "replacing it";
221                     }
222 
223                     statuses.insert(statusName, status);
224                 }
225             }
226 
227             protocolsMap.insert(protocol, ProtocolInfo(paramSpecList, PresenceSpecList(statuses)));
228 
229             /* now that we have all param-* created, let's find their default values */
230             foreach (param, params) {
231                 if (param.startsWith(QLatin1String("default-"))) {
232                     QString paramName = param.right(param.length() - 8);
233 
234                     if (!hasParameter(protocol, paramName)) {
235                         warning() << "param" << paramName
236                                   << "has default value set, but not a definition";
237                         continue;
238                     }
239 
240                     ParamSpec *spec = getParameter(protocol, paramName);
241 
242                     spec->flags |= ConnMgrParamFlagHasDefault;
243 
244                     /* map based on the param dbus signature, otherwise use
245                      * QString */
246                     QVariant value = valueForKey(param, spec->signature);
247                     if (value.type() == QVariant::Invalid) {
248                         warning() << "param" << paramName
249                                   << "has invalid signature";
250                         protocolsMap.clear();
251                         return false;
252                     }
253                     spec->defaultValue = QDBusVariant(value);
254                 }
255             }
256 
257             ProtocolInfo &info = protocolsMap[protocol];
258             info.vcardField = keyFile.value(QLatin1String("VCardField"));
259             info.englishName = keyFile.value(QLatin1String("EnglishName"));
260             if (info.englishName.isEmpty()) {
261                 QStringList words = protocol.split(QLatin1Char('-'));
262                 for (int i = 0; i < words.size(); ++i) {
263                     words[i][0] = words[i].at(0).toUpper();
264                 }
265                 info.englishName = words.join(QLatin1String(" "));
266             }
267 
268             info.iconName = keyFile.value(QLatin1String("Icon"));
269             if (info.iconName.isEmpty()) {
270                 info.iconName = QString(QLatin1String("im-%1")).arg(protocol);
271             }
272 
273             QStringList supportedMimeTypes = keyFile.valueAsStringList(
274                     QLatin1String("SupportedAvatarMIMETypes"));
275             uint minHeight = keyFile.value(QLatin1String("MinimumAvatarHeight")).toUInt();
276             uint maxHeight = keyFile.value(QLatin1String("MaximumAvatarHeight")).toUInt();
277             uint recommendedHeight = keyFile.value(
278                     QLatin1String("RecommendedAvatarHeight")).toUInt();
279             uint minWidth = keyFile.value(QLatin1String("MinimumAvatarWidth")).toUInt();
280             uint maxWidth = keyFile.value(QLatin1String("MaximumAvatarWidth")).toUInt();
281             uint recommendedWidth = keyFile.value(
282                     QLatin1String("RecommendedAvatarWidth")).toUInt();
283             uint maxBytes = keyFile.value(QLatin1String("MaximumAvatarBytes")).toUInt();
284             info.avatarRequirements = AvatarSpec(supportedMimeTypes,
285                     minHeight, maxHeight, recommendedHeight,
286                     minWidth, maxWidth, recommendedWidth,
287                     maxBytes);
288 
289             info.addressableVCardFields = keyFile.valueAsStringList(
290                     QLatin1String("AddressableVCardFields"));
291             info.addressableUriSchemes = keyFile.valueAsStringList(
292                     QLatin1String("AddressableURISchemes"));
293 
294             QStringList rccGroups = keyFile.valueAsStringList(
295                     QLatin1String("RequestableChannelClasses"));
296             RequestableChannelClass rcc;
297             foreach (const QString &rccGroup, rccGroups) {
298                 keyFile.setGroup(rccGroup);
299 
300                 foreach (const QString &key, keyFile.keys()) {
301                     int spaceIdx = key.indexOf(QLatin1String(" "));
302                     if (spaceIdx == -1) {
303                         continue;
304                     }
305 
306                     QString propertyName = key.mid(0, spaceIdx);
307                     QString signature = key.mid(spaceIdx + 1);
308                     QString param = keyFile.value(key);
309                     QVariant value = valueForKey(key, signature);
310                     rcc.fixedProperties.insert(propertyName, value);
311                 }
312 
313                 rcc.allowedProperties = keyFile.valueAsStringList(
314                         QLatin1String("allowed"));
315 
316                 info.rccs.append(rcc);
317 
318                 rcc.fixedProperties.clear();
319                 rcc.allowedProperties.clear();
320             }
321         }
322     }
323 
324     return true;
325 }
326 
isValid() const327 bool ManagerFile::Private::isValid() const
328 {
329     return ((keyFile.status() == KeyFile::NoError) && (valid));
330 }
331 
hasParameter(const QString & protocol,const QString & paramName) const332 bool ManagerFile::Private::hasParameter(const QString &protocol,
333                                         const QString &paramName) const
334 {
335     ParamSpecList paramSpecList = protocolsMap[protocol].params;
336     foreach (const ParamSpec &paramSpec, paramSpecList) {
337         if (paramSpec.name == paramName) {
338             return true;
339         }
340     }
341     return false;
342 }
343 
getParameter(const QString & protocol,const QString & paramName)344 ParamSpec *ManagerFile::Private::getParameter(const QString &protocol,
345                                               const QString &paramName)
346 {
347     ParamSpecList &paramSpecList = protocolsMap[protocol].params;
348     for (int i = 0; i < paramSpecList.size(); ++i) {
349         ParamSpec &paramSpec = paramSpecList[i];
350         if (paramSpec.name == paramName) {
351             return &paramSpec;
352         }
353     }
354     return NULL;
355 }
356 
protocols() const357 QStringList ManagerFile::Private::protocols() const
358 {
359     return protocolsMap.keys();
360 }
361 
parameters(const QString & protocol) const362 ParamSpecList ManagerFile::Private::parameters(const QString &protocol) const
363 {
364     return protocolsMap.value(protocol).params;
365 }
366 
valueForKey(const QString & param,const QString & dbusSignature)367 QVariant ManagerFile::Private::valueForKey(const QString &param,
368                                            const QString &dbusSignature)
369 {
370     QString value = keyFile.rawValue(param);
371     return parseValueWithDBusSignature(value, dbusSignature);
372 }
373 
374 
375 /**
376  * \class ManagerFile
377  * \ingroup utils
378  * \headerfile TelepathyQt/manager-file.h <TelepathyQt/ManagerFile>
379  *
380  * \brief The ManagerFile class provides an easy way to read Telepathy manager
381  * files according to the \telepathy_spec.
382  */
383 
384 /**
385  * Create a ManagerFile object used to read .manager compliant files.
386  */
ManagerFile()387 ManagerFile::ManagerFile()
388     : mPriv(new Private())
389 {
390 }
391 
392 /**
393  * Create a ManagerFile object used to read .manager compliant files.
394  *
395  * \param cmName Name of the connection manager to read the file for.
396  */
ManagerFile(const QString & cmName)397 ManagerFile::ManagerFile(const QString &cmName)
398     : mPriv(new Private(cmName))
399 {
400 }
401 
402 /**
403  * Create a ManagerFile object used to read .manager compliant files.
404  */
ManagerFile(const ManagerFile & other)405 ManagerFile::ManagerFile(const ManagerFile &other)
406     : mPriv(new Private())
407 {
408     mPriv->cmName = other.mPriv->cmName;
409     mPriv->keyFile = other.mPriv->keyFile;
410     mPriv->protocolsMap = other.mPriv->protocolsMap;
411     mPriv->valid = other.mPriv->valid;
412 }
413 
414 /**
415  * Class destructor.
416  */
~ManagerFile()417 ManagerFile::~ManagerFile()
418 {
419     delete mPriv;
420 }
421 
operator =(const ManagerFile & other)422 ManagerFile &ManagerFile::operator=(const ManagerFile &other)
423 {
424     mPriv->cmName = other.mPriv->cmName;
425     mPriv->keyFile = other.mPriv->keyFile;
426     mPriv->protocolsMap = other.mPriv->protocolsMap;
427     mPriv->valid = other.mPriv->valid;
428     return *this;
429 }
430 
431 /**
432  * Check whether or not a ManagerFile object is valid. If the file for the
433  * specified connection manager cannot be found it will be considered invalid.
434  *
435  * \return true if valid, false otherwise.
436  */
isValid() const437 bool ManagerFile::isValid() const
438 {
439     return mPriv->isValid();
440 }
441 
442 /**
443  * Return a list of all protocols defined in the manager file.
444  *
445  * \return List of all protocols defined in the file.
446  */
protocols() const447 QStringList ManagerFile::protocols() const
448 {
449     return mPriv->protocols();
450 }
451 
452 /**
453  * Return a list of parameters for the given \a protocol.
454  *
455  * \param protocol Name of the protocol to look for.
456  * \return List of ParamSpec of a specific protocol defined in the file, or an
457  *         empty list if the protocol is not defined.
458  */
parameters(const QString & protocol) const459 ParamSpecList ManagerFile::parameters(const QString &protocol) const
460 {
461     return mPriv->parameters(protocol);
462 }
463 
464 /**
465  * Return the name of the most common vcard field used for the given \a protocol's
466  * contact identifiers, normalized to lower case.
467  *
468  * \param protocol Name of the protocol to look for.
469  * \return The most common vcard field used for the given protocol's contact
470  *         identifiers, or an empty string if there is no such field or the
471  *         protocol is not defined.
472  */
vcardField(const QString & protocol) const473 QString ManagerFile::vcardField(const QString &protocol) const
474 {
475     return mPriv->protocolsMap.value(protocol).vcardField;
476 }
477 
addressableVCardFields(const QString & protocol) const478 QStringList ManagerFile::addressableVCardFields(const QString &protocol) const
479 {
480     return mPriv->protocolsMap.value(protocol).addressableVCardFields;
481 }
482 
addressableUriSchemes(const QString & protocol) const483 QStringList ManagerFile::addressableUriSchemes(const QString &protocol) const
484 {
485     return mPriv->protocolsMap.value(protocol).addressableUriSchemes;
486 }
487 
488 /**
489  * Return the English-language name of the given \a protocol, such as "AIM" or "Yahoo!".
490  *
491  * The name can be used as a fallback if an application doesn't have a localized name for the
492  * protocol.
493  *
494  * If the manager file doesn't specify the english name, it is inferred from the protocol name, such
495  * that for example "google-talk" becomes "Google Talk", but "local-xmpp" becomes "Local Xmpp".
496  *
497  * \param protocol Name of the protocol to look for.
498  * \return An English-language name for the given \a protocol.
499  */
englishName(const QString & protocol) const500 QString ManagerFile::englishName(const QString &protocol) const
501 {
502     return mPriv->protocolsMap.value(protocol).englishName;
503 }
504 
505 /**
506  * Return the name of an icon for the given \a protocol in the system's icon
507  * theme, such as "im-msn".
508  *
509  * If the manager file doesn't specify the icon name, "im-<protocolname>" is assumed.
510  *
511  * \param protocol Name of the protocol to look for.
512  * \return The likely name of an icon for the given \a protocol.
513  */
iconName(const QString & protocol) const514 QString ManagerFile::iconName(const QString &protocol) const
515 {
516     return mPriv->protocolsMap.value(protocol).iconName;
517 }
518 
519 /**
520  * Return a list of channel classes which might be requestable from a connection
521  * to the given \a protocol.
522  *
523  * \param protocol Name of the protocol to look for.
524  * \return A list of channel classes which might be requestable from a
525  *         connection to the given \a protocol or a default constructed
526  *         RequestableChannelClassList instance if the protocol is not defined.
527  */
requestableChannelClasses(const QString & protocol) const528 RequestableChannelClassList ManagerFile::requestableChannelClasses(
529         const QString &protocol) const
530 {
531     return mPriv->protocolsMap.value(protocol).rccs;
532 }
533 
534 /**
535  * Return a list of PresenceSpec representing the possible presence statuses
536  * from a connection to the given \a protocol.
537  *
538  * \param protocol Name of the protocol to look for.
539  * \return A list of PresenceSpec representing the possible presence statuses
540  *         from a connection to the given \a protocol or an empty list
541  *         if the protocol is not defined.
542  */
allowedPresenceStatuses(const QString & protocol) const543 PresenceSpecList ManagerFile::allowedPresenceStatuses(const QString &protocol) const
544 {
545     return mPriv->protocolsMap.value(protocol).statuses;
546 }
547 
548 /**
549  * Return the requirements (size limits, supported MIME types, etc)
550  * for avatars used on the given \a protocol.
551  *
552  * \param protocol Name of the protocol to look for.
553  * \return The requirements for avatars used on the given \a protocol or an invalid
554  *         AvatarSpec if the protocol is not defined.
555  */
avatarRequirements(const QString & protocol) const556 AvatarSpec ManagerFile::avatarRequirements(const QString &protocol) const
557 {
558     return mPriv->protocolsMap.value(protocol).avatarRequirements;
559 }
560 
561 } // Tp
562