Files
obs-studio/frontend/widgets/OBSBasic_Browser.cpp
Sebastian Beckmann 649beed45c frontend: Don't store QT_TO_UTF8 to std::string
QT_TO_UTF8 returns a const char * that, in general, shouldn't be stored.
This is because QT_TO_UTF8(str) expands to str.toUtf8().constData():
toUtf8() returns a QByteArray, and constData() the pointer to its data
which is only valid until the QByteArray goes out of scope, which is
immediately after the call.
The original code that is changed here only works because in all of the
situations, the object that is stored to is actually a std::string that
gets constructed implicitly, so the constData() pointer is valid long
enough for the std::string constructor to copy the data.

The issue is that any "... = QT_TO_UTF8" code *looks* unsafe, and may
lead new or unfamiliar contributors to assume that they can also use it,
only to do "const char *... = QT_TO_UTF8(...)" which is dangerous.
Additionally, it introduces an unnecessary round of implicit conversions
and copies when QString.toStdString() already exists and copies into the
string buffer directly.
2026-05-06 14:15:44 -04:00

286 lines
7.3 KiB
C++

/******************************************************************************
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSBasic.hpp"
#ifdef BROWSER_AVAILABLE
#include <dialogs/OBSExtraBrowsers.hpp>
#include <docks/BrowserDock.hpp>
#include <json11.hpp>
#include <qt-wrappers.hpp>
#include <QDir>
using namespace json11;
#endif
#include <random>
struct QCef;
struct QCefCookieManager;
QCef *cef = nullptr;
QCefCookieManager *panel_cookies = nullptr;
bool cef_js_avail = false;
#ifdef BROWSER_AVAILABLE
void OBSBasic::ClearExtraBrowserDocks()
{
extraBrowserDockTargets.clear();
extraBrowserDockNames.clear();
extraBrowserDocks.clear();
}
void OBSBasic::LoadExtraBrowserDocks()
{
const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks");
std::string err;
Json json = Json::parse(jsonStr, err);
if (!err.empty())
return;
Json::array array = json.array_items();
if (!array.empty())
extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator();
for (Json &item : array) {
std::string title = item["title"].string_value();
std::string url = item["url"].string_value();
std::string uuid = item["uuid"].string_value();
AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false);
}
}
void OBSBasic::SaveExtraBrowserDocks()
{
Json::array array;
for (int i = 0; i < extraBrowserDocks.size(); i++) {
QDockWidget *dock = extraBrowserDocks[i].get();
QString title = extraBrowserDockNames[i];
QString url = extraBrowserDockTargets[i];
QString uuid = dock->property("uuid").toString();
Json::object obj{
{"title", QT_TO_UTF8(title)},
{"url", QT_TO_UTF8(url)},
{"uuid", QT_TO_UTF8(uuid)},
};
array.push_back(obj);
}
std::string output = Json(array).dump();
config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str());
}
void OBSBasic::ManageExtraBrowserDocks()
{
if (!extraBrowsers.isNull()) {
extraBrowsers->show();
extraBrowsers->raise();
return;
}
extraBrowsers = new OBSExtraBrowsers(this);
extraBrowsers->show();
}
void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate)
{
static int panel_version = -1;
if (panel_version == -1) {
panel_version = obs_browser_qcef_version();
}
BrowserDock *dock = new BrowserDock(title);
QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid);
bId.replace(QRegularExpression("[{}-]"), "");
dock->setProperty("uuid", bId);
dock->setObjectName(title + OBJ_NAME_SUFFIX);
dock->resize(460, 600);
dock->setMinimumSize(80, 80);
dock->setWindowTitle(title);
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr);
if (browser && panel_version >= 1)
browser->allowAllPopups(true);
dock->SetWidget(browser);
/* Add support for Twitch Dashboard panels */
if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) {
QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/");
QRegularExpressionMatch match = re.match(url);
QString username = match.captured(1);
if (username.length() > 0) {
std::string script;
script = "Object.defineProperty(document, 'referrer', { get: () => '";
script += "https://twitch.tv/";
script += username.toStdString();
script += "/dashboard/live";
script += "'});";
browser->setStartupScript(script);
}
}
AddDockWidget(dock, Qt::RightDockWidgetArea, true);
extraBrowserDocks.push_back(std::shared_ptr<QDockWidget>(dock));
extraBrowserDockNames.push_back(title);
extraBrowserDockTargets.push_back(url);
if (firstCreate) {
dock->setFloating(true);
QPoint curPos = pos();
QSize wSizeD2 = size() / 2;
QSize dSizeD2 = dock->size() / 2;
curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width());
curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height());
dock->move(curPos);
dock->setVisible(true);
}
}
#endif
static std::string GenId()
{
std::random_device rd;
std::mt19937_64 e2(rd());
std::uniform_int_distribution<uint64_t> dist(0, 0xFFFFFFFFFFFFFFFF);
uint64_t id = dist(e2);
char id_str[20];
snprintf(id_str, sizeof(id_str), "%016llX", (unsigned long long)id);
return std::string(id_str);
}
void CheckExistingCookieId()
{
OBSBasic *main = OBSBasic::Get();
if (config_has_user_value(main->Config(), "Panels", "CookieId"))
return;
config_set_string(main->Config(), "Panels", "CookieId", GenId().c_str());
}
#ifdef BROWSER_AVAILABLE
static void InitPanelCookieManager()
{
if (!cef)
return;
if (panel_cookies)
return;
CheckExistingCookieId();
OBSBasic *main = OBSBasic::Get();
const char *cookie_id = config_get_string(main->Config(), "Panels", "CookieId");
std::string sub_path;
sub_path += "obs_profile_cookies/";
sub_path += cookie_id;
panel_cookies = cef->create_cookie_manager(sub_path);
}
#endif
void DestroyPanelCookieManager()
{
#ifdef BROWSER_AVAILABLE
if (panel_cookies) {
panel_cookies->FlushStore();
delete panel_cookies;
panel_cookies = nullptr;
}
#endif
}
void DeleteCookies()
{
#ifdef BROWSER_AVAILABLE
if (panel_cookies) {
panel_cookies->DeleteCookies("", "");
}
#endif
}
void DuplicateCurrentCookieProfile(ConfigFile &config)
{
#ifdef BROWSER_AVAILABLE
if (cef) {
OBSBasic *main = OBSBasic::Get();
std::string cookie_id = config_get_string(main->Config(), "Panels", "CookieId");
std::string src_path;
src_path += "obs_profile_cookies/";
src_path += cookie_id;
std::string new_id = GenId();
std::string dst_path;
dst_path += "obs_profile_cookies/";
dst_path += new_id;
BPtr<char> src_path_full = cef->get_cookie_path(src_path);
BPtr<char> dst_path_full = cef->get_cookie_path(dst_path);
QDir srcDir(src_path_full.Get());
QDir dstDir(dst_path_full.Get());
if (srcDir.exists()) {
if (!dstDir.exists())
dstDir.mkdir(dst_path_full.Get());
QStringList files = srcDir.entryList(QDir::Files);
for (const QString &file : files) {
QString src = QString(src_path_full);
QString dst = QString(dst_path_full);
src += QDir::separator() + file;
dst += QDir::separator() + file;
QFile::copy(src, dst);
}
}
config_set_string(config, "Panels", "CookieId", cookie_id.c_str());
config_set_string(main->Config(), "Panels", "CookieId", new_id.c_str());
}
#else
UNUSED_PARAMETER(config);
#endif
}
void OBSBasic::InitBrowserPanelSafeBlock()
{
#ifdef BROWSER_AVAILABLE
if (!cef)
return;
if (cef->init_browser()) {
InitPanelCookieManager();
return;
}
ExecThreadedWithoutBlocking([] { cef->wait_for_browser_init(); }, QTStr("BrowserPanelInit.Title"),
QTStr("BrowserPanelInit.Text"));
InitPanelCookieManager();
#endif
}