/******************************************************************************************** * * * 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( "(?:[!?.,:;'\"]|(?:&|&)(?: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; }