From f53b0a9e0f262c9a2350575eb60c1a6924527ebc Mon Sep 17 00:00:00 2001 From: Emil Gedda Date: Sat, 6 Mar 2021 19:56:36 +0100 Subject: [PATCH] Fix quotation and handling of additional streamlink options (#2495) --- CHANGELOG.md | 2 +- chatterino.pro | 1 + src/util/SplitCommand.cpp | 93 +++++++++++++++++++++++++++++++++++++++ src/util/SplitCommand.hpp | 15 +++++++ src/util/StreamLink.cpp | 21 +++------ 5 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 src/util/SplitCommand.cpp create mode 100644 src/util/SplitCommand.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eb6da8d..1d67ae9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,7 +54,7 @@ - Minor: Added human-readable formatting to remaining timeout duration. (#2398) - Minor: Update emojis version to 13 (2020). (#1555) - Minor: Remove EmojiOne 2 and 3 due to license restrictions. (#1555) -- Minor: Added `/streamlink` command. Usage: `/streamlink `. You can also use the command without arguments in any twitch channel to open it in streamlink. (#2443) +- Minor: Added `/streamlink` command. Usage: `/streamlink `. You can also use the command without arguments in any twitch channel to open it in streamlink. (#2443, #2495) - Minor: Humanized all numbers visible to end-users. (#2488) - Bugfix: Fix crash occurring when pressing Escape in the Color Picker Dialog (#1843) - Bugfix: Fix bug where the "check user follow state" event could trigger a network request requesting the user to follow or unfollow a user. By itself its quite harmless as it just repeats to Twitch the same follow state we had, so no follows should have been lost by this but it meant there was a rogue network request that was fired that could cause a crash (#1906) diff --git a/chatterino.pro b/chatterino.pro index 8ecf7662..aca8aa2d 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -246,6 +246,7 @@ SOURCES += \ src/util/RapidjsonHelpers.cpp \ src/util/StreamerMode.cpp \ src/util/StreamLink.cpp \ + src/util/SplitCommand.cpp \ src/util/Twitch.cpp \ src/util/WindowsHelper.cpp \ src/widgets/AccountSwitchPopup.cpp \ diff --git a/src/util/SplitCommand.cpp b/src/util/SplitCommand.cpp new file mode 100644 index 00000000..09c35afc --- /dev/null +++ b/src/util/SplitCommand.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "SplitCommand.hpp" + +#include +#include +#include + +QStringList chatterino::splitCommand(QStringView command) +{ + QStringList args; + QString tmp; + int quoteCount = 0; + bool inQuote = false; + + // handle quoting. tokens can be surrounded by double quotes + // "hello world". three consecutive double quotes represent + // the quote character itself. + for (int i = 0; i < command.size(); ++i) + { + if (command.at(i) == QLatin1Char('"')) + { + ++quoteCount; + if (quoteCount == 3) + { + // third consecutive quote + quoteCount = 0; + tmp += command.at(i); + } + continue; + } + if (quoteCount) + { + if (quoteCount == 1) + inQuote = !inQuote; + quoteCount = 0; + } + if (!inQuote && command.at(i).isSpace()) + { + if (!tmp.isEmpty()) + { + args += tmp; + tmp.clear(); + } + } + else + { + tmp += command.at(i); + } + } + if (!tmp.isEmpty()) + args += tmp; + + return args; +} diff --git a/src/util/SplitCommand.hpp b/src/util/SplitCommand.hpp new file mode 100644 index 00000000..9db79c72 --- /dev/null +++ b/src/util/SplitCommand.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace chatterino { + +// Splits the string command into a list of tokens, and returns the list. +// Tokens with spaces can be surrounded by double quotes; +// three consecutive double quotes represent the quote character itself. +// +// Backported from QProcess 5.15 +QStringList splitCommand(QStringView command); + +} // namespace chatterino diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp index 46463724..4251425e 100644 --- a/src/util/StreamLink.cpp +++ b/src/util/StreamLink.cpp @@ -3,6 +3,7 @@ #include "Application.hpp" #include "singletons/Settings.hpp" #include "util/Helpers.hpp" +#include "util/SplitCommand.hpp" #include "widgets/dialogs/QualityPopup.hpp" #include @@ -170,22 +171,14 @@ void getStreamQualities(const QString &channelURL, void openStreamlink(const QString &channelURL, const QString &quality, QStringList extraArguments) { - QStringList arguments; + QStringList arguments = extraArguments << channelURL << quality; + + // Remove empty arguments before appending additional streamlink options + // as the options might purposely contain empty arguments + arguments.removeAll(QString()); QString additionalOptions = getSettings()->streamlinkOpts.getValue(); - if (!additionalOptions.isEmpty()) - { - arguments << getSettings()->streamlinkOpts; - } - - arguments.append(extraArguments); - - arguments << channelURL; - - if (!quality.isEmpty()) - { - arguments << quality; - } + arguments << splitCommand(additionalOptions); bool res = QProcess::startDetached(getStreamlinkProgram(), arguments);