From 7a7b52efde8e737d1af269bebc9fae4bd5e5b0fc Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> Date: Wed, 8 Apr 2015 17:44:06 -0400 Subject: [PATCH] ContactMethod: Generate a protocol hint Refs #70363 --- src/account.cpp | 5 ++ src/accountmodel.cpp | 2 +- src/accountmodel.h | 4 +- src/contactmethod.cpp | 8 ++ src/contactmethod.h | 42 ++++----- src/person.cpp | 38 ++++++++ src/person.h | 1 + src/uri.cpp | 204 +++++++++++++++++++++++++++++++++++++----- src/uri.h | 57 +++++++----- 9 files changed, 298 insertions(+), 63 deletions(-) diff --git a/src/account.cpp b/src/account.cpp index 41b46039..ed4675f9 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -939,16 +939,21 @@ bool Account::supportScheme( URI::SchemeType type ) switch(type) { case URI::SchemeType::NONE : return true; + break; case URI::SchemeType::SIP : case URI::SchemeType::SIPS : if (protocol() == Account::Protocol::SIP) return true; + break; case URI::SchemeType::IAX : + case URI::SchemeType::IAX2 : if (protocol() == Account::Protocol::IAX) return true; + break; case URI::SchemeType::RING : if (protocol() == Account::Protocol::RING) return true; + break; } return false; } diff --git a/src/accountmodel.cpp b/src/accountmodel.cpp index 220e440b..d9ec60d1 100644 --- a/src/accountmodel.cpp +++ b/src/accountmodel.cpp @@ -612,7 +612,7 @@ bool AccountModel::isSipSupported() const return d_ptr->m_lSupportedProtocols[Account::Protocol::SIP]; } -bool AccountModel::isAixSupported() const +bool AccountModel::isIAXSupported() const { return d_ptr->m_lSupportedProtocols[Account::Protocol::IAX]; } diff --git a/src/accountmodel.h b/src/accountmodel.h index be5011b9..e6f51857 100644 --- a/src/accountmodel.h +++ b/src/accountmodel.h @@ -43,7 +43,7 @@ public: Q_PROPERTY(bool presenceSubscribeSupported READ isPresenceSubscribeSupported ) Q_PROPERTY(ProtocolModel* protocolModel READ protocolModel ) Q_PROPERTY(bool isSipSupported READ isSipSupported NOTIFY supportedProtocolsChanged) - Q_PROPERTY(bool isAixSupported READ isAixSupported NOTIFY supportedProtocolsChanged) + Q_PROPERTY(bool isIAXSupported READ isIAXSupported NOTIFY supportedProtocolsChanged) Q_PROPERTY(bool isIP2IPSupported READ isIP2IPSupported NOTIFY supportedProtocolsChanged) Q_PROPERTY(bool isRingSupported READ isRingSupported NOTIFY supportedProtocolsChanged) @@ -66,7 +66,7 @@ public: bool isPresenceSubscribeSupported( ) const; ProtocolModel* protocolModel ( ) const; bool isSipSupported ( ) const; - bool isAixSupported ( ) const; + bool isIAXSupported ( ) const; bool isIP2IPSupported ( ) const; bool isRingSupported ( ) const; diff --git a/src/contactmethod.cpp b/src/contactmethod.cpp index afc47a29..429e01af 100644 --- a/src/contactmethod.cpp +++ b/src/contactmethod.cpp @@ -414,6 +414,12 @@ QString ContactMethod::uid() const return d_ptr->m_Uid.isEmpty()?toHash():d_ptr->m_Uid; } +///Return the URI protocol hint +URI::ProtocolHint ContactMethod::protocolHint() const +{ + return d_ptr->m_Uri.protocolHint(); +} + ///Return all calls from this number QList<Call*> ContactMethod::calls() const { @@ -597,3 +603,5 @@ TemporaryContactMethod::TemporaryContactMethod(const ContactMethod* number) : setAccount(number->account()); } } + +Q_DECLARE_METATYPE(QList<Call*>) diff --git a/src/contactmethod.h b/src/contactmethod.h index 99eadd86..8c7833a4 100644 --- a/src/contactmethod.h +++ b/src/contactmethod.h @@ -54,26 +54,27 @@ public: }; //Properties - Q_PROPERTY(Account* account READ account WRITE setAccount ) - Q_PROPERTY(Person* person READ contact WRITE setPerson ) - Q_PROPERTY(int lastUsed READ lastUsed ) - Q_PROPERTY(QString uri READ uri ) - Q_PROPERTY(int callCount READ callCount ) - Q_PROPERTY(QList<Call*> calls READ calls ) - Q_PROPERTY(int popularityIndex READ popularityIndex ) - Q_PROPERTY(bool bookmarked READ isBookmarked ) - Q_PROPERTY(QString uid READ uid WRITE setUid ) - Q_PROPERTY(bool isTracked READ isTracked NOTIFY trackedChanged ) - Q_PROPERTY(bool isPresent READ isPresent NOTIFY presentChanged ) - Q_PROPERTY(bool supportPresence READ supportPresence ) - Q_PROPERTY(QString presenceMessage READ presenceMessage NOTIFY presenceMessageChanged ) - Q_PROPERTY(uint weekCount READ weekCount ) - Q_PROPERTY(uint trimCount READ trimCount ) - Q_PROPERTY(bool haveCalled READ haveCalled ) - Q_PROPERTY(QString primaryName READ primaryName ) - Q_PROPERTY(bool isBookmarked READ isBookmarked ) - Q_PROPERTY(QVariant icon READ icon ) - Q_PROPERTY(int totalSpentTime READ totalSpentTime ) + Q_PROPERTY(Account* account READ account WRITE setAccount ) + Q_PROPERTY(Person* person READ contact WRITE setPerson ) + Q_PROPERTY(int lastUsed READ lastUsed ) + Q_PROPERTY(QString uri READ uri ) + Q_PROPERTY(int callCount READ callCount ) + Q_PROPERTY(QList<Call*> calls READ calls ) + Q_PROPERTY(int popularityIndex READ popularityIndex ) + Q_PROPERTY(bool bookmarked READ isBookmarked ) + Q_PROPERTY(QString uid READ uid WRITE setUid ) + Q_PROPERTY(bool isTracked READ isTracked NOTIFY trackedChanged ) + Q_PROPERTY(bool isPresent READ isPresent NOTIFY presentChanged ) + Q_PROPERTY(bool supportPresence READ supportPresence ) + Q_PROPERTY(QString presenceMessage READ presenceMessage NOTIFY presenceMessageChanged ) + Q_PROPERTY(uint weekCount READ weekCount ) + Q_PROPERTY(uint trimCount READ trimCount ) + Q_PROPERTY(bool haveCalled READ haveCalled ) + Q_PROPERTY(QString primaryName READ primaryName ) + Q_PROPERTY(bool isBookmarked READ isBookmarked ) + Q_PROPERTY(QVariant icon READ icon ) + Q_PROPERTY(int totalSpentTime READ totalSpentTime ) + Q_PROPERTY(URI::ProtocolHint protocolHint READ protocolHint ) // Q_PROPERTY(QHash<QString,int> alternativeNames READ alternativeNames ) @@ -110,6 +111,7 @@ public: QVariant icon () const; int totalSpentTime () const; QString uid () const; + URI::ProtocolHint protocolHint () const; QVariant roleData(int role) const; diff --git a/src/person.cpp b/src/person.cpp index 521eb240..93152db8 100644 --- a/src/person.cpp +++ b/src/person.cpp @@ -25,6 +25,7 @@ //Ring library #include "contactmethod.h" +#include "accountmodel.h" #include "collectioninterface.h" #include "transitionalpersonbackend.h" #include "account.h" @@ -450,6 +451,43 @@ bool Person::supportPresence() const return false; } +///Return true if there is a change one if the account can be used to reach that person +bool Person::isReachable() const +{ + if (!d_ptr->m_Numbers.size()) + return false; + + AccountModel* m = AccountModel::instance(); + + const bool hasSip = m->isSipSupported (); + const bool hasIAX = m->isIAXSupported (); + const bool hasIP2IP = m->isIP2IPSupported(); + const bool hasRing = m->isRingSupported (); + + for (const ContactMethod* n : d_ptr->m_Numbers) { + switch (n->protocolHint()) { + case URI::ProtocolHint::SIP_HOST : + case URI::ProtocolHint::IP : + if (hasIP2IP) + return true; + //no break + case URI::ProtocolHint::SIP_OTHER: + if (hasSip) + return true; + break; + case URI::ProtocolHint::IAX : + if (hasIAX) + return true; + break; + case URI::ProtocolHint::RING : + if (hasRing) + return true; + break; + } + } + return false; +} + ///Recomputing the filter string is heavy, cache it QString Person::filterString() const { diff --git a/src/person.h b/src/person.h index 6642b351..e5f3816d 100644 --- a/src/person.h +++ b/src/person.h @@ -144,6 +144,7 @@ public: bool isPresent () const; bool isTracked () const; bool supportPresence () const; + bool isReachable () const; //Setters void setContactMethods ( ContactMethods ); diff --git a/src/uri.cpp b/src/uri.cpp index b745bd63..a3cf025e 100644 --- a/src/uri.cpp +++ b/src/uri.cpp @@ -17,30 +17,45 @@ ***************************************************************************/ #include "uri.h" -constexpr const char* URI::schemeNames[]; class URIPrivate { public: + ///Strings associated with SchemeType + constexpr static const char* schemeNames[] = { + /*NONE = */ "" , + /*SIP = */ "sip:" , + /*SIPS = */ "sips:", + /*IAX = */ "iax:" , + /*IAX2 = */ "iax2:", + /*RING = */ "ring:", + }; + URIPrivate(QString* uri); //Attributes - QString m_Hostname ; - QString m_Userinfo ; - QStringList m_lAttributes ; - QString m_Stripped ; - URI::SchemeType m_HeaderType ; - bool m_hasChevrons ; - bool m_Parsed ; + QString m_Hostname ; + QString m_Userinfo ; + QStringList m_lAttributes ; + QString m_Stripped ; + URI::SchemeType m_HeaderType ; + bool m_hasChevrons ; + bool m_Parsed ; + bool m_HasAt ; + URI::ProtocolHint m_ProtocolHint; + bool m_HintParsed ; //Helper static QString strip(const QString& uri, URI::SchemeType& scheme); void parse(); + static bool checkIp(const QString& str, bool &isHash); private: QString* q_ptr; }; +constexpr const char* URIPrivate::schemeNames[]; + URIPrivate::URIPrivate(QString* uri) : m_Parsed(false),m_HeaderType(URI::SchemeType::NONE),q_ptr(uri), -m_hasChevrons(false) +m_hasChevrons(false),m_HasAt(false),m_ProtocolHint(URI::ProtocolHint::SIP_OTHER),m_HintParsed(false) { } @@ -89,26 +104,50 @@ QString URIPrivate::strip(const QString& uri, URI::SchemeType& scheme) { if (uri.isEmpty()) return QString(); - int start(0),end(uri.size()-1); //Other type of comparisons were too slow - if (end > 5 && uri[0] == '<' ) { - if (uri[4] == ':') { - scheme = uri[1] == 's'?URI::SchemeType::SIP : URI::SchemeType::IAX; - start = 5; + + int start(uri[0] == '<'?1:0),end(uri.size()-1); //Other type of comparisons were too slow + uchar c; + + //Assume the scheme is either iax, sip or ring using the first letter and length, this + //is dangerous and can cause undefined behaviour that will cause the call to fail + //later on, but this is not really a problem for now + if (end > start+3 && (c = uri[start+3].toLatin1()) == ':') { + switch (c) { + case 'i': + scheme = URI::SchemeType::IAX; + break; + case 's': + scheme = URI::SchemeType::SIP; + break; } - else if (uri[5] == ':') { - scheme = URI::SchemeType::SIPS; - start = 6; + start = 5; + } + else if (end > start+4 && (c = uri[start+4].toLatin1()) == ':') { + switch (c) { + case 'i': + scheme = URI::SchemeType::IAX2; + break; + case 'r': + scheme = URI::SchemeType::RING; + break; + case 's': + scheme = URI::SchemeType::SIPS; + break; } + start = 6; } + if (end && uri[end] == '>') end--; else if (start) { //TODO there may be a ';' section with arguments, check } + return uri.mid(start,end-start+1); } -/**Return the domaine of an URI +/** + * Return the domaine of an URI * * For example, example.com in <sip:12345@example.com> */ @@ -141,14 +180,117 @@ URI::SchemeType URI::schemeType() const return d_ptr->m_HeaderType; } +/** + * "Fast" Ipv4 and Ipv6 check, accept 999.999.999.999, :::::::::FF and other + * atrocities, but at least perform a O(N) ish check and validate the hash + * + * @param str an uservalue (faster the scheme and before the "at" sign) + * @param [out] isHash if the content is pure hexadecimal ASCII + */ +bool URIPrivate::checkIp(const QString& str, bool &isHash) +{ + char* raw = str.toLatin1().data(); + ushort max = str.size(); + + if (max < 3 || max > 45) + return false; + + uchar dc(0),sc(0),i(0),d(0),hx(1); + + while (i < max) { + switch(raw[i]) { + case '.': + isHash = false; + d = 0; + dc++; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (++d > 3 && dc) + return false; + break; + case ':': + isHash = false; + sc++; + //No break + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + hx = 0; + break; + default: + return false; + }; + i++; + } + return (hx && dc == 3 && d < 4) ^ (~(sc < 2 || dc)); +} + +/** + * This method return an hint to guess the protocol that could be used to call + * this URI. It is a quick guess, not something that should be trusted + * + * @warning, this method is O(N) when called for the first time on an URI + */ +URI::ProtocolHint URI::protocolHint() const +{ + if (!d_ptr->m_Parsed) + const_cast<URI*>(this)->d_ptr->parse(); + + if (!d_ptr->m_HintParsed) { + bool isHash = d_ptr->m_Userinfo.size() == 40; + d_ptr->m_ProtocolHint = \ + ( + //Step one : Check IAX protocol, is has already been detected at this point + d_ptr->m_HeaderType == URI::SchemeType::IAX2 || d_ptr->m_HeaderType == URI::SchemeType::IAX + ? URI::ProtocolHint::IAX + + : ( + //Step two : check IP + URIPrivate::checkIp(d_ptr->m_Userinfo,isHash) ? URI::ProtocolHint::IP + + : ( + //Step three : Check RING protocol, is has already been detected at this point + d_ptr->m_HeaderType == URI::SchemeType::RING && isHash ? URI::ProtocolHint::RING + + : ( + //Step four : Differentiate between ***@*** and *** type URIs + d_ptr->m_HasAt ? URI::ProtocolHint::SIP_HOST : URI::ProtocolHint::SIP_OTHER + + )))); + + d_ptr->m_HintParsed = true; + qDebug() << "ICI" << (int)d_ptr->m_ProtocolHint; + } + return d_ptr->m_ProtocolHint; +} + ///Keep a cache of the values to avoid re-parsing them void URIPrivate::parse() { - //TODO DHT hashes have 40 chars or 45 with sips:/ring: is set + //FIXME the indexOf is done twice, the second time could be avoided if (q_ptr->indexOf('@') != -1) { const QStringList split = q_ptr->split('@'); m_Hostname = split[1];//split[1].left(split[1].size()) m_Userinfo = split[0]; + m_HasAt = split.size(); m_Parsed = true; } } @@ -171,6 +313,28 @@ QString URI::userinfo() const QString URI::fullUri() const { return QString("<%1%2>") - .arg(schemeNames[static_cast<int>(d_ptr->m_HeaderType == SchemeType::NONE?SchemeType::SIP:d_ptr->m_HeaderType)]) + .arg(URIPrivate::schemeNames[static_cast<int>(d_ptr->m_HeaderType == SchemeType::NONE?SchemeType::SIP:d_ptr->m_HeaderType)]) .arg(*this); } + +QDataStream& operator<<( QDataStream& stream, const URI::ProtocolHint& ph ) +{ + switch(ph) { + case URI::ProtocolHint::SIP_OTHER: + stream << "SIP_OTHER"; + break; + case URI::ProtocolHint::IAX : + stream << "IAX"; + break; + case URI::ProtocolHint::RING : + stream << "RING"; + break; + case URI::ProtocolHint::IP : + stream << "IP"; + break; + case URI::ProtocolHint::SIP_HOST : + stream << "SIP_HOST"; + break; + } + return stream; +} diff --git a/src/uri.h b/src/uri.h index f3353b47..99c219e6 100644 --- a/src/uri.h +++ b/src/uri.h @@ -23,13 +23,14 @@ #include <QStringList> class URIPrivate; +class QDataStream; /** * @class URI A specialized string with multiple attributes - * - * Most of Ring-KDE handle uri as strings, but more + * + * Most of LibRingClient handle uri as strings, but more * advanced algorithms need to access the various sections. - * + * * Here is some example of common numbers/URIs: * * 123 * * 123@192.168.123.123 @@ -43,12 +44,12 @@ class URIPrivate; * * iax:example.com/alice * * iax:johnQ@example.com/12022561414 * * iax:example.com:4570/alice?friends - * + * * @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 - * + * * From the RFC: * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ @@ -85,17 +86,10 @@ public: SIP , SIPS , IAX , + IAX2 , RING , }; - - ///Strings associated with SchemeType - constexpr static const char* schemeNames[] = { - /*NONE = */ "" , - /*SIP = */ "sip:" , - /*SIPS = */ "sips:", - /*IAX = */ "iax:" , - /*RING = */ "ring:", - }; + Q_ENUMS(URI::SchemeType) /** * @enum Transport each known valid transport types @@ -112,18 +106,41 @@ public: SCTP , /*!< */ sctp , /*!< */ }; + Q_ENUMS(URI::Transport) + + /** + * @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 */ + IAX = 1, /*!< Start with "iax:" or "iax2:" */ + RING = 2, /*!< Start with "ring:" and has 45 ASCII characters */ + IP = 3, /*!< Match an IPv4 address */ + SIP_HOST = 4, /*!< Has an @ and no "ring:" prefix */ + }; + Q_ENUMS(URI::ProtocolHint) - QString hostname () const; - QString fullUri () const; - QString userinfo () const; - bool hasHostname () const; - SchemeType schemeType () const; + QString hostname () const; + QString fullUri () const; + QString userinfo () const; + bool hasHostname () const; + SchemeType schemeType () const; + ProtocolHint protocolHint() const; URI& operator=(const URI&); private: const QScopedPointer<URIPrivate> d_ptr; }; -// Q_DECLARE_METATYPE(URI*) + +Q_DECLARE_METATYPE(URI::ProtocolHint) + +QDataStream& operator<< ( QDataStream& stream, const URI::ProtocolHint& ph ); #endif //URI_H -- GitLab