diff --git a/chatterino.pro b/chatterino.pro index cda40d7e0..7268f78f3 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -134,6 +134,7 @@ SOURCES += \ src/common/Env.cpp \ src/common/LinkParser.cpp \ src/common/Modes.cpp \ + src/common/NetworkCommon.cpp \ src/common/NetworkManager.cpp \ src/common/NetworkPrivate.cpp \ src/common/NetworkRequest.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42b872d78..872089ee3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,8 @@ set(SOURCE_FILES main.cpp common/LinkParser.hpp common/Modes.cpp common/Modes.hpp + common/NetworkCommon.cpp + common/NetworkCommon.hpp common/NetworkManager.cpp common/NetworkManager.hpp common/NetworkPrivate.cpp diff --git a/src/common/NetworkCommon.cpp b/src/common/NetworkCommon.cpp new file mode 100644 index 000000000..850612417 --- /dev/null +++ b/src/common/NetworkCommon.cpp @@ -0,0 +1,35 @@ +#include "common/NetworkCommon.hpp" + +#include + +namespace chatterino { + +std::vector> parseHeaderList( + const QString &headerListString) +{ + std::vector> res; + + // Split the string into a list of header pairs + // e.g. "Authorization:secretkey;NextHeader:boo" turning into ["Authorization:secretkey","NextHeader:boo"] + auto headerPairs = headerListString.split(";"); + + for (const auto &headerPair : headerPairs) + { + const auto headerName = + headerPair.section(":", 0, 0).trimmed().toUtf8(); + const auto headerValue = headerPair.section(":", 1).trimmed().toUtf8(); + + if (headerName.isEmpty() || headerValue.isEmpty()) + { + // The header part either didn't contain a : or the name/value was empty + // Skip the value + continue; + } + + res.emplace_back(headerName, headerValue); + } + + return res; +} + +} // namespace chatterino diff --git a/src/common/NetworkCommon.hpp b/src/common/NetworkCommon.hpp index 1ecfe30ce..5f90a95a0 100644 --- a/src/common/NetworkCommon.hpp +++ b/src/common/NetworkCommon.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include + +#include class QNetworkReply; @@ -22,4 +25,13 @@ enum class NetworkRequestType { Patch, }; +// parseHeaderList takes a list of headers in string form, +// where each header pair is separated by semicolons (;) and the header name and value is divided by a colon (:) +// +// We return a vector of pairs, where the first value is the header name and the second value is the header value +// +// e.g. "Authorization:secretkey;NextHeader:boo" will return [{"Authorization", "secretkey"}, {"NextHeader", "boo"}] +std::vector> parseHeaderList( + const QString &headerListString); + } // namespace chatterino diff --git a/src/common/NetworkRequest.cpp b/src/common/NetworkRequest.cpp index 0b6beb22f..70cfa4979 100644 --- a/src/common/NetworkRequest.cpp +++ b/src/common/NetworkRequest.cpp @@ -106,16 +106,12 @@ NetworkRequest NetworkRequest::header(const char *headerName, return std::move(*this); } -NetworkRequest NetworkRequest::headerList(const QStringList &headers) && +NetworkRequest NetworkRequest::headerList( + const std::vector> &headers) && { - for (const QString &header : headers) + for (const auto &[headerName, headerValue] : headers) { - const QStringList thisHeader = header.trimmed().split(":"); - if (thisHeader.size() == 2) - { - this->data->request_.setRawHeader(thisHeader[0].trimmed().toUtf8(), - thisHeader[1].trimmed().toUtf8()); - } + this->data->request_.setRawHeader(headerName, headerValue); } return std::move(*this); } diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp index 2480eabb3..51ee1e962 100644 --- a/src/common/NetworkRequest.hpp +++ b/src/common/NetworkRequest.hpp @@ -54,7 +54,8 @@ public: NetworkRequest header(const char *headerName, const char *value) &&; NetworkRequest header(const char *headerName, const QByteArray &value) &&; NetworkRequest header(const char *headerName, const QString &value) &&; - NetworkRequest headerList(const QStringList &headers) &&; + NetworkRequest headerList( + const std::vector> &headers) &&; NetworkRequest timeout(int ms) &&; NetworkRequest concurrent() &&; NetworkRequest authorizeTwitchV5(const QString &clientID, diff --git a/src/util/NuulsUploader.cpp b/src/util/NuulsUploader.cpp index 38a2ee8d3..0705861d0 100644 --- a/src/util/NuulsUploader.cpp +++ b/src/util/NuulsUploader.cpp @@ -128,8 +128,8 @@ void uploadImageToNuuls(RawImageData imageData, ChannelPtr channel, getSettings()->imageUploaderFormField.getValue().isEmpty() ? getSettings()->imageUploaderFormField.getDefaultValue() : getSettings()->imageUploaderFormField); - QStringList extraHeaders( - getSettings()->imageUploaderHeaders.getValue().split(";")); + auto extraHeaders = + parseHeaderList(getSettings()->imageUploaderHeaders.getValue()); QString originalFilePath = imageData.filePath; QHttpMultiPart *payload = new QHttpMultiPart(QHttpMultiPart::FormDataType); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 941b000fb..2fb6ddadf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,7 @@ set(chatterino_SOURCES ${CMAKE_SOURCE_DIR}/src/common/ChatterinoSetting.cpp ${CMAKE_SOURCE_DIR}/src/common/Modes.cpp + ${CMAKE_SOURCE_DIR}/src/common/NetworkCommon.cpp ${CMAKE_SOURCE_DIR}/src/common/NetworkManager.cpp ${CMAKE_SOURCE_DIR}/src/common/NetworkPrivate.cpp ${CMAKE_SOURCE_DIR}/src/common/NetworkRequest.cpp @@ -35,6 +36,7 @@ set(chatterino_SOURCES set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/NetworkCommon.cpp ${CMAKE_CURRENT_LIST_DIR}/src/NetworkRequest.cpp ${CMAKE_CURRENT_LIST_DIR}/src/UsernameSet.cpp ${CMAKE_CURRENT_LIST_DIR}/src/HighlightPhrase.cpp diff --git a/tests/src/NetworkCommon.cpp b/tests/src/NetworkCommon.cpp new file mode 100644 index 000000000..9db0efc7e --- /dev/null +++ b/tests/src/NetworkCommon.cpp @@ -0,0 +1,59 @@ +#include "common/NetworkCommon.hpp" + +#include + +using namespace chatterino; + +TEST(NetworkCommon, parseHeaderList1) +{ + const QString input = "Authorization:secretKey;NextHeader:boo"; + const std::vector> expected = { + {"Authorization", "secretKey"}, + {"NextHeader", "boo"}, + }; + + const auto actual = parseHeaderList(input); + + ASSERT_EQ(expected, actual); +} + +TEST(NetworkCommon, parseHeaderListTrimmed) +{ + const QString input = "Authorization: secretKey; NextHeader :boo"; + const std::vector> expected = { + {"Authorization", "secretKey"}, + {"NextHeader", "boo"}, + }; + + const auto actual = parseHeaderList(input); + + ASSERT_EQ(expected, actual); +} + +TEST(NetworkCommon, parseHeaderListColonInValue) +{ + // The input values first header pair contains an invalid value, too many colons. We expect this value to be skipped + const QString input = "Authorization: secretKey:hehe; NextHeader :boo"; + const std::vector> expected = { + {"Authorization", "secretKey:hehe"}, + {"NextHeader", "boo"}, + }; + + const auto actual = parseHeaderList(input); + + ASSERT_EQ(expected, actual); +} + +TEST(NetworkCommon, parseHeaderListBadPair) +{ + // The input values first header pair doesn't have a colon, so we don't know where the header name and value start/end + const QString input = "Authorization secretKeybad; NextHeader :boo"; + const std::vector> expected = { + {"NextHeader", "boo"}, + }; + + const auto actual = parseHeaderList(input); + + ASSERT_EQ(expected, actual); + ASSERT_EQ(1, actual.size()); +}