Skip to content
Snippets Groups Projects
utils.cpp 14.24 KiB
/********************************************************************************************
 *                                                                                          *
 * Copyright (C) 2017 Armin Felder, Dennis Beier                                            *
 * This file is part of RocketChatMobileEngine <https://git.fairkom.net/chat/fairchat>.     *
 *                                                                                          *
 * RocketChatMobileEngine is free software: you can redistribute it and/or modify           *
 * it under the terms of the GNU General Public License as published by                     *
 * the Free Software Foundation, either version 3 of the License, or                        *
 * (at your option) any later version.                                                      *
 *                                                                                          *
 * RocketChatMobileEngine 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 General Public License for more details.                                             *
 *                                                                                          *
 * You should have received a copy of the GNU General Public License                        *
 * along with RocketChatMobileEngine. If not, see <http://www.gnu.org/licenses/>.           *
 *                                                                                          *
 ********************************************************************************************/


#include <QRegularExpressionMatch>
#include <QVector>
#include <QDebug>
#include <functional>
#include <QJsonObject>
#include <QJsonDocument>
#include <sstream>
#include <string>

#include "utils.h"

/**
 * C++ implementation of Ben Almans linkify
 * @brief Utils::emojiFy
 * @param text
 * @return
 */
QString Utils::removeUtf8Emojis( const QString &pText )
{
    static const QRegularExpression nonAlphaNumeric{ QStringLiteral( "[\\p{L}\\p{M}\\p{N}\\p{Z}\\p{Sm}\\p{Sc}\\p{Sk}\\p{P}\\p{Cc}\\p{Cf}*]" ), QRegularExpression::UseUnicodePropertiesOption };

    QRegularExpressionMatchIterator iter = nonAlphaNumeric.globalMatch( pText );
    QString retString;
    retString.reserve( 150 );

    for ( ; iter.hasNext(); ) {
        QRegularExpressionMatch match = iter.next();

        retString += match.captured();

    }

    return retString;
}

QString Utils::linkiFy( const QString &pText )
{
    static const QString scheme{ QStringLiteral( "[a-z\\d.-]+://" ) };
    static const QString ipv4{ QStringLiteral( "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])" ) };
    static const QString hostname{ QStringLiteral( "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+" ) };
    static const QString tld{ QStringLiteral( "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)" ) };
    static const QString host_or_ip{ QStringLiteral( "(?:" ) + hostname + tld + '|' + ipv4 + ')' };
    static const QString path{ QStringLiteral( "(?:[;/][^#?<>\\s]*)?" ) };
    static const QString query_frag{ QStringLiteral( "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?" ) };
    static const QString uri1{ QStringLiteral( "\\b" ) + scheme + QStringLiteral( "[^<>\\s]+" ) };
    static const QString uri2{ QStringLiteral( "\\b" ) + host_or_ip + path + query_frag + QStringLiteral( "(?!\\w)" ) };
    static const QString skip {QStringLiteral( "(?!file)" ) };
    static const QString mailto{ QStringLiteral( "mailto" ) };
    static const QString email{ QStringLiteral( "(?:" ) + mailto + QStringLiteral( ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" ) + host_or_ip + query_frag + QStringLiteral( "(?!\\w)" ) };
    static const QRegularExpression uri_re{ skip + QStringLiteral( "(?:" ) + uri1 + '|' + uri2 + '|' + email + ')', QRegularExpression::CaseInsensitiveOption };
    static const QRegularExpression scheme_re{ '^' + scheme, QRegularExpression::CaseInsensitiveOption};
    static const QRegularExpression punctRegexp{ QStringLiteral( "(?:[!?.,:;'\"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$" ) };
    static const QRegularExpression illegalEnding{ QStringLiteral( "([:/])w+$" ) };
    static QHash<QString, QString> quotes;
    quotes["'"] = "`";
    quotes[">"] = "<";
    quotes[")"] = "(";
    quotes["]"] = "[";
    quotes["}"] = "{";
    quotes["»"] = "«";
    quotes["›"] = "‹";


    std::function<QString( QString text )> parse = [&]( QString pText ) {
        QString html;
        html.reserve( 300 );
        QVector<QString> parts;
        QString link_last;
        link_last.reserve( 100 );
        int idx = 0;
        int idx_last = 0;
        int idx_prev = 0;
        QRegularExpressionMatchIterator iter = uri_re.globalMatch( pText );

        for ( int i = 0; iter.hasNext(); i++ ) {
            QRegularExpressionMatch match = iter.next();
            QString link = match.captured();

            if ( illegalEnding.match( link ).hasMatch() ) {

                continue;
            }

            idx_last = match.capturedEnd();
            idx = idx_last - link.length();



            do {
                link_last = link;
                QString quote_end = link.right( 1 );
                QString quote_begin = quotes[quote_end];

                if ( quote_begin.length() ) {
                    QRegularExpression begin( QStringLiteral( "\\" ) + quote_begin + QStringLiteral( "(?!$)" ) );
                    QRegularExpression end( QStringLiteral( "\\" ) + quote_end );
                    QRegularExpressionMatch matches_begin = begin.match( link );
                    QRegularExpressionMatch matches_end = end.match( link );

                    if ( ( matches_begin.hasMatch() ? matches_begin.lastCapturedIndex() : 0 ) < ( matches_end.hasMatch() ? matches_end.lastCapturedIndex() : 0 ) ) {
                        link = link.left( link.length() - 1 );
                    }
                }
            } while ( link.length() && link != link_last );

            QString href = link;

            if ( !scheme_re.match( href ).hasMatch() ) {
                if ( href.indexOf( '@' ) != -1 ) {
                    if ( !href.indexOf( mailto ) ) {

                    } else {
                        href = mailto + href;
                    }
                } else if ( !href.indexOf( QStringLiteral( "irc." ) ) ) {
                    href = QStringLiteral( "irc://" ) + href;
                } else if ( !href.indexOf( QStringLiteral( "ftp." ) ) ) {
                    href = QStringLiteral( "ftp://" ) + href;
                } else {
                    href = QStringLiteral( "http://" ) + href;
                }

            }

            if ( idx_prev != idx ) {
                parts.append( pText.mid( idx_prev, idx - idx_prev ) );
            }

            idx_prev = idx_last;

            parts.append( QStringLiteral( "<a href='" ) + href + QStringLiteral( "'>" ) + link + QStringLiteral( "</a>" ) );
        }

        parts.append( pText.right( pText.length() - idx_last ) );

        for ( const auto part : parts ) {
            html += part;
        }

        return html;

    };
    QString html = parse( pText );

    return html;
}

double Utils::getMessageTimestamp( const QJsonObject &pMessage )
{
    double seconds = 0;

    if ( Q_LIKELY( pMessage.contains( QStringLiteral( "ts" ) ) ) ) {
        QJsonObject ts = pMessage[QStringLiteral( "ts" )].toObject();

        if ( Q_LIKELY( ts.contains( QStringLiteral( "$date" ) ) ) ) {
            seconds = ts[QStringLiteral( "$date" )].toDouble();
        }
    }

    return seconds;
}
bool  Utils::messageHasTimestamp( const QJsonObject &pMessage )
{
    if ( Q_LIKELY( pMessage.contains( QStringLiteral( "ts" ) ) ) ) {
        QJsonObject ts = pMessage[QStringLiteral( "ts" )].toObject();
        return ts.contains( QStringLiteral( "$date" ) );
    }

    return false;
}

QString Utils::getPathPrefix()
{
#if defined(Q_OS_WINDOWS)||defined(Q_OS_WINRT)
    return QStringLiteral( "file:///" );
#else
    return QStringLiteral( "file://" );
#endif
}

QString Utils::escapeUnicodeEmoji( const QString &pString )
{
    qDebug() << pString;
    static const QRegularExpression reg{ QStringLiteral( "(\\b[A-Fa-f0-9]{2,6}\\b)" ) };
    QRegularExpressionMatchIterator iter = reg.globalMatch( pString );

    QString retString;

    if ( pString.contains( "-" ) ) {
        QStringList parts = pString.split( "-" );

        for ( const auto &item : parts ) {
            int part;
            std::stringstream ss;
            ss << std::hex << item.toStdString();
            ss >> part;

            if ( part >= 0x10000 && part <= 0x10FFFF ) {
                int hi = ( ( part - 0x10000 ) / 0x400 ) + 0xD800;
                int lo = ( ( part - 0x10000 ) % 0x400 ) + 0xDC00;
                retString += QChar( hi );
                retString += QChar( lo );
            } else {
                retString = QChar( part );
            }
        }
    } else {
        int part;
        std::stringstream ss;
        ss << std::hex << pString.toStdString();
        ss >> part;

        if ( part >= 0x10000 && part <= 0x10FFFF ) {
            int hi = ( ( part - 0x10000 ) / 0x400 ) + 0xD800;
            int lo = ( ( part - 0x10000 ) % 0x400 ) + 0xDC00;
            retString += QChar( hi );
            retString += QChar( lo );
        } else {
            retString = QChar( part );
        }
    }

    return retString;
}

QString Utils::replaceUnicodeEmojis( const QString &pText, EmojiRepo *pEmojiRepo )
{
    QString returnText = pText;

    if ( pEmojiRepo != nullptr ) {
        auto items = pEmojiRepo->getElements();

        for ( const auto &emoji : items ) {
            if ( Q_LIKELY( !emoji.isNull() ) ) {
                QString unicode = emoji->getUnicodeChar();

                if ( Q_LIKELY( unicode.size() ) ) {
                    returnText.replace( unicode, emoji->getIdentifier() );
                }
            }
        }
    }

    return returnText;
}
bool Utils::compareMessageTimestamps( const QJsonObject &pMessage1, const QJsonObject &pMessage2 )
{
    return ( getMessageTimestamp( pMessage1 ) - getMessageTimestamp( pMessage2 ) ) > 0;
}

QString Utils::emojiFy( const QString &pMessage, EmojiRepo *pEmojiRepo )
{
    QString returnText = pMessage;
    static const QRegularExpression reg{ QStringLiteral( ":[a-zA-Z-_0-9ßöüäÖÜÄ]+:" ) };

    if ( pEmojiRepo != nullptr ) {
        QRegularExpressionMatchIterator iter = reg.globalMatch( pMessage );
        QVector<QString> parts;
        parts.reserve( 150 );
        int idx = 0;
        int idx_last = 0;
        int idx_prev = 0;

        while ( iter.hasNext() ) {
            QRegularExpressionMatch match = iter.next();
            QString matchString = match.captured();

            if ( pEmojiRepo->contains( matchString ) ) {
                idx_last = match.capturedEnd();
                idx = idx_last - matchString.length();
                //               QString before = pMessage.left( match.capturedStart() );
                //               QString after = pMessage.right( pMessage.length() - match.capturedEnd() );

                if ( idx_prev != idx ) {
                    parts.append( pMessage.mid( idx_prev, idx - idx_prev ) );
                }

                idx_prev = idx_last;
                parts.append( pEmojiRepo->get( matchString )->getHtml() );
            }
        }

        if ( parts.length() ) {
            parts.append( pMessage.right( pMessage.length() - idx_last ) );
            returnText = "";

            for ( const auto &part : parts ) {
                returnText += part;
            }
        }
    }

    return returnText;
}

qint16 Utils::hash( const QStringList &pStrings )
{
    QByteArray stringSum;
    stringSum.reserve( 30 );

    for ( const auto &string : pStrings ) {
        QByteArray stringArray( string.toUtf8() );
        int limit = std::max( stringArray.length(), stringSum.length() );

        if ( stringSum.isEmpty() ) {
            stringSum = stringArray;
        } else {
            for ( int i = 0; i < limit; i++ ) {
                int num1 = 0;

                if ( i < stringArray.length() ) {
                    num1 = stringArray[i];
                }

                if ( i < stringSum.length() ) {
                    stringSum[i] = stringSum[i] + num1;
                } else {
                    stringSum.append( num1 );
                }
            }
        }
    }

    return qChecksum( stringSum, stringSum.length() );
}

QString Utils::getRandomString( int pLength )
{
    static const QString BASE64_CHARS{ QStringLiteral( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" )};


    QString randString;
    randString.reserve( pLength );

    for ( int i = 0; i < pLength; i++ ) {
        int index = std::rand() % BASE64_CHARS.size();
        randString += BASE64_CHARS.at( index );
    }

    return randString;

}