From 699ad316f0d90580fa13d570d6c25aff903a470d Mon Sep 17 00:00:00 2001 From: timoreo22 Date: Sat, 28 May 2022 21:53:12 +0200 Subject: Rework curseforge download (#611) * Use the bulk endpoint on mod resolution for faster download * Search on modrinth for api blocked mods * Display a dialog for manually downloading blocked mods --- launcher/net/Upload.cpp | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 launcher/net/Upload.cpp (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp new file mode 100644 index 00000000..bbd27390 --- /dev/null +++ b/launcher/net/Upload.cpp @@ -0,0 +1,199 @@ +// +// Created by timoreo on 20/05/22. +// + +#include "Upload.h" + +#include +#include "ByteArraySink.h" +#include "BuildConfig.h" +#include "Application.h" + +namespace Net { + + void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + setProgress(bytesReceived, bytesTotal); + } + + void Upload::downloadError(QNetworkReply::NetworkError error) { + if (error == QNetworkReply::OperationCanceledError) { + qCritical() << "Aborted " << m_url.toString(); + m_state = State::AbortedByUser; + } else { + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_state = State::Failed; + } + } + + void Upload::sslErrors(const QList &errors) { + int i = 1; + for (const auto& error : errors) { + qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } + } + + bool Upload::handleRedirect() + { + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if (!redirect.isValid()) { + if (!m_reply->hasRawHeader("Location")) { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if (redirectBA.size() == 0) { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); + + if (redirectStr.startsWith("//")) { + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 + */ + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } else if (redirectStr.startsWith("/")) { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); + } + + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if (!redirect.isValid()) { + qWarning() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qDebug() << "Fixed location header:" << redirect; + } else { + qDebug() << "Location header:" << redirect; + } + + m_url = QUrl(redirect.toString()); + qDebug() << "Following redirect to " << m_url.toString(); + startAction(m_network); + return true; + } + + void Upload::downloadFinished() { + // handle HTTP redirection first + // very unlikely for post requests, still can happen + if (handleRedirect()) { + qDebug() << "Upload redirected:" << m_url.toString(); + return; + } + + // if the download failed before this point ... + if (m_state == State::Succeeded) { + qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit succeeded(); + return; + } else if (m_state == State::Failed) { + qDebug() << "Upload failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(""); + return; + } else if (m_state == State::AbortedByUser) { + qDebug() << "Upload aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(); + return; + } + + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if (data.size()) { + qDebug() << "Writing extra" << data.size() << "bytes"; + m_state = m_sink->write(data); + } + + // otherwise, finalize the whole graph + m_state = m_sink->finalize(*m_reply.get()); + if (m_state != State::Succeeded) { + qDebug() << "Upload failed to finalize:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(""); + return; + } + m_reply.reset(); + qDebug() << "Upload succeeded:" << m_url.toString(); + emit succeeded(); + } + + void Upload::downloadReadyRead() { + if (m_state == State::Running) { + auto data = m_reply->readAll(); + m_state = m_sink->write(data); + } + } + + void Upload::executeTask() { + setStatus(tr("Uploading %1").arg(m_url.toString())); + + if (m_state == State::AbortedByUser) { + qWarning() << "Attempt to start an aborted Upload:" << m_url.toString(); + emit aborted(); + return; + } + QNetworkRequest request(m_url); + m_state = m_sink->init(request); + switch (m_state) { + case State::Succeeded: + emitSucceeded(); + qDebug() << "Upload cache hit " << m_url.toString(); + return; + case State::Running: + qDebug() << "Uploading " << m_url.toString(); + break; + case State::Inactive: + case State::Failed: + emitFailed(""); + return; + case State::AbortedByUser: + emitAborted(); + return; + } + + request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + if (request.url().host().contains("api.curseforge.com")) { + request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); + } + //TODO other types of post requests ? + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkReply* rep = m_network->post(request, m_post_data); + + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); + } + + Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) { + auto* up = new Upload(); + up->m_url = std::move(url); + up->m_sink.reset(new ByteArraySink(output)); + up->m_post_data = std::move(m_post_data); + return up; + } +} // Net -- cgit From 25ab121e42f624352bb4f32faa29e9e455328f09 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 4 Jun 2022 15:33:17 +0800 Subject: feat: custom user-agent --- launcher/Application.cpp | 21 +++++++++++++++++++++ launcher/Application.h | 2 ++ launcher/net/Download.cpp | 2 +- launcher/net/PasteUpload.cpp | 4 ++-- launcher/net/Upload.cpp | 2 +- launcher/screenshots/ImgurAlbumCreation.cpp | 2 +- launcher/screenshots/ImgurUpload.cpp | 3 ++- launcher/ui/pages/global/APIPage.cpp | 4 ++++ launcher/ui/pages/global/APIPage.ui | 27 ++++++++++++++++++++++++++- 9 files changed, 60 insertions(+), 7 deletions(-) (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ba4096b6..dd6f8ec6 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -708,6 +708,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Custom MSA credentials m_settings->registerSetting("MSAClientIDOverride", ""); m_settings->registerSetting("CFKeyOverride", ""); + m_settings->registerSetting("UserAgentOverride", ""); // Init page provider { @@ -1553,3 +1554,23 @@ QString Application::getCurseKey() return BuildConfig.CURSEFORGE_API_KEY; } + +QString Application::getUserAgent() +{ + QString keyOverride = m_settings->get("UserAgentOverride").toString(); + if (!keyOverride.isEmpty()) { + return keyOverride; + } + + return BuildConfig.USER_AGENT; +} + +QString Application::getUserAgentUncached() +{ + QString keyOverride = m_settings->get("UserAgentOverride").toString(); + if (!keyOverride.isEmpty()) { + return keyOverride; + } + + return BuildConfig.USER_AGENT_UNCACHED; +} diff --git a/launcher/Application.h b/launcher/Application.h index 3129b4fb..f440f433 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -156,6 +156,8 @@ public: QString getMSAClientID(); QString getCurseKey(); + QString getUserAgent(); + QString getUserAgentUncached(); /// this is the root of the 'installation'. Used for automatic updates const QString &root() { diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 966d4126..d93eb088 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -116,7 +116,7 @@ void Download::executeTask() return; } - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); if (request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); }; diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 3855190a..ead5e170 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -71,7 +71,7 @@ void PasteUpload::executeTask() QNetworkRequest request{QUrl(m_uploadUrl)}; QNetworkReply *rep{}; - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); switch (m_pasteType) { case NullPointer: { @@ -91,7 +91,7 @@ void PasteUpload::executeTask() break; } case Hastebin: { - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); rep = APPLICATION->network()->post(request, m_text); break; } diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index bbd27390..c9942a8d 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -173,7 +173,7 @@ namespace Net { return; } - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); if (request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8()); } diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 7afdc5cc..04e26ea2 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -55,7 +55,7 @@ void ImgurAlbumCreation::executeTask() { m_state = State::Running; QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); request.setRawHeader("Accept", "application/json"); diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index fbcfb95f..9aeb6fb8 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -35,6 +35,7 @@ #include "ImgurUpload.h" #include "BuildConfig.h" +#include "Application.h" #include #include @@ -56,7 +57,7 @@ void ImgurUpload::executeTask() finished = false; m_state = Task::State::Running; QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str()); request.setRawHeader("Accept", "application/json"); diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 5d812d07..0c1d7ca2 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -78,6 +78,7 @@ APIPage::APIPage(QWidget *parent) : ui->tabWidget->tabBar()->hide(); ui->metaURL->setPlaceholderText(BuildConfig.META_URL); + ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); loadSettings(); @@ -139,6 +140,8 @@ void APIPage::loadSettings() ui->metaURL->setText(metaURL); QString curseKey = s->get("CFKeyOverride").toString(); ui->curseKey->setText(curseKey); + QString customUserAgent = s->get("UserAgentOverride").toString(); + ui->userAgentLineEdit->setText(customUserAgent); } void APIPage::applySettings() @@ -167,6 +170,7 @@ void APIPage::applySettings() s->set("MetaURLOverride", metaURL); QString curseKey = ui->curseKey->text(); s->set("CFKeyOverride", curseKey); + s->set("UserAgentOverride", ui->userAgentLineEdit->text()); } bool APIPage::apply() diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 5c927391..0981c700 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -7,7 +7,7 @@ 0 0 800 - 600 + 702 @@ -220,6 +220,31 @@ + + + + + 0 + 0 + + + + User Agent + + + + + + + + + Enter a custom User Agent here. The special string ${launcher_version} will be replaced with the version of the launcher. + + + + + + -- cgit