Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/releaseTest
  • release/releaseWindowsTest
  • release/windowsReleaseTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
25 results

uri.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    uri.cpp 10.16 KiB
    /****************************************************************************
     *   Copyright (C) 2014-2015 by Savoir-Faire Linux                          *
     *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
     *                                                                          *
     *   This library is free software; you can redistribute it and/or          *
     *   modify it under the terms of the GNU Lesser General Public             *
     *   License as published by the Free Software Foundation; either           *
     *   version 2.1 of the License, or (at your option) any later version.     *
     *                                                                          *
     *   This library is distributed in the hope that it will be useful,        *
     *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
     *   Lesser General Public License for more details.                        *
     *                                                                          *
     *   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"
    
    
    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      ;
       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, const URI::SchemeType& scheme);
    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_HasAt(false),m_ProtocolHint(URI::ProtocolHint::SIP_OTHER),m_HintParsed(false)
    {
    }
    
    ///Constructor
    URI::URI(const QString& other):QString(), d_ptr(new URIPrivate(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):QString(), d_ptr(new URIPrivate(this))
    {
       //TODO see if a copy on write kind of algo could be used for this
       d_ptr->m_Parsed       = o.d_ptr->m_Parsed      ;
       d_ptr->m_HintParsed   = o.d_ptr->m_HintParsed  ;
       d_ptr->m_Hostname     = o.d_ptr->m_Hostname    ;
       d_ptr->m_HasAt        = o.d_ptr->m_HasAt       ;
       d_ptr->m_ProtocolHint = o.d_ptr->m_ProtocolHint;
       d_ptr->m_HeaderType   = o.d_ptr->m_HeaderType  ;
       d_ptr->m_Userinfo     = o.d_ptr->m_Userinfo    ;
       d_ptr->m_Stripped     = o.d_ptr->m_Stripped    ;
    
       (*static_cast<QString*>(this)) = o.d_ptr->m_Stripped;
    }
    
    ///Destructor
    URI::~URI()
    {
       (*static_cast<QString*>(this)) = QString();
       d_ptr->m_Stripped = QString();
    //    delete d_ptr;
    }
    
    /// Copy operator, make sure the cache is also copied
    URI& URI::operator=(const URI& o)
    {
       d_ptr->m_Parsed       = o.d_ptr->m_Parsed      ;
       d_ptr->m_HintParsed   = o.d_ptr->m_HintParsed  ;
       d_ptr->m_Hostname     = o.d_ptr->m_Hostname    ;
       d_ptr->m_HasAt        = o.d_ptr->m_HasAt       ;
       d_ptr->m_ProtocolHint = o.d_ptr->m_ProtocolHint;
       d_ptr->m_HeaderType   = o.d_ptr->m_HeaderType  ;
       d_ptr->m_Userinfo     = o.d_ptr->m_Userinfo    ;
       d_ptr->m_Stripped     = o.d_ptr->m_Stripped    ;
    
       (*static_cast<QString*>(this)) = o.d_ptr->m_Stripped;
       return (*this);
    }
    
    ///Strip out <sip:****> from the URI
    QString URIPrivate::strip(const QString& uri, URI::SchemeType& scheme)
    {
       if (uri.isEmpty())
          return {};
    
       int start(uri[0] == '<'?1:0),end(uri.size()-1); //Other type of comparisons were too slow
    
       if (start == end+1)
          return {};
    
       const uchar c = uri[start].toLatin1();
    
       //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 && uri[start+3] == ':') {
          switch (c) {
             case 'i':
                scheme = URI::SchemeType::IAX;
                break;
             case 's':
                scheme = URI::SchemeType::SIP;
                break;
          }
          start = start +4;
       }
       else if (end > start+4 && uri[start+4] == ':') {
          switch (c) {
             case 'i':
                scheme = URI::SchemeType::IAX2;
                break;
             case 'r':
                scheme = URI::SchemeType::RING;
                break;
             case 's':
                scheme = URI::SchemeType::SIPS;
                break;
          }
          start = start +5;
       }
    
       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
     *
     * 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_Hostname;
    }
    
    /**
     * Check if the URI has an hostname
     * 
     * This will return true if there is something between '@' and ';' (or an end of line)
     */
    bool URI::hasHostname() const
    {
       if (!d_ptr->m_Parsed)
          const_cast<URI*>(this)->d_ptr->parse();
       return !d_ptr->m_Hostname.isEmpty();
    }
    
    /**
     * Return the URI SchemeType
     */
    URI::SchemeType URI::schemeType() const
    {
       if (!d_ptr->m_Parsed)
          const_cast<URI*>(this)->d_ptr->parse();
       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, const URI::SchemeType& scheme)
    {
       const QByteArray raw = str.toLatin1();
       ushort max = str.size();
    
       if (max < 3 || max > 45 || (!isHash && scheme == URI::SchemeType::RING))
          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:
                isHash = false;
                return false;
          };
          i++;
       }
       return (hx && dc == 3 && d < 4) ^ (sc > 1 && dc==0);
    }
    
    /**
     * 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,d_ptr->m_HeaderType) ? URI::ProtocolHint::IP
    
          : (
             //Step three    : Check RING protocol, is has already been detected at this point
             (d_ptr->m_HeaderType == URI::SchemeType::RING && isHash) || (isHash && d_ptr->m_Userinfo.size() == 40)
                ? 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;
       }
       return d_ptr->m_ProtocolHint;
    }
    
    ///Keep a cache of the values to avoid re-parsing them
    void URIPrivate::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_Hostname = split[1];
          m_Userinfo = split[0];
          m_Parsed   = true;
       }
       else
          m_Userinfo = (*q_ptr);
    }
    
    /**
     * Extract the user info field from the URI
     *
     * For example, "123" in sip:123@myserver.net
     */
    QString URI::userinfo() const
    {
       if (!d_ptr->m_Parsed)
          const_cast<URI*>(this)->d_ptr->parse();
       return d_ptr->m_Userinfo;
    }
    
    /**
     * Some feature, like SIP presence, require a properly formatted URI
     */
    QString URI::fullUri() const
    {
       return QString("<%1%2>")
          .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;
    }