diff --git a/depends/packages/native_libdmg-hfsplus.mk b/depends/packages/native_libdmg-hfsplus.mk index daf9fcee27..d0c6fe113f 100644 --- a/depends/packages/native_libdmg-hfsplus.mk +++ b/depends/packages/native_libdmg-hfsplus.mk @@ -13,7 +13,7 @@ define $(package)_preprocess_cmds endef define $(package)_config_cmds - $(host_prefix)/bin/cmake -DCMAKE_INSTALL_PREFIX:PATH=$(build_prefix) .. + $(host_prefix)/bin/cmake -DCMAKE_INSTALL_PREFIX:PATH=$(build_prefix) -DCMAKE_C_FLAGS="-Wl,--build-id=none" .. endef define $(package)_build_cmds diff --git a/src/governance/validators.cpp b/src/governance/validators.cpp index 13c3ffc9eb..af89b6adc4 100644 --- a/src/governance/validators.cpp +++ b/src/governance/validators.cpp @@ -100,7 +100,7 @@ bool CProposalValidator::ValidateName() static constexpr std::string_view strAllowedChars{"-_abcdefghijklmnopqrstuvwxyz0123456789"}; - std::transform(strName.begin(), strName.end(), strName.begin(), ToLower); + strName = ToLower(strName); if (strName.find_first_not_of(strAllowedChars) != std::string::npos) { strErrorMessages += "name contains invalid characters;"; diff --git a/src/logging.cpp b/src/logging.cpp index 7ac3cc7594..1a65a8534c 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -261,10 +261,32 @@ std::string BCLog::Logger::LogTimestampStr(const std::string& str) return strStamped; } +namespace BCLog { + /** Belts and suspenders: make sure outgoing log messages don't contain + * potentially suspicious characters, such as terminal control codes. + * + * This escapes control characters except newline ('\n') in C syntax. + * It escapes instead of removes them to still allow for troubleshooting + * issues where they accidentally end up in strings. + */ + std::string LogEscapeMessage(const std::string& str) { + std::string ret; + for (char ch_in : str) { + uint8_t ch = (uint8_t)ch_in; + if ((ch >= 32 || ch == '\n') && ch != '\x7f') { + ret += ch_in; + } else { + ret += strprintf("\\x%02x", ch); + } + } + return ret; + } +} + void BCLog::Logger::LogPrintStr(const std::string& str) { StdLockGuard scoped_lock(m_cs); - std::string str_prefixed = str; + std::string str_prefixed = LogEscapeMessage(str); if (m_log_threadnames && m_started_new_line) { // 16 chars total, "dash-" is 5 of them and another 1 is a NUL terminator diff --git a/src/netbase.cpp b/src/netbase.cpp index 83e128f606..680c3ac2a9 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -42,8 +42,8 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP; static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; static std::atomic interruptSocks5Recv(false); -enum Network ParseNetwork(std::string net) { - Downcase(net); +enum Network ParseNetwork(const std::string& net_in) { + std::string net = ToLower(net_in); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; if (net == "onion") return NET_ONION; diff --git a/src/netbase.h b/src/netbase.h index 7dc0992822..fa57b5914c 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -38,7 +38,7 @@ public: bool randomize_credentials; }; -enum Network ParseNetwork(std::string net); +enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); bool SetProxy(enum Network net, const proxyType &addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 11e459fd8e..bf400d8ae6 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -29,6 +29,7 @@ #include #include +#include #include static int64_t nLastHeaderTipUpdateNotification = 0; @@ -40,23 +41,37 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO optionsModel(_optionsModel), peerTableModel(nullptr), banTableModel(nullptr), - pollTimer(nullptr) + m_thread(new QThread(this)) { cachedBestHeaderHeight = -1; cachedBestHeaderTime = -1; peerTableModel = new PeerTableModel(m_node, this); banTableModel = new BanTableModel(m_node, this); - pollTimer = new QTimer(this); - connect(pollTimer, &QTimer::timeout, this, &ClientModel::updateTimer); - pollTimer->start(MODEL_UPDATE_DELAY); mnListCached = std::make_shared(); + QTimer* timer = new QTimer; + timer->setInterval(MODEL_UPDATE_DELAY); + connect(timer, &QTimer::timeout, [this] { + // no locking required at this point + // the following calls will acquire the required lock + Q_EMIT mempoolSizeChanged(m_node.getMempoolSize(), m_node.getMempoolDynamicUsage()); + Q_EMIT islockCountChanged(m_node.llmq().getInstantSentLockCount()); + }); + connect(m_thread, &QThread::finished, timer, &QObject::deleteLater); + connect(m_thread, &QThread::started, [timer] { timer->start(); }); + // move timer to thread so that polling doesn't disturb main event loop + timer->moveToThread(m_thread); + m_thread->start(); + subscribeToCoreSignals(); } ClientModel::~ClientModel() { unsubscribeFromCoreSignals(); + + m_thread->quit(); + m_thread->wait(); } int ClientModel::getNumConnections(unsigned int flags) const @@ -128,14 +143,6 @@ std::vector ClientModel::getAllGovernanceObjects() return m_node.gov().getAllNewerThan(0); } -void ClientModel::updateTimer() -{ - // no locking required at this point - // the following calls will acquire the required lock - Q_EMIT mempoolSizeChanged(m_node.getMempoolSize(), m_node.getMempoolDynamicUsage()); - Q_EMIT islockCountChanged(m_node.llmq().getInstantSentLockCount()); -} - void ClientModel::updateNumConnections(int numConnections) { Q_EMIT numConnectionsChanged(numConnections); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 69d16b7bba..77224c4c3a 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -104,7 +104,8 @@ private: PeerTableModel *peerTableModel; BanTableModel *banTableModel; - QTimer *pollTimer; + //! A thread to interact with m_node asynchronously + QThread* const m_thread; // The cache for mn list is not technically needed because CDeterministicMNManager // caches it internally for recent blocks but it's not enough to get consistent @@ -133,7 +134,6 @@ Q_SIGNALS: void showProgress(const QString &title, int nProgress); public Q_SLOTS: - void updateTimer(); void updateNumConnections(int numConnections); void updateNetworkActive(bool networkActive); void updateAlert(); diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index 24603e1c46..53077b4311 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -470,16 +470,19 @@ int GuiMain(int argc, char* argv[]) BitcoinApplication app(*node, argc, argv); - // Register meta types used for QMetaObject::invokeMethod - qRegisterMetaType< bool* >(); + // Register meta types used for QMetaObject::invokeMethod and Qt::QueuedConnection + qRegisterMetaType(); #ifdef ENABLE_WALLET qRegisterMetaType(); #endif - // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) - // IMPORTANT if it is no longer a typedef use the normal variant above - qRegisterMetaType< CAmount >("CAmount"); - qRegisterMetaType< std::function >("std::function"); + // Register typedefs (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) + // IMPORTANT: if CAmount is no longer a typedef use the normal variant above (see https://doc.qt.io/qt-5/qmetatype.html#qRegisterMetaType-1) + qRegisterMetaType("CAmount"); + qRegisterMetaType("size_t"); + + qRegisterMetaType>("std::function"); qRegisterMetaType("QMessageBox::Icon"); + /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: node->setupServerArgs(); diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 8a11dc91e6..e1dddafb5a 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -564,7 +564,7 @@ static UniValue masternodelist(const JSONRPCRequest& request) if (!request.params[0].isNull()) strMode = request.params[0].get_str(); if (!request.params[1].isNull()) strFilter = request.params[1].get_str(); - std::transform(strMode.begin(), strMode.end(), strMode.begin(), ToLower); + strMode = ToLower(strMode); if (request.fHelp || ( strMode != "addr" && strMode != "full" && strMode != "info" && strMode != "json" && diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 80cf9fc556..569d15a569 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -188,7 +188,7 @@ bool ParseBoolV(const UniValue& v, const std::string &strName) else if (v.isStr()) strBool = v.get_str(); - std::transform(strBool.begin(), strBool.end(), strBool.begin(), ToLower); + strBool = ToLower(strBool); if (strBool == "true" || strBool == "yes" || strBool == "1") { return true; diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 72a7a4a182..4dbf4419aa 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -25,6 +25,11 @@ #include +/* defined in logging.cpp */ +namespace BCLog { + std::string LogEscapeMessage(const std::string& str); +} + BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(util_criticalsection) @@ -1325,17 +1330,9 @@ BOOST_AUTO_TEST_CASE(test_ToLower) BOOST_CHECK_EQUAL(ToLower(0), 0); BOOST_CHECK_EQUAL(ToLower('\xff'), '\xff'); - std::string testVector; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, ""); - - testVector = "#HODL"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "#hodl"); - - testVector = "\x00\xfe\xff"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "\x00\xfe\xff"); + BOOST_CHECK_EQUAL(ToLower(""), ""); + BOOST_CHECK_EQUAL(ToLower("#HODL"), "#hodl"); + BOOST_CHECK_EQUAL(ToLower("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_ToUpper) @@ -1346,6 +1343,10 @@ BOOST_AUTO_TEST_CASE(test_ToUpper) BOOST_CHECK_EQUAL(ToUpper('{'), '{'); BOOST_CHECK_EQUAL(ToUpper(0), 0); BOOST_CHECK_EQUAL(ToUpper('\xff'), '\xff'); + + BOOST_CHECK_EQUAL(ToUpper(""), ""); + BOOST_CHECK_EQUAL(ToUpper("#hodl"), "#HODL"); + BOOST_CHECK_EQUAL(ToUpper("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_Capitalize) @@ -1478,4 +1479,17 @@ BOOST_AUTO_TEST_CASE(test_spanparsing) BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); } +BOOST_AUTO_TEST_CASE(test_LogEscapeMessage) +{ + // ASCII and UTF-8 must pass through unaltered. + BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("Valid log message貓"), "Valid log message貓"); + // Newlines must pass through unaltered. + BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("Message\n with newlines\n"), "Message\n with newlines\n"); + // Other control characters are escaped in C syntax. + BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("\x01\x7f Corrupted log message\x0d"), R"(\x01\x7f Corrupted log message\x0d)"); + // Embedded NULL characters are escaped too. + const std::string NUL("O\x00O", 3); + BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage(NUL), R"(O\x00O)"); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 6f05602fd7..cfb09e78eb 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -630,9 +630,18 @@ std::string HexStr(const Span s) return rv; } -void Downcase(std::string& str) +std::string ToLower(const std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), [](char c){return ToLower(c);}); + std::string r; + for (auto ch : str) r += ToLower((unsigned char)ch); + return r; +} + +std::string ToUpper(const std::string& str) +{ + std::string r; + for (auto ch : str) r += ToUpper((unsigned char)ch); + return r; } std::string Capitalize(std::string str) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 79a5937713..54e4777fb5 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -203,6 +203,8 @@ bool ConvertBits(const O& outfn, I it, I end) { * Converts the given character to its lowercase equivalent. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to lowercase. * @return the lowercase equivalent of c; or the argument * if no conversion is possible. @@ -213,17 +215,22 @@ constexpr char ToLower(char c) } /** - * Converts the given string to its lowercase equivalent. + * Returns the lowercase equivalent of the given string. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. - * @param[in,out] str the string to convert to lowercase. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to lowercase. + * @returns lowercased equivalent of str */ -void Downcase(std::string& str); +std::string ToLower(const std::string& str); /** * Converts the given character to its uppercase equivalent. * This function is locale independent. It only converts lowercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to uppercase. * @return the uppercase equivalent of c; or the argument * if no conversion is possible. @@ -233,13 +240,25 @@ constexpr char ToUpper(char c) return (c >= 'a' && c <= 'z' ? (c - 'a') + 'A' : c); } +/** + * Returns the uppercase equivalent of the given string. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to uppercase. + * @returns UPPERCASED EQUIVALENT OF str + */ +std::string ToUpper(const std::string& str); + /** * Capitalizes the first character of the given string. - * This function is locale independent. It only capitalizes the - * first character of the argument if it has an uppercase equivalent - * in the standard 7-bit ASCII range. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] str the string to capitalize. - * @return string with the first letter capitalized. + * @returns string with the first letter capitalized. */ std::string Capitalize(std::string str); diff --git a/src/util/system.cpp b/src/util/system.cpp index e08f0a2256..015220eed5 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -427,7 +427,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(is_index); } #ifdef WIN32 - std::transform(key.begin(), key.end(), key.begin(), ToLower); + key = ToLower(key); if (key[0] == '/') key[0] = '-'; #endif