Commit 20e8d7b3 authored by Hugo Lefeuvre's avatar Hugo Lefeuvre Committed by Sébastien Blin

contactmodel: fix broken uri support in searchContact

We also make smartlist output more verbose in case of lookup failure.

Also, this patch does a major cleanup in the URI class, applying
new LRC style and improving RFC support.

Change-Id: Ia82f9af75b4a685df7a895b335af9f55fa2cb355
Gitlab: #384Reviewed-by: Sébastien Blin's avatarSebastien Blin <sebastien.blin@savoirfairelinux.com>
parent 910db9b7
......@@ -1395,6 +1395,7 @@ QVariant Account::roleData(int role) const
bool Account::supportScheme( URI::SchemeType type ) const
{
switch(type) {
case URI::SchemeType::UNRECOGNIZED :
case URI::SchemeType::NONE :
if (protocol() == Account::Protocol::RING)
/* the URIs which are supported by accounts of type RING are well
......
......@@ -163,7 +163,6 @@ Q_SIGNALS:
private:
std::unique_ptr<ContactModelPimpl> pimpl_;
};
} // namespace api
......
......@@ -114,6 +114,7 @@ Account* AvailableAccountModel::currentDefaultAccount(ContactMethod* method)
case URI::ProtocolHint::IP:
type = URI::SchemeType::SIP;
break;
case URI::ProtocolHint::UNRECOGNIZED:
case URI::ProtocolHint::RING:
case URI::ProtocolHint::RING_USERNAME:
type = URI::SchemeType::RING;
......
......@@ -689,6 +689,7 @@ QString ContactMethod::toHash() const
//There is no point in keeping the full URI, a Ring hash is unique
uristr = uri().userinfo();
break;
case URI::ProtocolHint::UNRECOGNIZED:
case URI::ProtocolHint::RING_USERNAME:
case URI::ProtocolHint::SIP_OTHER:
case URI::ProtocolHint::IP :
......@@ -866,6 +867,10 @@ bool ContactMethod::isReachable() const
if (hasRing)
return true;
break;
case URI::ProtocolHint::UNRECOGNIZED:
if (hasRing || hasSip)
return true;
break;
}
return false;
......
......@@ -3,6 +3,7 @@
* Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com> *
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> *
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> *
* Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
......@@ -81,6 +82,15 @@ public:
* @param banned whether contact is banned or not
*/
void addToContacts(ContactMethod* cm, const profile::Type& type, bool banned = false);
/**
* Helpers for searchContact. Search for a given RING or SIP contact.
*/
void searchRingContact(const std::string& query);
void searchSipContact(const std::string& query);
/**
* Update temporary item to display a given message about a given uri.
*/
void updateTemporaryMessage(const std::string& mes, const std::string& uri);
// Helpers
const BehaviorController& behaviorController;
......@@ -323,72 +333,75 @@ ContactModel::getContactProfileId(const std::string& contactUri) const
void
ContactModel::searchContact(const std::string& query)
{
auto& temporaryContact = pimpl_->contacts[""];
temporaryContact = {}; // reset in any case
// always reset temporary contact
pimpl_->contacts[""] = {};
auto uri = URI(QString(query.c_str()));
if (owner.profileInfo.type == profile::Type::SIP) {
// We don't need to search anything for SIP contacts.
// NOTE: there is no registeredName for SIP contacts
std::string uriID = uri.format(URI::Section::USER_INFO | URI::Section::HOSTNAME | URI::Section::PORT).toStdString();
// Reset temporary if contact exists, else save the query inside it
{
std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
auto iter = pimpl_->contacts.find(query);
if (iter == pimpl_->contacts.end()) {
profile::Info profileInfo;
profileInfo.uri = query;
profileInfo.alias = query;
profileInfo.type = profile::Type::TEMPORARY;
temporaryContact.profileInfo = profileInfo;
}
}
emit modelUpdated(query);
} else if (uri.full().startsWith("ring:")) {
auto updated = false;
// Reset temporary if contact exists, else save the query inside it
{
std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
auto shortUri = uri.full().mid(5).toStdString();
auto iter = pimpl_->contacts.begin();
while (iter != pimpl_->contacts.end()) {
if (iter->first == shortUri || iter->second.registeredName == shortUri) {
break;
}
++iter;
}
if (iter == pimpl_->contacts.end()) {
// query is a valid RingID?
profile::Info profileInfo;
profileInfo.uri = shortUri;
profileInfo.alias = shortUri;
profileInfo.type = profile::Type::TEMPORARY;
temporaryContact.profileInfo = profileInfo;
updated = true;
}
auto uriScheme = uri.schemeType();
if (uri.schemeType() == URI::SchemeType::NONE) {
// uri has no scheme, default to current account scheme
if (owner.profileInfo.type == profile::Type::SIP) {
uriScheme = URI::SchemeType::SIP;
} else if (owner.profileInfo.type == profile::Type::RING) {
uriScheme = URI::SchemeType::RING;
}
if (updated)
emit modelUpdated(query);
}
if (uriScheme == URI::SchemeType::SIP && owner.profileInfo.type == profile::Type::SIP) {
pimpl_->searchSipContact(uriID);
} else if (uriScheme == URI::SchemeType::RING && owner.profileInfo.type == profile::Type::RING) {
pimpl_->searchRingContact(uriID);
} else {
// Default searching
profile::Info profileInfo;
profileInfo.alias = "Searching…";
profileInfo.type = profile::Type::TEMPORARY;
temporaryContact.profileInfo = profileInfo;
temporaryContact.registeredName = query;
emit modelUpdated(query);
// Query Name Server
if (auto* account = AccountModel::instance().getById(owner.id.c_str())) {
if (not account->lookupName(QString(query.c_str()))) {
profileInfo.alias = "No reference of " + query + " found";
}
emit modelUpdated(query);
}
pimpl_->updateTemporaryMessage(tr("Bad URI scheme").toStdString(), uri.full().toStdString());
}
}
void
ContactModelPimpl::updateTemporaryMessage(const std::string& mes, const std::string& uri)
{
std::lock_guard<std::mutex> lk(contactsMtx_);
auto& temporaryContact = contacts[""];
temporaryContact.profileInfo.alias = mes;
temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
temporaryContact.registeredName = uri;
}
void
ContactModelPimpl::searchRingContact(const std::string& query)
{
if (query.empty()) {
return;
}
updateTemporaryMessage(tr("Searching…").toStdString(), query);
// Default searching
if (auto* account = AccountModel::instance().getById(linked.owner.id.c_str())) {
account->lookupName(QString(query.c_str()));
}
}
void
ContactModelPimpl::searchSipContact(const std::string& query)
{
if (query.empty()) {
return;
}
auto& temporaryContact = contacts[""];
{
std::lock_guard<std::mutex> lk(contactsMtx_);
if (contacts.find(query) == contacts.end()) {
temporaryContact.profileInfo.uri = query;
temporaryContact.profileInfo.alias = query;
temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
}
}
emit linked.modelUpdated(query);
}
uint64_t
......@@ -697,36 +710,42 @@ ContactModelPimpl::slotRegisteredNameFound(const std::string& accountId,
auto& temporaryContact = contacts[""];
if (status == 0 /* SUCCESS */) {
{
std::lock_guard<std::mutex> lk(contactsMtx_);
if (contacts.find(uri) == contacts.end()) {
// contact not present, update the temporaryContact
lrc::api::profile::Info profileInfo = {uri, "", "", profile::Type::TEMPORARY};
temporaryContact = {profileInfo, registeredName, false, false};
} else {
// Update contact
contacts[uri].registeredName = registeredName;
if (temporaryContact.registeredName == uri || temporaryContact.registeredName == registeredName) {
// contact already present, remove the temporaryContact
lrc::api::profile::Info profileInfo = {"", "", "", profile::Type::TEMPORARY};
temporaryContact = {profileInfo, "", false, false};
}
std::lock_guard<std::mutex> lk(contactsMtx_);
if (contacts.find(uri) != contacts.end()) {
// update contact and remove temporary item
contacts[uri].registeredName = registeredName;
temporaryContact = {};
} else {
if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) {
// we are notified that a previous lookup ended
return;
}
// update temporary item
lrc::api::profile::Info profileInfo = {uri, "", "", profile::Type::TEMPORARY};
temporaryContact = {profileInfo, registeredName, false, false};
}
emit linked.modelUpdated(uri);
} else if (!uri.empty() || !registeredName.empty()) {
} else {
if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) {
// we are notified that a previous lookup ended
return;
}
{
std::lock_guard<std::mutex> lk(contactsMtx_);
temporaryContact.registeredName = registeredName;
temporaryContact.profileInfo.alias = "Not found";
switch (status) {
case 1 /* INVALID */:
updateTemporaryMessage(tr("Invalid ringID").toStdString(), registeredName);
break;
case 2 /* NOT FOUND */:
updateTemporaryMessage(tr("Not found").toStdString(), registeredName);
break;
case 3 /* ERROR */:
updateTemporaryMessage(tr("Couldn't lookup…").toStdString(), registeredName);
break;
}
emit linked.modelUpdated(uri);
} else {
qDebug() << "ContactModelPimpl::slotRegisteredNameFound, status = " << status << " with empty uri and registeredName";
}
emit linked.modelUpdated(uri);
}
void
......
......@@ -295,10 +295,14 @@ ConversationModel::allFilteredConversations() const
auto filter = pimpl_->filter;
auto uri = URI(QString(filter.c_str()));
if (uri.full().startsWith("ring:")) {
filter = uri.full().mid(5).toStdString();;
bool stripScheme = (uri.schemeType() == URI::SchemeType::NONE) || (uri.schemeType() == URI::SchemeType::RING);
FlagPack<URI::Section> flags = URI::Section::USER_INFO | URI::Section::HOSTNAME | URI::Section::PORT;
if (!stripScheme) {
flags |= URI::Section::SCHEME;
}
filter = uri.format(flags).toStdString();
/* Check contact */
// If contact is banned, only match if filter is a perfect match
if (contactInfo.isBanned) {
......
This diff is collapsed.
/****************************************************************************
* Copyright (C) 2014-2018 Savoir-faire Linux *
* Copyright (C) 2014-2018 Savoir-faire Linux *
* Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
* Author : Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
......@@ -19,182 +20,177 @@
#include "typedefs.h"
#include <memory>
#include <QStringList>
class URIPrivate;
class URIPimpl;
class QDataStream;
/**
* @class URI A specialized string with multiple attributes
*
* Most of LibRingClient handle uri as strings, but more
* advanced algorithms need to access the various sections.
* This class implement a centralized and progressive URI
* parser to avoid having custom implementation peppered
* everywhere. This class doesn't attempt to produce perfect
* output. It has multiple tradeoff to be faster when
* accuracy has little value in the context of LibRingClient.
*
* Here is some example of common numbers/URIs:
* * 123
* * 123@192.168.123.123
* * 123@asterisk-server
* * <sip:123@192.168.123.123>
* * <sips:123@192.168.123.123>
* * <sips:888@192.168.48.213;transport=TLS>
* * <sip:c8oqz84zk7z@privacy.org>;tag=hyh8
* * 1 800 123-4567
* * 18001234567
*
* @ref http://tools.ietf.org/html/rfc5456#page-8
* @ref http://tools.ietf.org/html/rfc3986
* @ref http://tools.ietf.org/html/rfc3261
* @ref http://tools.ietf.org/html/rfc5630
*
* <code>
* From the RFC:
* foo://example.com:8042/over/there?name=ferret#nose
* \_/ \______________/\_________/ \_________/ \__/
* | | | | |
* scheme authority path query fragment
* | _____________________|__
* / \ / \
* urn:example:animal:ferret:nose
*
* authority = [ userinfo "@" ] host [ ":" port ]
* </code>
*
* "For example, the semicolon (";") and equals ("=") reserved characters are
* often used to delimit parameters and parameter values applicable to
* that segment. The comma (",") reserved character is often used for
* similar purposes. For example, one URI producer might use a segment
* such as "name;v=1.1" to indicate a reference to version 1.1 of
* "name", whereas another might use a segment such as "name,1.1" to
* indicate the same. "
*/
* @class URI A specialized string with multiple attributes
*
* Most of LibRingClient handle uri as strings, but more
* advanced algorithms need to access the various sections.
* This class implement a centralized and progressive URI
* parser to avoid having custom implementation peppered
* everywhere. This class doesn't attempt to produce perfect
* output. It has multiple tradeoff to be faster when
* accuracy has little value in the context of LibRingClient.
*
* Here is some example of common numbers/URIs:
* * 123
* * 123@192.168.123.123
* * 123@asterisk-server
* * <sip:123@192.168.123.123>
* * <sips:123@192.168.123.123>
* * <sips:888@192.168.48.213;transport=TLS>
* * <sip:c8oqz84zk7z@privacy.org>;tag=hyh8
* * 1 800 123-4567
* * 18001234567
*
* @ref http://tools.ietf.org/html/rfc5456#page-8
* @ref http://tools.ietf.org/html/rfc3986
* @ref http://tools.ietf.org/html/rfc3261
* @ref http://tools.ietf.org/html/rfc5630
*
* <code>
* From the RFC:
* foo://example.com:8042/over/there?name=ferret#nose
* \_/ \______________/\_________/ \_________/ \__/
* | | | | |
* scheme authority path query fragment
* | _____________________|__
* / \ / \
* urn:example:animal:ferret:nose
*
* authority = [ userinfo "@" ] host [ ":" port ]
* </code>
*
* "For example, the semicolon (";") and equals ("=") reserved characters are
* often used to delimit parameters and parameter values applicable to
* that segment. The comma (",") reserved character is often used for
* similar purposes. For example, one URI producer might use a segment
* such as "name;v=1.1" to indicate a reference to version 1.1 of
* "name", whereas another might use a segment such as "name,1.1" to
* indicate the same. "
*/
class LIB_EXPORT URI : public QString
{
friend class URIPrivate;
public:
///Default constructor
URI();
/**
* Default copy constructor
* @param other an URI string
*/
URI(const URI& other);
URI(const QString& other);
virtual ~URI();
///@enum SchemeType The very first part of the URI followed by a ':'
enum class SchemeType {
NONE , //Implicit SIP, use account type as reference
SIP ,
SIPS ,
RING ,
COUNT__
};
Q_ENUMS(URI::SchemeType)
/**
* @enum Transport each known valid transport types
* Defined at http://tools.ietf.org/html/rfc3261#page-222
*/
enum class Transport {
NOT_SET, /*!< The transport have not been set directly in the URI */
TLS , /*!< Encrypted calls (capital) */
tls , /*!< Encrypted calls */
TCP , /*!< TCP (the default) (capital) */
tcp , /*!< TCP (the default) */
UDP , /*!< Without a connection (capital) */
udp , /*!< Without a connection */
SCTP , /*!< */
sctp , /*!< */
DTLS , /*!< */
dtls , /*!< */
COUNT__
};
Q_ENUMS(URI::Transport)
/**
* @enum Section flags associated with each logical sections of the URI
*
* Those sections can be packed into a block to be used to define the
* expected URI syntax
*
*/
enum class Section {
CHEVRONS = 0x1 << 0, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
URI();
URI(const URI& other);
URI(const QString& other);
virtual ~URI();
// @enum SchemeType The very first part of the URI followed by a ':'
enum class SchemeType {
SIP ,
SIPS ,
RING ,
NONE ,
COUNT__,
UNRECOGNIZED
};
Q_ENUMS(URI::SchemeType)
/**
* @enum Transport each known valid transport types
* Defined at http://tools.ietf.org/html/rfc3261#page-222
*/
enum class Transport {
NOT_SET, /*!< The transport have not been set directly in the URI */
TLS , /*!< Encrypted calls (capital) */
tls , /*!< Encrypted calls */
TCP , /*!< TCP (the default) (capital) */
tcp , /*!< TCP (the default) */
UDP , /*!< Without a connection (capital) */
udp , /*!< Without a connection */
SCTP , /*!< */
sctp , /*!< */
DTLS , /*!< */
dtls , /*!< */
COUNT__
};
Q_ENUMS(URI::Transport)
/**
* @enum Section flags associated with each logical sections of the URI
*
* Those sections can be packed into a block to be used to define the
* expected URI syntax
*
*/
enum class Section {
CHEVRONS = 0x1 << 0, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\_/ \_/
|_________________Chevrons_______________________|
</code>*/
SCHEME = 0x1 << 1, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\___/
|______Scheme|</code> */
USER_INFO = 0x1 << 2, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\___/
|_________Userinfo</code> */
HOSTNAME = 0x1 << 3, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\______________/
|_________Hostname</code> */
PORT = 0x1 << 4, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\____/
|_____Port</code> */
TRANSPORT = 0x1 << 5, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\_____________/
Transport________|</code> */
TAG = 0x1 << 6, /*!< <code><sips:888@192.168.48.213:5060;tag=b5c73d9ef>
\_____________/
Tag_________|</code> */
};
/**
* @enum ProtocolHint Expanded version of Account::Protocol
*
* This is used to make better choice when it come to choose an account or
* guess if the URI can be used with the current set et configured accounts.
*
* @warning This is an approximation. Those values are guessed using partial
* parsing (for performance) and are not definitive.
*/
enum class ProtocolHint {
SIP_OTHER = 0, /*!< Anything non empty that doesn't fit in other categories */
RING = 1, /*!< Start with "ring:" and 45 ASCII chars OR 40 ASCII chars */
IP = 2, /*!< Match an IPv4 address */
SIP_HOST = 3, /*!< Has an @ and no "ring:" prefix */
RING_USERNAME = 4, /*!< Anything that starts with "ring:" and isn't followed by 40 ASCII chars */
};
Q_ENUMS(URI::ProtocolHint)
//Getter
QString hostname () const;
QString userinfo () const;
bool hasHostname () const;
bool hasPort () const;
int port () const;
SchemeType schemeType () const;
ProtocolHint protocolHint() const;
//Setter
void setSchemeType(SchemeType t);
//Converter
QString format(FlagPack<URI::Section> sections) const;
/**
* Helper function which returns a QString containing a uri formatted to include at minimum the
* SCHEME and USER_INFO, and also the HOSTNAME and PORT, if available.
*/
QString full() const;
URI& operator=(const URI&);
|_________________Chevrons_______________________|
</code>*/
SCHEME = 0x1 << 1, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\___/
|______Scheme|</code> */
USER_INFO = 0x1 << 2, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\___/
|_________Userinfo</code> */
HOSTNAME = 0x1 << 3, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\______________/
|_________Hostname</code> */
PORT = 0x1 << 4, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\____/
|_____Port</code> */
TRANSPORT = 0x1 << 5, /*!< <code><sips:888@192.168.48.213:5060;transport=TLS>
\_____________/
Transport________|</code> */
TAG = 0x1 << 6, /*!< <code><sips:888@192.168.48.213:5060;tag=b5c73d9ef>
\_____________/
Tag_________|</code> */
};
/**
* @enum ProtocolHint Expanded version of Account::Protocol
*
* This is used to make better choice when it come to choose an account or
* guess if the URI can be used with the current set et configured accounts.
*
* @warning This is an approximation. Those values are guessed using partial
* parsing (for performance) and are not definitive.
*/
enum class ProtocolHint {
RING, /* Start with "ring:" and 45 ASCII chars OR 40 ASCII chars */
IP, /* Match an IPv4 address */
SIP_HOST, /* Start with "sip:", has an @ and no "ring:" prefix */
SIP_OTHER, /* Start with "sip:" and doesn't fit in other categories */
RING_USERNAME, /* Anything that starts with "ring:" and isn't followed by 40 ASCII chars */
UNRECOGNIZED /* Anything that doesn't fit in other categories */
};
Q_ENUMS(URI::ProtocolHint)
// Getter
QString hostname() const;
QString userinfo() const;
bool hasHostname() const;
bool hasPort() const;
int port() const;
SchemeType schemeType() const;
ProtocolHint protocolHint() const;
// Setter
void setSchemeType(SchemeType t);
// Converter
QString format(FlagPack<URI::Section> sections) const;