diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index f2a13991cb2..0bca020553e 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -269,4 +269,71 @@ QTCREATOR_UTILS_EXPORT bool readMultiLineString(const QJsonValue &value, QString return true; } +QTCREATOR_UTILS_EXPORT int parseUsedPortFromNetstatOutput(const QByteArray &line) +{ + const QByteArray trimmed = line.trimmed(); + int base = 0; + QByteArray portString; + + if (trimmed.startsWith("TCP") || trimmed.startsWith("UDP")) { + // Windows. Expected output is something like + // + // Active Connections + // + // Proto Local Address Foreign Address State + // TCP 0.0.0.0:80 0.0.0.0:0 LISTENING + // TCP 0.0.0.0:113 0.0.0.0:0 LISTENING + // [...] + // TCP 10.9.78.4:14714 0.0.0.0:0 LISTENING + // TCP 10.9.78.4:50233 12.13.135.180:993 ESTABLISHED + // [...] + // TCP [::]:445 [::]:0 LISTENING + // TCP 192.168.0.80:51905 169.55.74.50:443 ESTABLISHED + // UDP [fe80::880a:2932:8dff:a858%6]:1900 *:* + const int firstBracketPos = trimmed.indexOf('['); + int colonPos = -1; + if (firstBracketPos == -1) { + colonPos = trimmed.indexOf(':'); // IPv4 + } else { + // jump over host part + const int secondBracketPos = trimmed.indexOf(']', firstBracketPos + 1); + colonPos = trimmed.indexOf(':', secondBracketPos); + } + const int firstDigitPos = colonPos + 1; + const int spacePos = trimmed.indexOf(' ', firstDigitPos); + if (spacePos < 0) + return -1; + const int len = spacePos - firstDigitPos; + base = 10; + portString = trimmed.mid(firstDigitPos, len); + } else { + // Expected output on Linux something like + // + // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt ... + // 0: 00000000:2805 00000000:0000 0A 00000000:00000000 00:00000000 00000000 ... + // + const int firstColonPos = trimmed.indexOf(':'); + if (firstColonPos < 0) + return -1; + const int secondColonPos = trimmed.indexOf(':', firstColonPos + 1); + if (secondColonPos < 0) + return -1; + const int spacePos = trimmed.indexOf(' ', secondColonPos + 1); + if (spacePos < 0) + return -1; + const int len = spacePos - secondColonPos - 1; + base = 16; + portString = trimmed.mid(secondColonPos + 1, len); + } + + bool ok = true; + const int port = portString.toInt(&ok, base); + if (!ok) { + qWarning("%s: Unexpected string '%s' is not a port. Tried to read from '%s'", + Q_FUNC_INFO, line.data(), portString.data()); + return -1; + } + return port; +} + } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index 0723e76656f..1ec3a0aeecc 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -79,4 +79,6 @@ private: QTCREATOR_UTILS_EXPORT void expandMacros(QString *str, AbstractMacroExpander *mx); QTCREATOR_UTILS_EXPORT QString expandMacros(const QString &str, AbstractMacroExpander *mx); +QTCREATOR_UTILS_EXPORT int parseUsedPortFromNetstatOutput(const QByteArray &line); + } // namespace Utils diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index 7f243e8e435..7e81057c634 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include @@ -168,65 +169,10 @@ class DesktopPortsGatheringMethod : public PortsGatheringMethod { QList ports; const QList lines = output.split('\n'); - if (HostOsInfo::isWindowsHost()) { - // Expected output is something like - // - // Active Connections - // - // Proto Local Address Foreign Address State - // TCP 0.0.0.0:80 0.0.0.0:0 LISTENING - // TCP 0.0.0.0:113 0.0.0.0:0 LISTENING - // [...] - // TCP 10.9.78.4:14714 0.0.0.0:0 LISTENING - // TCP 10.9.78.4:50233 12.13.135.180:993 ESTABLISHED - for (const QByteArray &line : lines) { - const QByteArray trimmed = line.trimmed(); - if (!trimmed.startsWith("TCP")) - continue; - int colonPos = trimmed.indexOf(':'); - if (colonPos < 0) - continue; - int spacePos = trimmed.indexOf(':', colonPos + 1); - if (spacePos < 0) - continue; - bool ok; - int len = spacePos - colonPos - 1; - const Utils::Port port(line.mid(colonPos + 1, len).toInt(&ok, 16)); - if (ok) { - if (!ports.contains(port)) - ports << port; - } else { - qWarning("%s: Unexpected string '%s' is not a port.", - Q_FUNC_INFO, line.data()); - } - } - } else if (HostOsInfo::isLinuxHost()) { - // Expected outpit is something like - // - // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt ... - // 0: 00000000:2805 00000000:0000 0A 00000000:00000000 00:00000000 00000000 ... - // - for (const QByteArray &line : lines) { - int firstColonPos = line.indexOf(':'); - if (firstColonPos < 0) - continue; - int secondColonPos = line.indexOf(':', firstColonPos + 1); - if (secondColonPos < 0) - continue; - int spacePos = line.indexOf(':', secondColonPos + 1); - if (spacePos < 0) - continue; - bool ok; - int len = spacePos - secondColonPos - 1; - const Utils::Port port(line.mid(secondColonPos + 1, len).toInt(&ok, 16)); - if (ok) { - if (!ports.contains(port)) - ports << port; - } else { - qWarning("%s: Unexpected string '%s' is not a port.", - Q_FUNC_INFO, line.data()); - } - } + for (const QByteArray &line : lines) { + const Port port(Utils::parseUsedPortFromNetstatOutput(line)); + if (port.isValid() && !ports.contains(port)) + ports.append(port); } return ports; } diff --git a/tests/auto/utils/stringutils/tst_stringutils.cpp b/tests/auto/utils/stringutils/tst_stringutils.cpp index 96a950bb436..5a5933b323c 100644 --- a/tests/auto/utils/stringutils/tst_stringutils.cpp +++ b/tests/auto/utils/stringutils/tst_stringutils.cpp @@ -82,6 +82,8 @@ private slots: void testMacroExpander(); void testStripAccelerator(); void testStripAccelerator_data(); + void testParseUsedPortFromNetstatOutput(); + void testParseUsedPortFromNetstatOutput_data(); private: TestMacroExpander mx; @@ -202,6 +204,37 @@ void tst_StringUtils::testStripAccelerator_data() QTest::newRow("Test&") << "Test"; } +void tst_StringUtils::testParseUsedPortFromNetstatOutput() +{ + QFETCH(QString, line); + QFETCH(int, port); + + QCOMPARE(Utils::parseUsedPortFromNetstatOutput(line.toUtf8()), port); +} + +void tst_StringUtils::testParseUsedPortFromNetstatOutput_data() +{ + QTest::addColumn("line"); + QTest::addColumn("port"); + + QTest::newRow("Empty") << "" << -1; + + // Windows netstat. + QTest::newRow("Win1") << "Active Connection" << -1; + QTest::newRow("Win2") << " Proto Local Address Foreign Address State" << -1; + QTest::newRow("Win3") << " TCP 0.0.0.0:80 0.0.0.0:0 LISTENING" << 80; + QTest::newRow("Win4") << " TCP 0.0.0.0:113 0.0.0.0:0 LISTENING" << 113; + QTest::newRow("Win5") << " TCP 10.9.78.4:14714 0.0.0.0:0 LISTENING" << 14714; + QTest::newRow("Win6") << " TCP 10.9.78.4:50233 12.13.135.180:993 ESTABLISHED" << 50233; + QTest::newRow("Win7") << " TCP [::]:445 [::]:0 LISTENING" << 445; + QTest::newRow("Win8") << " TCP 192.168.0.80:51905 169.55.74.50:443 ESTABLISHED" << 51905; + QTest::newRow("Win9") << " UDP [fe80::840a:2942:8def:abcd%6]:1900 *:* " << 1900; + + // Linux + QTest::newRow("Linux1") << "sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt ..." << -1; + QTest::newRow("Linux2") << "0: 00000000:2805 00000000:0000 0A 00000000:00000000 00:00000000 00000000 ..." << 10245; +} + QTEST_MAIN(tst_StringUtils) #include "tst_stringutils.moc"