From 20e8d7b341c25a724f4494f23d4d8e7fe8a97732 Mon Sep 17 00:00:00 2001
From: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
Date: Thu, 30 Aug 2018 16:48:34 -0400
Subject: [PATCH] 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: #384
Reviewed-by: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
---
 src/account.cpp               |   1 +
 src/api/contactmodel.h        |   1 -
 src/availableaccountmodel.cpp |   1 +
 src/contactmethod.cpp         |   5 +
 src/contactmodel.cpp          | 181 ++++----
 src/conversationmodel.cpp     |   8 +-
 src/uri.cpp                   | 760 +++++++++++++++-------------------
 src/uri.h                     | 326 +++++++--------
 8 files changed, 611 insertions(+), 672 deletions(-)

diff --git a/src/account.cpp b/src/account.cpp
index 242508a3..fc371a47 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -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
diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h
index ed75f1e7..2ff58edb 100644
--- a/src/api/contactmodel.h
+++ b/src/api/contactmodel.h
@@ -163,7 +163,6 @@ Q_SIGNALS:
 
 private:
     std::unique_ptr<ContactModelPimpl> pimpl_;
-
 };
 
 } // namespace api
diff --git a/src/availableaccountmodel.cpp b/src/availableaccountmodel.cpp
index a0b3aa1b..a802d8c6 100644
--- a/src/availableaccountmodel.cpp
+++ b/src/availableaccountmodel.cpp
@@ -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;
diff --git a/src/contactmethod.cpp b/src/contactmethod.cpp
index b3a55399..5d0701c7 100644
--- a/src/contactmethod.cpp
+++ b/src/contactmethod.cpp
@@ -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;
diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp
index 041e42c8..a279a75e 100644
--- a/src/contactmodel.cpp
+++ b/src/contactmodel.cpp
@@ -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
diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp
index 2dee502b..81487395 100644
--- a/src/conversationmodel.cpp
+++ b/src/conversationmodel.cpp
@@ -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) {
diff --git a/src/uri.cpp b/src/uri.cpp
index 40d4f3ee..6d217c60 100644
--- a/src/uri.cpp
+++ b/src/uri.cpp
@@ -1,6 +1,7 @@
 /****************************************************************************
- *   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             *
@@ -15,216 +16,184 @@
  *   You should have received a copy of the GNU General Public License      *
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
  ***************************************************************************/
-#include "uri.h"
-
-#include "private/matrixutils.h"
 
-#include <QRegularExpression>
+#include "uri.h"
+#include <regex>
 
-class URIPrivate
+class URIPimpl
 {
 public:
-   ///Strings associated with SchemeType
-   static const Matrix1D<URI::SchemeType, const char*> schemeNames;
-
-   ///String associated with the transport name
-   static const Matrix1D<URI::Transport, const char*> transportNames;
-
-   static const QString whitespaceCharClass;
-
-   static const QRegularExpression startWhitespaceMatcher;
-   static const QRegularExpression endWhitespaceMatcher;
-
-   ///Attributes names
-   struct Constants {
-      constexpr static const char TRANSPORT[] = "transport";
-      constexpr static const char TAG      [] = "tag"      ;
-   };
-
-   //Constructor
-   URIPrivate(URI* uri);
-   void commonCopyConstructor(const URI& o);
-
-   //Attributes
-   QString           m_ExtHostname ;
-   QString           m_Userinfo    ;
-   QStringList       m_lAttributes ;
-   QString           m_Stripped    ;
-   QString           m_Hostname2   ;
-   QByteArray        m_Tag         ;
-   URI::SchemeType   m_HeaderType  ;
-   URI::Transport    m_Transport   ;
-   bool              m_hasChevrons ;
-   bool              m_Parsed      ;
-   bool              m_HasAt       ;
-   URI::ProtocolHint m_ProtocolHint;
-   bool              m_HintParsed  ;
-   bool              m_IsHNParsed  ;
-   int               m_Port        ;
-
-   //Helper
-   static QString strip(const QString& uri, URI::SchemeType& scheme);
-   void parse();
-   void parseHostname();
-   static bool checkIp(const QString& str, bool &isHash, const URI::SchemeType& scheme);
-   URI::Transport nameToTransport(const QByteArray& name);
-   void parseAttribute(const QByteArray& extHn, const int start, const int pos);
-private:
-   URI* q_ptr;
+    // Strings associated with SchemeType
+    static const std::map<URI::SchemeType, const char*> schemeNames;
+
+    // String associated with the transport name
+    static const std::map<URI::Transport, const char*> transportNames;
+
+    struct Constants {
+        constexpr static const char TRANSPORT[] = "transport";
+        constexpr static const char TAG[] = "tag";
+    };
+
+    URIPimpl(const URI& uri);
+    URIPimpl& operator=(const URIPimpl&);
+
+    // Attributes
+    const URI& linked;
+
+    QString m_ExtHostname;
+    QString m_Scheme;
+    QString m_Userinfo;
+    QStringList m_lAttributes;
+    QString m_Stripped;
+    QString m_Hostname2;
+    QByteArray m_Tag;
+    int m_Port = -1;
+
+    URI::SchemeType m_HeaderType = URI::SchemeType::NONE;
+    URI::Transport m_Transport = URI::Transport::NOT_SET;
+    URI::ProtocolHint m_ProtocolHint = URI::ProtocolHint::UNRECOGNIZED;
+
+    bool m_hasChevrons {false};
+    bool m_Parsed {false};
+    bool m_HasAt {false};
+    bool m_HintParsed {false};
+    bool m_IsHNParsed {false};
+
+    // Helpers
+    URI::Transport nameToTransport(const QByteArray& name);
+    static QString strip(const QString& uri, URI::SchemeType& schemeType, QString& scheme);
+    static bool checkIp(const QString& str, bool &isHash, const URI::SchemeType& scheme);
+    void parseAttribute(const QByteArray& extHn, const int start, const int pos);
+    void parseHostname();
+    void parse();
+};
+
+constexpr const char URIPimpl::Constants::TRANSPORT[];
+constexpr const char URIPimpl::Constants::TAG[];
+
+const std::map<URI::Transport, const char*> URIPimpl::transportNames = {
+    { URI::Transport::NOT_SET, "NOT_SET" },
+    { URI::Transport::TLS, "TLS" },
+    { URI::Transport::tls, "tls" },
+    { URI::Transport::TCP, "TCP" },
+    { URI::Transport::tcp, "tcp" },
+    { URI::Transport::UDP, "UDP" },
+    { URI::Transport::udp, "udp" },
+    { URI::Transport::SCTP, "SCTP" },
+    { URI::Transport::sctp, "sctp" },
+    { URI::Transport::DTLS, "DTLS" },
+    { URI::Transport::dtls, "dtls" }
+};
+
+const std::map<URI::SchemeType, const char*> URIPimpl::schemeNames = {
+    { URI::SchemeType::SIP, "sip:" },
+    { URI::SchemeType::SIPS, "sips:" },
+    { URI::SchemeType::RING, "ring:" }
 };
 
-constexpr const char  URIPrivate::Constants::TRANSPORT[];
-constexpr const char  URIPrivate::Constants::TAG      [];
-
-const Matrix1D<URI::Transport, const char*> URIPrivate::transportNames = {{
-   /*NOT_SET*/ "NOT_SET",
-   /*TLS    */ "TLS"    ,
-   /*tls    */ "tls"    ,
-   /*TCP    */ "TCP"    ,
-   /*tcp    */ "tcp"    ,
-   /*UDP    */ "UDP"    ,
-   /*udp    */ "udp"    ,
-   /*SCTP   */ "SCTP"   ,
-   /*sctp   */ "sctp"   ,
-   /*DTLS   */ "DTLS"   ,
-   /*dtls   */ "dtls"   ,
-}};
-
-const Matrix1D<URI::SchemeType, const char*> URIPrivate::schemeNames = {{
-   /*NONE = */ ""     ,
-   /*SIP  = */ "sip:" ,
-   /*SIPS = */ "sips:",
-   /*RING = */ "ring:",
-}};
-
-const QString URIPrivate::whitespaceCharClass = QStringLiteral("[\\h\\x{200B}\\x{200C}\\x{200D}\\x{FEFF}]+");
-
-const QRegularExpression URIPrivate::startWhitespaceMatcher = QRegularExpression(
-                                                   "^" + URIPrivate::whitespaceCharClass,
-                                                   QRegularExpression::UseUnicodePropertiesOption);
-const QRegularExpression URIPrivate::endWhitespaceMatcher = QRegularExpression(
-                                                   URIPrivate::whitespaceCharClass + "$",
-                                                   QRegularExpression::UseUnicodePropertiesOption);
-
-URIPrivate::URIPrivate(URI* uri) : m_Parsed(false),m_HeaderType(URI::SchemeType::NONE),q_ptr(uri),
-m_hasChevrons(false),m_HasAt(false),m_ProtocolHint(URI::ProtocolHint::SIP_OTHER),m_HintParsed(false),
-m_IsHNParsed(false),m_Port(-1),m_Transport(URI::Transport::NOT_SET)
+URIPimpl::URIPimpl(const URI& uri)
+: linked(uri)
 {
 }
 
-///Default constructor
-URI::URI() : QString(), d_ptr(new URIPrivate(this))
+URIPimpl& URIPimpl::operator=(const URIPimpl& other)
 {
-
+    m_Parsed = other.m_Parsed;
+    m_HintParsed = other.m_HintParsed;
+    m_ExtHostname = other.m_ExtHostname;
+    m_HasAt = other.m_HasAt;
+    m_Scheme = other.m_Scheme;
+    m_ProtocolHint = other.m_ProtocolHint;
+    m_HeaderType = other.m_HeaderType;
+    m_Userinfo = other.m_Userinfo;
+    m_Stripped = other.m_Stripped;
+    m_IsHNParsed = other.m_IsHNParsed;
+    m_Port = other.m_Port;
+    m_Transport = other.m_Transport;
+    m_Tag = other.m_Tag;
+    m_hasChevrons = other.m_hasChevrons;
+    m_Hostname2 = other.m_Hostname2;
+    m_lAttributes = other.m_lAttributes;
+    return *this;
 }
 
-///Constructor
-URI::URI(const QString& other) : URI()
+URI::URI()
+: QString()
+, pimpl_(std::make_unique<URIPimpl>(*this))
 {
-   d_ptr->m_Stripped              = URIPrivate::strip(other,d_ptr->m_HeaderType);
-   (*static_cast<QString*>(this)) = d_ptr->m_Stripped                           ;
 }
 
-///Copy constructor
-URI::URI(const URI& o) : URI()
+URI::URI(const QString& uri)
+: URI()
 {
-   d_ptr->commonCopyConstructor(o);
+    QString simplified = uri.simplified().remove(' ').remove('<').remove('>');
+    pimpl_->m_Stripped = URIPimpl::strip(simplified, pimpl_->m_HeaderType, pimpl_->m_Scheme);
+    (*static_cast<QString*>(this)) = pimpl_->m_Stripped;
 }
 
-///Destructor
-URI::~URI()
+URI::URI(const URI& other)
+: URI()
 {
-   (*static_cast<QString*>(this)) = QString();
-   d_ptr->m_Stripped = QString();
-//    delete d_ptr;
+    *pimpl_ = *other.pimpl_;
+    (*static_cast<QString*>(this)) = pimpl_->m_Stripped;
 }
 
-void URIPrivate::commonCopyConstructor(const URI& o)
+URI& URI::operator=(const URI& other)
 {
-   //TODO see if a copy on write kind of algo could be used for this
-   m_Parsed       = o.d_ptr->m_Parsed      ;
-   m_HintParsed   = o.d_ptr->m_HintParsed  ;
-   m_ExtHostname  = o.d_ptr->m_ExtHostname ;
-   m_HasAt        = o.d_ptr->m_HasAt       ;
-   m_ProtocolHint = o.d_ptr->m_ProtocolHint;
-   m_HeaderType   = o.d_ptr->m_HeaderType  ;
-   m_Userinfo     = o.d_ptr->m_Userinfo    ;
-   m_Stripped     = o.d_ptr->m_Stripped    ;
-   m_IsHNParsed   = o.d_ptr->m_IsHNParsed  ;
-   m_Port         = o.d_ptr->m_Port        ;
-   m_Transport    = o.d_ptr->m_Transport   ;
-   m_Tag          = o.d_ptr->m_Tag         ;
-
-   (*static_cast<QString*>(q_ptr)) = o.d_ptr->m_Stripped;
+    if (this != &other) {
+        *pimpl_ = *other.pimpl_;
+        (*static_cast<QString*>(this)) = pimpl_->m_Stripped;
+    }
+    return *this;
 }
 
-/// Copy operator, make sure the cache is also copied
-URI& URI::operator=(const URI& o)
+URI::~URI()
 {
-   d_ptr->commonCopyConstructor(o);
-   return (*this);
 }
 
-///Strip out <sip:****> from the URI
-QString URIPrivate::strip(const QString& uri, URI::SchemeType& scheme)
+/**
+ * Strip out scheme from the URI
+ */
+QString URIPimpl::strip(const QString& uri, URI::SchemeType& schemeType, QString& scheme)
 {
-   if (uri.isEmpty())
-      return {};
-
-   /* remove whitespace at the start and end */
-   auto uriTrimmed = uri;
-   uriTrimmed.replace(startWhitespaceMatcher, "");
-   uriTrimmed.replace(endWhitespaceMatcher, "");
-
-   int start(uriTrimmed[0] == '<'?1:0),end(uriTrimmed.size()-1); //Other type of comparisons were too slow
+    if (uri.isEmpty())
+        return {};
 
-   if (start == end+1)
-      return {};
+    std::regex uri_regex = std::regex("[a-zA-Z][a-zA-Z0-9+.-]*:");
+    std::string uri_to_match = uri.toStdString();
+    std::smatch match;
 
-   const char c = uriTrimmed[start].toLatin1();
-
-   //Assume the scheme is either 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 && uriTrimmed[start+3] == ':') {
-      switch (c) {
-         case 's':
-            scheme = URI::SchemeType::SIP;
-            break;
-      }
-      start = start +4;
-   }
-   else if (end > start+4 && uriTrimmed[start+4] == ':') {
-      switch (c) {
-         case 'r':
-            scheme = URI::SchemeType::RING;
-            break;
-         case 's':
-            scheme = URI::SchemeType::SIPS;
-            break;
-      }
-      start = start +5;
-   }
+    if (std::regex_search(uri_to_match, match, uri_regex)) {
+        if (match.ready()) {
+            scheme = match.str(0).c_str();
+        }
+    }
 
-   if (end && uriTrimmed[end] == '>')
-      end--;
-   else if (start) {
-      //TODO there may be a ';' section with arguments, check
-   }
+    if (scheme == URIPimpl::schemeNames.at(URI::SchemeType::SIP)) {
+        schemeType = URI::SchemeType::SIP;
+    } else if (scheme == URIPimpl::schemeNames.at(URI::SchemeType::RING)) {
+        schemeType = URI::SchemeType::RING;
+    } else if (scheme == URIPimpl::schemeNames.at(URI::SchemeType::SIPS)) {
+        schemeType = URI::SchemeType::SIPS;
+    } else if (!scheme.isEmpty()) {
+        schemeType = URI::SchemeType::UNRECOGNIZED;
+    } else {
+        schemeType = URI::SchemeType::NONE;
+    }
 
-   return uriTrimmed.mid(start,end-start+1);
+    return uri.mid(scheme.size(), uri.size());
 }
 
 /**
- * Return the domaine of an URI
+ * Return the domain of the URI
  *
  * For example, example.com in <sip:12345@example.com>
  */
 QString URI::hostname() const
 {
-   if (!d_ptr->m_Parsed)
-      const_cast<URI*>(this)->d_ptr->parse();
-   return d_ptr->m_ExtHostname;
+    if (!pimpl_->m_Parsed)
+        pimpl_->parse();
+    return pimpl_->m_ExtHostname;
 }
 
 /**
@@ -234,9 +203,7 @@ QString URI::hostname() const
  */
 bool URI::hasHostname() const
 {
-   if (!d_ptr->m_Parsed)
-      const_cast<URI*>(this)->d_ptr->parse();
-   return !d_ptr->m_ExtHostname.isEmpty();
+    return hostname().isEmpty();
 }
 
 /**
@@ -245,21 +212,18 @@ bool URI::hasHostname() const
  */
 bool URI::hasPort() const
 {
-   if (!d_ptr->m_IsHNParsed) {
-      d_ptr->parseHostname();
-   }
-   return d_ptr->m_Port != -1;
+    return port() != -1;
 }
 
 /**
  * Return the port, -1 is none is set
  */
-int  URI::port() const
+int URI::port() const
 {
-   if (!d_ptr->m_IsHNParsed) {
-      d_ptr->parseHostname();
-   }
-   return d_ptr->m_Port;
+    if (!pimpl_->m_IsHNParsed) {
+        pimpl_->parseHostname();
+    }
+    return pimpl_->m_Port;
 }
 
 /**
@@ -267,9 +231,9 @@ int  URI::port() const
  */
 URI::SchemeType URI::schemeType() const
 {
-   if (!d_ptr->m_Parsed)
-      const_cast<URI*>(this)->d_ptr->parse();
-   return d_ptr->m_HeaderType;
+    if (!pimpl_->m_Parsed)
+        const_cast<URI*>(this)->pimpl_->parse();
+    return pimpl_->m_HeaderType;
 }
 
 /**
@@ -279,48 +243,48 @@ URI::SchemeType URI::schemeType() const
  * @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, const URI::SchemeType& scheme)
+bool URIPimpl::checkIp(const QString& str, bool &isHash, const URI::SchemeType& scheme)
 {
-   const QByteArray raw = str.toLatin1();
-   int max = str.size();
+    const QByteArray raw = str.toLatin1();
+    int max = str.size();
 
-   if (max < 3 || max > 45 || (!isHash && scheme == URI::SchemeType::RING))
-      return false;
+    if (max < 3 || max > 45 || (!isHash && scheme == URI::SchemeType::RING))
+        return false;
 
-   uchar dc(0),sc(0),i(0),d(0),hx(1);
+    uchar dc(0), sc(0), i(0), d(0), hx(1);
 
-   while (i < max) {
-      switch(raw[i]) {
-         case '.':
+    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':
+        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;
+                return false;
             break;
-         case ':':
+        case ':':
             isHash = false;
             sc++;
             //No break
             [[clang::fallthrough]];
-         case 'A': case 'B': case 'C':
-         case 'D': case 'E': case 'F':
-         case 'a': case 'b': case 'c':
-         case 'd': case 'e': case 'f':
+        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:
+        default:
             isHash = false;
             return false;
-      };
-      i++;
-   }
-   return (hx && dc == 3 && d < 4) ^ (sc > 1 && dc==0);
+        }
+        i++;
+    }
+    return (hx && dc == 3 && d < 4) ^ (sc > 1 && dc==0);
 }
 
 /**
@@ -329,180 +293,145 @@ bool URIPrivate::checkIp(const QString& str, bool &isHash, const URI::SchemeType
  *
  * @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;
-
-       URI::ProtocolHint hint;
-
-       //Step 1: Check IP
-       if (URIPrivate::checkIp(d_ptr->m_Userinfo, isHash, d_ptr->m_HeaderType)) {
-           hint = URI::ProtocolHint::IP;
-       }
-       //Step 2: Check RING hash
-       else if (isHash)
-       {
-           hint = URI::ProtocolHint::RING;
-       }
-       //Step 3: Not a hash but it begins with ring:. This is a username.
-       else if (d_ptr->m_HeaderType == URI::SchemeType::RING){
-           hint = URI::ProtocolHint::RING_USERNAME;
-       }
-       //Step 4: Check for SIP URIs
-       else if (d_ptr->m_HeaderType == URI::SchemeType::SIP)
-       {
-           //Step 4.1: Check for SIP URI with hostname
-           if (d_ptr->m_HasAt) {
-               hint = URI::ProtocolHint::SIP_HOST;
-           }
-           //Step 4.2: Assume SIP URI without hostname
-           else {
-               hint = URI::ProtocolHint::SIP_OTHER;
-           }
-       }
-       //Step 5: Assume SIP
-       else {
-           hint = URI::ProtocolHint::SIP_OTHER;
-       }
-
-       d_ptr->m_ProtocolHint = hint;
-       d_ptr->m_HintParsed = true;
+URI::ProtocolHint URI::protocolHint() const
+{
+    if (!pimpl_->m_Parsed)
+        pimpl_->parse();
+
+    if (!pimpl_->m_HintParsed) {
+        bool isHash = pimpl_->m_Userinfo.size() == 40;
+
+        URI::ProtocolHint hint;
+
+        //Step 1: Check IP
+        if (URIPimpl::checkIp(pimpl_->m_Userinfo, isHash, pimpl_->m_HeaderType)) {
+            hint = URI::ProtocolHint::IP;
+        }
+        //Step 2: Check RING hash
+        else if (isHash)
+        {
+            hint = URI::ProtocolHint::RING;
+        }
+        //Step 3: Not a hash but it begins with ring:. This is a username.
+        else if (pimpl_->m_HeaderType == URI::SchemeType::RING){
+            hint = URI::ProtocolHint::RING_USERNAME;
+        }
+        //Step 4: Check for SIP URIs
+        else if (pimpl_->m_HeaderType == URI::SchemeType::SIP)
+        {
+            //Step 4.1: Check for SIP URI with hostname
+            if (pimpl_->m_HasAt) {
+                hint = URI::ProtocolHint::SIP_HOST;
+            }
+            //Step 4.2: Assume SIP URI without hostname
+            else {
+                hint = URI::ProtocolHint::SIP_OTHER;
+            }
+        }
+        //Step 5: UNRECOGNIZED
+        else {
+            hint = URI::ProtocolHint::UNRECOGNIZED;
+        }
+
+        pimpl_->m_ProtocolHint = hint;
+        pimpl_->m_HintParsed = true;
     }
-    return d_ptr->m_ProtocolHint;
- }
+    return pimpl_->m_ProtocolHint;
+}
 
-///Convert the transport name to a string
-URI::Transport URIPrivate::nameToTransport(const QByteArray& name)
+// Convert the transport name to a string
+URI::Transport URIPimpl::nameToTransport(const QByteArray& name)
 {
-   if (name == transportNames[URI::Transport::NOT_SET  ])
-      return URI::Transport::NOT_SET;
-   else if (name == transportNames[URI::Transport::TLS ])
-      return URI::Transport::TLS    ;
-   else if (name == transportNames[URI::Transport::tls ])
-      return URI::Transport::tls    ;
-   else if (name == transportNames[URI::Transport::TCP ])
-      return URI::Transport::TCP    ;
-   else if (name == transportNames[URI::Transport::tcp ])
-      return URI::Transport::tcp    ;
-   else if (name == transportNames[URI::Transport::UDP ])
-      return URI::Transport::UDP    ;
-   else if (name == transportNames[URI::Transport::udp ])
-      return URI::Transport::udp    ;
-   else if (name == transportNames[URI::Transport::SCTP])
-      return URI::Transport::SCTP   ;
-   else if (name == transportNames[URI::Transport::sctp])
-      return URI::Transport::sctp   ;
-   else if (name == transportNames[URI::Transport::DTLS])
-      return URI::Transport::DTLS   ;
-   else if (name == transportNames[URI::Transport::dtls])
-      return URI::Transport::dtls   ;
-   return URI::Transport::NOT_SET   ;
+    auto it = std::find_if(transportNames.begin(), transportNames.end(), [&name](auto& entry){
+        return entry.second == name;
+    });
+
+    if (it != transportNames.end()) {
+        return it->first;
+    } else {
+        return URI::Transport::NOT_SET;
+    }
 }
 
-///Keep a cache of the values to avoid re-parsing them
-void URIPrivate::parse()
+// Keep a cache of the values to avoid re-parsing them
+void URIPimpl::parse()
 {
-   //FIXME the indexOf is done twice, the second time could be avoided
-   if (q_ptr->indexOf('@') != -1) {
-      const QStringList split = q_ptr->split('@');
-      m_HasAt       = true;
-      m_ExtHostname = split[1];
-      m_Userinfo    = split[0];
-      m_Parsed      = true;
-   }
-   else
-      m_Userinfo = (*q_ptr);
+    //FIXME the indexOf is done twice, the second time could be avoided
+    if (linked.indexOf('@') != -1) {
+        const QStringList split = linked.split('@');
+        m_HasAt       = true;
+        m_ExtHostname = split[1];
+        m_Userinfo    = split[0];
+        m_Parsed      = true;
+    } else {
+        m_Userinfo = (linked);
+    }
 }
 
-void URIPrivate::parseAttribute(const QByteArray& extHn, const int start, const int pos)
+void URIPimpl::parseAttribute(const QByteArray& extHn, const int start, const int pos)
 {
-   const QList<QByteArray> parts = extHn.mid(start+1,pos-start).split('=');
-
-   if (parts.size() == 2) {
-      if (parts[0].toLower() == Constants::TRANSPORT) {
-         m_Transport = nameToTransport(parts[1]);
-      }
-      else if (parts[0].toLower() == Constants::TAG) {
-         m_Tag = parts[1];
-      }
-   }
+    const QList<QByteArray> parts = extHn.mid(start+1,pos-start).split('=');
+
+    if (parts.size() == 2) {
+        if (parts[0].toLower() == Constants::TRANSPORT) {
+            m_Transport = nameToTransport(parts[1]);
+        }
+        else if (parts[0].toLower() == Constants::TAG) {
+            m_Tag = parts[1];
+        }
+    }
 }
 
-///Extract the hostname, port and attributes
-void URIPrivate::parseHostname()
+// Extract the hostname, port and attributes
+void URIPimpl::parseHostname()
 {
-   if (!m_Parsed)
-      parse();
-
-   const QByteArray extHn = q_ptr->hostname().toLatin1();
-   int length(extHn.size()), start(0);
-   bool inAttributes = false;
-
-   URI::Section section = URI::Section::HOSTNAME;
-
-   // in case no port, attributes, etc are provided
-   m_Hostname2 = q_ptr->hostname();
-
-   for (int i = 0; i < length; i++) {
-      const char c = extHn[i];
-      switch (c) {
-         case ':': //Begin port
-            switch(section) {
-               case URI::Section::HOSTNAME:
-                  m_Hostname2 = extHn.mid(start,i);
-                  start = i;
-                  section = URI::Section::PORT;
-                  break;
-               case URI::Section::USER_INFO:
-               case URI::Section::CHEVRONS :
-               case URI::Section::SCHEME   :
-               case URI::Section::TRANSPORT:
-               case URI::Section::TAG      :
-               case URI::Section::PORT     :
-                  break;
+    if (!m_Parsed)
+        parse();
+
+    const QByteArray extHn = linked.hostname().toLatin1();
+    int length(extHn.size()), start(0);
+    bool inAttributes = false;
+
+    URI::Section section = URI::Section::HOSTNAME;
+
+    // in case no port, attributes, etc are provided
+    m_Hostname2 = linked.hostname();
+
+    for (int i = 0; i < length; i++) {
+        const char c = extHn[i];
+        switch (c) {
+        case ':': //Begin port
+            if (section == URI::Section::HOSTNAME) {
+                m_Hostname2 = extHn.mid(start,i);
+                start = i;
+                section = URI::Section::PORT;
             }
             break;
-         case ';': //Begin attributes
-
+        case ';': // Begin attributes
             if (inAttributes) {
-               parseAttribute(extHn, start, i);
-            }
-            else {
-               switch(section) {
-                  case URI::Section::HOSTNAME:
-                     m_Hostname2 = extHn.mid(start+1,i-start);
-                     break;
-                  case URI::Section::PORT:
-                     m_Port = extHn.mid(start+1,i-start-1).toInt();
-                     break;
-                  case URI::Section::USER_INFO:
-                  case URI::Section::CHEVRONS :
-                  case URI::Section::SCHEME   :
-                  case URI::Section::TRANSPORT:
-                  case URI::Section::TAG      :
-                     break;
-               }
-               inAttributes = true;
+                parseAttribute(extHn, start, i);
+            } else {
+                if (section == URI::Section::HOSTNAME) {
+                    m_Hostname2 = extHn.mid(start+1,i-start);
+                } else if (section == URI::Section::HOSTNAME) {
+                    m_Port = extHn.mid(start+1,i-start-1).toInt();
+                }
+                inAttributes = true;
             }
 
             start = i;
             break;
-         case '#': //Begin fragments
+        case '#': // Begin fragments
             //TODO handle fragments to comply to the RFC
             break;
-         default:
+        default:
             break;
-      }
-   }
-
-   ///Get the remaining attribute
-   parseAttribute(extHn, start, length-1);
+        }
+    }
 
-   m_IsHNParsed = true;
+    // Get the remaining attribute
+    parseAttribute(extHn, start, length-1);
+    m_IsHNParsed = true;
 }
 
 /**
@@ -512,18 +441,19 @@ void URIPrivate::parseHostname()
  */
 QString URI::userinfo() const
 {
-   if (!d_ptr->m_Parsed)
-      const_cast<URI*>(this)->d_ptr->parse();
-   return d_ptr->m_Userinfo;
+    if (!pimpl_->m_Parsed)
+        pimpl_->parse();
+    return pimpl_->m_Userinfo;
 }
 
 /**
  * Sometime, some metadata can be used to deduce the scheme even if it wasn't
- * originally known. This will improve the result of ::format.
+ * originally known.
  */
 void URI::setSchemeType(SchemeType t)
 {
-    d_ptr->m_HeaderType = t;
+    pimpl_->m_HeaderType = t;
+    pimpl_->m_Scheme = URIPimpl::schemeNames.at(t);
 }
 
 /**
@@ -534,57 +464,37 @@ void URI::setSchemeType(SchemeType t)
  */
 QString URI::format(FlagPack<URI::Section> sections) const
 {
-   if (!d_ptr->m_IsHNParsed) {
-      d_ptr->parseHostname();
-   }
-
-   QString ret;
-
-   if (sections & URI::Section::CHEVRONS)
-      ret += '<';
+    if (!pimpl_->m_IsHNParsed) {
+        pimpl_->parseHostname();
+    }
 
-   if (sections & URI::Section::SCHEME) {
-       auto header_type = d_ptr->m_HeaderType;
+    QString ret;
 
-       // Try to use the protocol hint on undeterminated header type.
-       // Use SIP scheme type on last resort
-       if (header_type == SchemeType::NONE) {
-           switch (protocolHint()) {
-               case ProtocolHint::RING:
-               case ProtocolHint::RING_USERNAME:
-                   header_type = SchemeType::RING;
-                   break;
-               case ProtocolHint::SIP_HOST:
-               case ProtocolHint::SIP_OTHER:
-               case ProtocolHint::IP:
-               default:
-                   header_type = SchemeType::SIP;
-                   break;
-           }
-       }
+    if (sections & URI::Section::CHEVRONS)
+        ret += '<';
 
-      ret += URIPrivate::schemeNames[header_type];
-   }
+    if (sections & URI::Section::SCHEME)
+        ret += pimpl_->m_Scheme;
 
-   if (sections & URI::Section::USER_INFO)
-      ret += d_ptr->m_Userinfo;
+    if (sections & URI::Section::USER_INFO)
+        ret += pimpl_->m_Userinfo;
 
-   if (sections & URI::Section::HOSTNAME && !d_ptr->m_Hostname2.isEmpty())
-      ret += '@' + d_ptr->m_Hostname2;
+    if (sections & URI::Section::HOSTNAME && !pimpl_->m_Hostname2.isEmpty())
+        ret += '@' + pimpl_->m_Hostname2;
 
-   if (sections & URI::Section::PORT && d_ptr->m_Port != -1)
-      ret += ':' + QString::number(d_ptr->m_Port);
+    if (sections & URI::Section::PORT && pimpl_->m_Port != -1)
+        ret += ':' + QString::number(pimpl_->m_Port);
 
-   if (sections & URI::Section::CHEVRONS)
-      ret += '>';
+    if (sections & URI::Section::CHEVRONS)
+        ret += '>';
 
-   if (sections & URI::Section::TRANSPORT && d_ptr->m_Transport != URI::Transport::NOT_SET)
-      ret += ";transport=" + QString(URIPrivate::transportNames[d_ptr->m_Transport]);
+    if (sections & URI::Section::TRANSPORT && pimpl_->m_Transport != URI::Transport::NOT_SET)
+        ret += ";transport=" + QString(URIPimpl::transportNames.at(pimpl_->m_Transport));
 
-   if (sections & URI::Section::TAG && !d_ptr->m_Tag.isEmpty())
-      ret += ";tag=" + d_ptr->m_Tag;
+    if (sections & URI::Section::TAG && !pimpl_->m_Tag.isEmpty())
+        ret += ";tag=" + pimpl_->m_Tag;
 
-   return ret;
+    return ret;
 }
 
 /**
@@ -598,20 +508,24 @@ QString URI::full() const
 
 QDataStream& operator<<( QDataStream& stream, const URI::ProtocolHint& ph )
 {
-   switch(ph) {
-      case URI::ProtocolHint::SIP_OTHER:
-         stream << QStringLiteral("SIP_OTHER");
-         break;
-      case URI::ProtocolHint::RING:
-      case URI::ProtocolHint::RING_USERNAME:
-         stream << QStringLiteral("RING");
-         break;
-      case URI::ProtocolHint::IP       :
-         stream << QStringLiteral("IP");
-         break;
-      case URI::ProtocolHint::SIP_HOST :
-         stream << QStringLiteral("SIP_HOST");
-         break;
-   }
-   return stream;
+    switch(ph) {
+        case URI::ProtocolHint::SIP_OTHER:
+            stream << QStringLiteral("SIP_OTHER");
+            break;
+        case URI::ProtocolHint::RING:
+        case URI::ProtocolHint::RING_USERNAME:
+            stream << QStringLiteral("RING");
+            break;
+        case URI::ProtocolHint::IP:
+            stream << QStringLiteral("IP");
+            break;
+        case URI::ProtocolHint::SIP_HOST:
+            stream << QStringLiteral("SIP_HOST");
+            break;
+        case URI::ProtocolHint::UNRECOGNIZED:
+            stream << QStringLiteral("UNRECOGNIZED");
+            break;
+    }
+
+    return stream;
 }
diff --git a/src/uri.h b/src/uri.h
index 8d928554..6c1dd153 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -1,6 +1,7 @@
 /****************************************************************************
- *   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;
+
+    /**
+     * 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&);
 
 private:
-   const QScopedPointer<URIPrivate> d_ptr;
+    std::unique_ptr<URIPimpl> pimpl_;
 };
+
 Q_DECLARE_METATYPE(URI)
 
 Q_DECLARE_METATYPE(URI::ProtocolHint)
-- 
GitLab