Removed AuthRequest and NetAction

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-05-13 23:25:08 +03:00
parent f15981d786
commit 3336f8107c
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
50 changed files with 381 additions and 743 deletions

View File

@ -126,7 +126,6 @@ set(NET_SOURCES
net/MetaCacheSink.h net/MetaCacheSink.h
net/Logging.h net/Logging.h
net/Logging.cpp net/Logging.cpp
net/NetAction.h
net/NetJob.cpp net/NetJob.cpp
net/NetJob.h net/NetJob.h
net/NetUtils.h net/NetUtils.h
@ -212,8 +211,6 @@ set(MINECRAFT_SOURCES
minecraft/auth/AccountList.h minecraft/auth/AccountList.h
minecraft/auth/AccountTask.cpp minecraft/auth/AccountTask.cpp
minecraft/auth/AccountTask.h minecraft/auth/AccountTask.h
minecraft/auth/AuthRequest.cpp
minecraft/auth/AuthRequest.h
minecraft/auth/AuthSession.cpp minecraft/auth/AuthSession.cpp
minecraft/auth/AuthSession.h minecraft/auth/AuthSession.h
minecraft/auth/AuthStep.cpp minecraft/auth/AuthStep.cpp
@ -624,7 +621,6 @@ set(PRISMUPDATER_SOURCES
net/HttpMetaCache.h net/HttpMetaCache.h
net/Logging.h net/Logging.h
net/Logging.cpp net/Logging.cpp
net/NetAction.h
net/NetRequest.cpp net/NetRequest.cpp
net/NetRequest.h net/NetRequest.h
net/NetJob.cpp net/NetJob.cpp

View File

@ -3,8 +3,6 @@
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
InstanceCreationTask::InstanceCreationTask() = default;
void InstanceCreationTask::executeTask() void InstanceCreationTask::executeTask()
{ {
setAbortable(true); setAbortable(true);

View File

@ -6,7 +6,7 @@
class InstanceCreationTask : public InstanceTask { class InstanceCreationTask : public InstanceTask {
Q_OBJECT Q_OBJECT
public: public:
InstanceCreationTask(); InstanceCreationTask() = default;
virtual ~InstanceCreationTask() = default; virtual ~InstanceCreationTask() = default;
protected: protected:

View File

@ -51,6 +51,7 @@
#include "net/Download.h" #include "net/Download.h"
#include "Application.h" #include "Application.h"
#include "net/NetRequest.h"
namespace { namespace {
QSet<QString> collectPathsFromDir(QString dirPath) QSet<QString> collectPathsFromDir(QString dirPath)
@ -276,7 +277,7 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
} // namespace AssetsUtils } // namespace AssetsUtils
NetAction::Ptr AssetObject::getDownloadAction() Net::NetRequest::Ptr AssetObject::getDownloadAction()
{ {
QFileInfo objectFile(getLocalPath()); QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size)) { if ((!objectFile.isFile()) || (objectFile.size() != size)) {

View File

@ -17,14 +17,14 @@
#include <QMap> #include <QMap>
#include <QString> #include <QString>
#include "net/NetAction.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/NetRequest.h"
struct AssetObject { struct AssetObject {
QString getRelPath(); QString getRelPath();
QUrl getUrl(); QUrl getUrl();
QString getLocalPath(); QString getLocalPath();
NetAction::Ptr getDownloadAction(); Net::NetRequest::Ptr getDownloadAction();
QString hash; QString hash;
qint64 size; qint64 size;

View File

@ -35,6 +35,7 @@
#include "Library.h" #include "Library.h"
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include "net/NetRequest.h"
#include <BuildConfig.h> #include <BuildConfig.h>
#include <FileSystem.h> #include <FileSystem.h>
@ -74,12 +75,12 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
} }
} }
QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext, QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
class HttpMetaCache* cache, class HttpMetaCache* cache,
QStringList& failedLocalFiles, QStringList& failedLocalFiles,
const QString& overridePath) const const QString& overridePath) const
{ {
QList<NetAction::Ptr> out; QList<Net::NetRequest::Ptr> out;
bool stale = isAlwaysStale(); bool stale = isAlwaysStale();
bool local = isLocal(); bool local = isLocal();

View File

@ -34,7 +34,6 @@
*/ */
#pragma once #pragma once
#include <net/NetAction.h>
#include <QDir> #include <QDir>
#include <QList> #include <QList>
#include <QMap> #include <QMap>
@ -48,6 +47,7 @@
#include "MojangDownloadInfo.h" #include "MojangDownloadInfo.h"
#include "Rule.h" #include "Rule.h"
#include "RuntimeContext.h" #include "RuntimeContext.h"
#include "net/NetRequest.h"
class Library; class Library;
class MinecraftInstance; class MinecraftInstance;
@ -144,10 +144,10 @@ class Library {
bool isForge() const; bool isForge() const;
// Get a list of downloads for this library // Get a list of downloads for this library
QList<NetAction::Ptr> getDownloads(const RuntimeContext& runtimeContext, QList<Net::NetRequest::Ptr> getDownloads(const RuntimeContext& runtimeContext,
class HttpMetaCache* cache, class HttpMetaCache* cache,
QStringList& failedLocalFiles, QStringList& failedLocalFiles,
const QString& overridePath) const; const QString& overridePath) const;
QString getCompatibleNative(const RuntimeContext& runtimeContext) const; QString getCompatibleNative(const RuntimeContext& runtimeContext) const;

View File

@ -36,6 +36,7 @@
#include "AccountList.h" #include "AccountList.h"
#include "AccountData.h" #include "AccountData.h"
#include "AccountTask.h" #include "AccountTask.h"
#include "tasks/Task.h"
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@ -639,8 +640,8 @@ void AccountList::tryNext()
if (account->internalId() == accountId) { if (account->internalId() == accountId) {
m_currentTask = account->refresh(); m_currentTask = account->refresh();
if (m_currentTask) { if (m_currentTask) {
connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded);
connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed); connect(m_currentTask.get(), &Task::failed, this, &AccountList::authFailed);
m_currentTask->start(); m_currentTask->start();
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID " qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
<< accountId; << accountId;

View File

@ -1,175 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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, version 3.
*
* 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 <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <QBuffer>
#include <QDebug>
#include <QTimer>
#include <QUrlQuery>
#include "Application.h"
#include "AuthRequest.h"
#include "katabasis/Globals.h"
AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {}
AuthRequest::~AuthRequest() {}
void AuthRequest::get(const QNetworkRequest& req, int timeout /* = 60*1000*/)
{
setup(req, QNetworkAccessManager::GetOperation);
reply_ = APPLICATION->network()->get(request_);
status_ = Requesting;
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
#else // &QNetworkReply::error SIGNAL depricated
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
#endif
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
}
void AuthRequest::post(const QNetworkRequest& req, const QByteArray& data, int timeout /* = 60*1000*/)
{
setup(req, QNetworkAccessManager::PostOperation);
data_ = data;
status_ = Requesting;
reply_ = APPLICATION->network()->post(request_, data_);
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
#else // &QNetworkReply::error SIGNAL depricated
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
#endif
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
}
void AuthRequest::onRequestFinished()
{
if (status_ == Idle) {
return;
}
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
return;
}
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
finish();
}
void AuthRequest::onRequestError(QNetworkReply::NetworkError error)
{
qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
if (status_ == Idle) {
return;
}
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
return;
}
errorString_ = reply_->errorString();
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
error_ = error;
qWarning() << "AuthRequest::onRequestError: Error string: " << errorString_;
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_
<< reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
// QTimer::singleShot(10, this, SLOT(finish()));
}
void AuthRequest::onSslErrors(QList<QSslError> errors)
{
int i = 1;
for (auto error : errors) {
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
}
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total)
{
if (status_ == Idle) {
qWarning() << "AuthRequest::onUploadProgress: No pending request";
return;
}
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
return;
}
// Restart timeout because request in progress
Katabasis::Reply* o2Reply = timedReplies_.find(reply_);
if (o2Reply) {
o2Reply->start();
}
emit uploadProgress(uploaded, total);
}
void AuthRequest::setup(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& verb)
{
request_ = req;
operation_ = operation;
url_ = req.url();
QUrl url = url_;
request_.setUrl(url);
if (!verb.isEmpty()) {
request_.setRawHeader(Katabasis::HTTP_HTTP_HEADER, verb);
}
status_ = Requesting;
error_ = QNetworkReply::NoError;
errorString_.clear();
httpStatus_ = 0;
}
void AuthRequest::finish()
{
QByteArray data;
if (status_ == Idle) {
qWarning() << "AuthRequest::finish: No pending request";
return;
}
data = reply_->readAll();
status_ = Idle;
timedReplies_.remove(reply_);
reply_->disconnect(this);
reply_->deleteLater();
QList<QNetworkReply::RawHeaderPair> headers = reply_->rawHeaderPairs();
emit finished(error_, data, headers);
}

View File

@ -1,67 +0,0 @@
#pragma once
#include <QByteArray>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QObject>
#include <QUrl>
#include "katabasis/Reply.h"
/// Makes authentication requests.
class AuthRequest : public QObject {
Q_OBJECT
public:
explicit AuthRequest(QObject* parent = 0);
~AuthRequest();
public slots:
void get(const QNetworkRequest& req, int timeout = 60 * 1000);
void post(const QNetworkRequest& req, const QByteArray& data, int timeout = 60 * 1000);
signals:
/// Emitted when a request has been completed or failed.
void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
/// Emitted when an upload has progressed.
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
protected slots:
/// Handle request finished.
void onRequestFinished();
/// Handle request error.
void onRequestError(QNetworkReply::NetworkError error);
/// Handle ssl errors.
void onSslErrors(QList<QSslError> errors);
/// Finish the request, emit finished() signal.
void finish();
/// Handle upload progress.
void onUploadProgress(qint64 uploaded, qint64 total);
public:
QNetworkReply::NetworkError error_;
int httpStatus_ = 0;
QString errorString_;
protected:
void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray());
enum Status { Idle, Requesting, ReRequesting };
QNetworkRequest request_;
QByteArray data_;
QNetworkReply* reply_;
Status status_;
QNetworkAccessManager::Operation operation_;
QUrl url_;
Katabasis::ReplyList timedReplies_;
QTimer* timer_;
};

View File

@ -1,5 +1,3 @@
#include "AuthStep.h" #include "AuthStep.h"
AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {} AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {}
AuthStep::~AuthStep() noexcept = default;

View File

@ -15,13 +15,12 @@ class AuthStep : public QObject {
public: public:
explicit AuthStep(AccountData* data); explicit AuthStep(AccountData* data);
virtual ~AuthStep() noexcept; virtual ~AuthStep() noexcept = default;
virtual QString describe() = 0; virtual QString describe() = 0;
public slots: public slots:
virtual void perform() = 0; virtual void perform() = 0;
virtual void rehydrate() = 0;
signals: signals:
void finished(AccountTaskState resultingState, QString message); void finished(AccountTaskState resultingState, QString message);

View File

@ -153,7 +153,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
if (data.type == AccountType::MSA) { if (data.type == AccountType::MSA) {
m_currentTask.reset(new MSASilent(&data)); m_currentTask.reset(new MSASilent(&data));
} else { } else {
m_currentTask.reset(new OfflineRefresh(&data)); m_currentTask.reset(new OfflineLogin(&data));
} }
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);

View File

@ -2,11 +2,6 @@
#include "minecraft/auth/steps/OfflineStep.h" #include "minecraft/auth/steps/OfflineStep.h"
OfflineRefresh::OfflineRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<OfflineStep>(m_data));
}
OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent) OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{ {
m_steps.append(makeShared<OfflineStep>(m_data)); m_steps.append(makeShared<OfflineStep>(m_data));

View File

@ -1,12 +1,6 @@
#pragma once #pragma once
#include "AuthFlow.h" #include "AuthFlow.h"
class OfflineRefresh : public AuthFlow {
Q_OBJECT
public:
explicit OfflineRefresh(AccountData* data, QObject* parent = 0);
};
class OfflineLogin : public AuthFlow { class OfflineLogin : public AuthFlow {
Q_OBJECT Q_OBJECT
public: public:

View File

@ -1,16 +1,20 @@
#include "EntitlementsStep.h" #include "EntitlementsStep.h"
#include <QList>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrl>
#include <QUuid> #include <QUuid>
#include <memory>
#include "Application.h"
#include "Logging.h" #include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/Download.h"
#include "net/StaticHeaderProxy.h"
#include "tasks/Task.h"
EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {} EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
EntitlementsStep::~EntitlementsStep() noexcept = default;
QString EntitlementsStep::describe() QString EntitlementsStep::describe()
{ {
return tr("Determining game ownership."); return tr("Determining game ownership.");
@ -19,35 +23,31 @@ QString EntitlementsStep::describe()
void EntitlementsStep::perform() void EntitlementsStep::perform()
{ {
auto uuid = QUuid::createUuid(); auto uuid = QUuid::createUuid();
m_entitlementsRequestId = uuid.toString().remove('{').remove('}'); m_entitlements_request_id = uuid.toString().remove('{').remove('}');
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId;
QNetworkRequest request = QNetworkRequest(url); QUrl url("https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlements_request_id);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
request.setRawHeader("Accept", "application/json"); { "Accept", "application/json" },
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); { "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone); m_response.reset(new QByteArray());
requestor->get(request); m_task = Net::Download::makeByteArray(url, m_response);
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_task.get(), &Task::finished, this, &EntitlementsStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting entitlements..."; qDebug() << "Getting entitlements...";
} }
void EntitlementsStep::rehydrate() void EntitlementsStep::onRequestDone()
{ {
// NOOP, for now. We only save bools and there's nothing to check. qCDebug(authCredentials()) << *m_response;
}
void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error,
QByteArray data,
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
// TODO: check presence of same entitlementsRequestId? // TODO: check presence of same entitlementsRequestId?
// TODO: validate JWTs? // TODO: validate JWTs?
Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement); Parsers::parseMinecraftEntitlements(*m_response, m_data->minecraftEntitlement);
emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements")); emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements"));
} }

View File

@ -1,24 +1,26 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
class EntitlementsStep : public AuthStep { class EntitlementsStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit EntitlementsStep(AccountData* data); explicit EntitlementsStep(AccountData* data);
virtual ~EntitlementsStep() noexcept; virtual ~EntitlementsStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private: private:
QString m_entitlementsRequestId; QString m_entitlements_request_id;
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
}; };

View File

@ -3,13 +3,10 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "minecraft/auth/AuthRequest.h" #include "Application.h"
#include "minecraft/auth/Parsers.h"
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {} GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {}
GetSkinStep::~GetSkinStep() noexcept = default;
QString GetSkinStep::describe() QString GetSkinStep::describe()
{ {
return tr("Getting skin."); return tr("Getting skin.");
@ -17,25 +14,20 @@ QString GetSkinStep::describe()
void GetSkinStep::perform() void GetSkinStep::perform()
{ {
auto url = QUrl(m_data->minecraftProfile.skin.url); QUrl url(m_data->minecraftProfile.skin.url);
QNetworkRequest request = QNetworkRequest(url);
AuthRequest* requestor = new AuthRequest(this); m_response.reset(new QByteArray());
connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone); m_task = Net::Download::makeByteArray(url, m_response);
requestor->get(request);
connect(m_task.get(), &Task::finished, this, &GetSkinStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
} }
void GetSkinStep::rehydrate() void GetSkinStep::onRequestDone()
{ {
// NOOP, for now. if (m_task->error() == QNetworkReply::NoError)
} m_data->minecraftProfile.skin.data = *m_response;
void GetSkinStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error == QNetworkReply::NoError) {
m_data->minecraftProfile.skin.data = data;
}
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin")); emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
} }

View File

@ -1,21 +1,25 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
class GetSkinStep : public AuthStep { class GetSkinStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit GetSkinStep(AccountData* data); explicit GetSkinStep(AccountData* data);
virtual ~GetSkinStep() noexcept; virtual ~GetSkinStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
}; };

View File

@ -1,17 +1,18 @@
#include "LauncherLoginStep.h" #include "LauncherLoginStep.h"
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrl>
#include "Application.h"
#include "Logging.h" #include "Logging.h"
#include "minecraft/auth/AccountTask.h" #include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h"
#include "net/Upload.h"
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {} LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
LauncherLoginStep::~LauncherLoginStep() noexcept = default;
QString LauncherLoginStep::describe() QString LauncherLoginStep::describe()
{ {
return tr("Accessing Mojang services."); return tr("Accessing Mojang services.");
@ -19,7 +20,7 @@ QString LauncherLoginStep::describe()
void LauncherLoginStep::perform() void LauncherLoginStep::perform()
{ {
auto requestURL = "https://api.minecraftservices.com/launcher/login"; QUrl url("https://api.minecraftservices.com/launcher/login");
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString(); auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
auto xToken = m_data->mojangservicesToken.token; auto xToken = m_data->mojangservicesToken.token;
@ -31,40 +32,37 @@ void LauncherLoginStep::perform()
)XXX"; )XXX";
auto requestBody = mc_auth_template.arg(uhs, xToken); auto requestBody = mc_auth_template.arg(uhs, xToken);
QNetworkRequest request = QNetworkRequest(QUrl(requestURL)); auto headers = QList<Net::HeaderPair>{
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); { "Content-Type", "application/json" },
request.setRawHeader("Accept", "application/json"); { "Accept", "application/json" },
AuthRequest* requestor = new AuthRequest(this); };
connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone);
requestor->post(request, requestBody.toUtf8()); m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8());
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_task.get(), &Task::finished, this, &LauncherLoginStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting Minecraft access token..."; qDebug() << "Getting Minecraft access token...";
} }
void LauncherLoginStep::rehydrate() void LauncherLoginStep::onRequestDone()
{ {
// TODO: check the token validity qCDebug(authCredentials()) << *m_response;
} if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_task->error();
void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) if (Net::isApplicationError(m_task->error())) {
{ emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
qCDebug(authCredentials()) << data;
if (Net::isApplicationError(error)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
} else { } else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
} }
return; return;
} }
if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) { if (!Parsers::parseMojangResponse(*m_response, m_data->yggdrasilToken)) {
qWarning() << "Could not parse login_with_xbox response..."; qWarning() << "Could not parse login_with_xbox response...";
qCDebug(authCredentials()) << data;
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response.")); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response."));
return; return;
} }

View File

@ -1,21 +1,25 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Upload.h"
class LauncherLoginStep : public AuthStep { class LauncherLoginStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit LauncherLoginStep(AccountData* data); explicit LauncherLoginStep(AccountData* data);
virtual ~LauncherLoginStep() noexcept; virtual ~LauncherLoginStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private:
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
}; };

View File

@ -37,10 +37,6 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "BuildConfig.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "Application.h" #include "Application.h"
#include "Logging.h" #include "Logging.h"
@ -63,27 +59,11 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac
connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode); connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode);
} }
MSAStep::~MSAStep() noexcept = default;
QString MSAStep::describe() QString MSAStep::describe()
{ {
return tr("Logging in with Microsoft account."); return tr("Logging in with Microsoft account.");
} }
void MSAStep::rehydrate()
{
switch (m_action) {
case Refresh: {
// TODO: check the tokens and see if they are old (older than a day)
return;
}
case Login: {
// NOOP
return;
}
}
}
void MSAStep::perform() void MSAStep::perform()
{ {
switch (m_action) { switch (m_action) {

View File

@ -48,10 +48,9 @@ class MSAStep : public AuthStep {
public: public:
explicit MSAStep(AccountData* data, Action action); explicit MSAStep(AccountData* data, Action action);
virtual ~MSAStep() noexcept; virtual ~MSAStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;

View File

@ -2,15 +2,13 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h" #include "Application.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h"
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {} MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
MinecraftProfileStep::~MinecraftProfileStep() noexcept = default;
QString MinecraftProfileStep::describe() QString MinecraftProfileStep::describe()
{ {
return tr("Fetching the Minecraft profile."); return tr("Fetching the Minecraft profile.");
@ -18,52 +16,47 @@ QString MinecraftProfileStep::describe()
void MinecraftProfileStep::perform() void MinecraftProfileStep::perform()
{ {
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); QUrl url("https://api.minecraftservices.com/minecraft/profile");
QNetworkRequest request = QNetworkRequest(url); auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); { "Accept", "application/json" },
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); { "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
AuthRequest* requestor = new AuthRequest(this); m_response.reset(new QByteArray());
connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone); m_task = Net::Download::makeByteArray(url, m_response);
requestor->get(request); m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_task.get(), &Task::finished, this, &MinecraftProfileStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
} }
void MinecraftProfileStep::rehydrate() void MinecraftProfileStep::onRequestDone()
{ {
// NOOP, for now. We only save bools and there's nothing to check. if (m_task->error() == QNetworkReply::ContentNotFoundError) {
}
void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state. // NOTE: Succeed even if we do not have a profile. This is a valid account state.
m_data->minecraftProfile = MinecraftProfile(); m_data->minecraftProfile = MinecraftProfile();
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile.")); emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
return; return;
} }
if (error != QNetworkReply::NoError) { if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Error getting profile:"; qWarning() << "Error getting profile:";
qWarning() << " HTTP Status: " << requestor->httpStatus_; qWarning() << " HTTP Status: " << m_task->replyStatusCode();
qWarning() << " Internal error no.: " << error; qWarning() << " Internal error no.: " << m_task->error();
qWarning() << " Error string: " << requestor->errorString_; qWarning() << " Error string: " << m_task->errorString();
qWarning() << " Response:"; qWarning() << " Response:";
qWarning() << QString::fromUtf8(data); qWarning() << QString::fromUtf8(*m_response);
if (Net::isApplicationError(error)) { if (Net::isApplicationError(m_task->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)); tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
} else { } else {
emit finished(AccountTaskState::STATE_OFFLINE, emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
} }
return; return;
} }
if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) { if (!Parsers::parseMinecraftProfile(*m_response, m_data->minecraftProfile)) {
m_data->minecraftProfile = MinecraftProfile(); m_data->minecraftProfile = MinecraftProfile();
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed")); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
return; return;

View File

@ -1,21 +1,25 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
class MinecraftProfileStep : public AuthStep { class MinecraftProfileStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit MinecraftProfileStep(AccountData* data); explicit MinecraftProfileStep(AccountData* data);
virtual ~MinecraftProfileStep() noexcept; virtual ~MinecraftProfileStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
}; };

View File

@ -1,20 +1,12 @@
#include "OfflineStep.h" #include "OfflineStep.h"
#include "Application.h"
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
OfflineStep::~OfflineStep() noexcept = default;
QString OfflineStep::describe() QString OfflineStep::describe()
{ {
return tr("Creating offline account."); return tr("Creating offline account.");
} }
void OfflineStep::rehydrate()
{
// NOOP
}
void OfflineStep::perform() void OfflineStep::perform()
{ {
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));

View File

@ -1,19 +1,15 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include <katabasis/DeviceFlow.h>
class OfflineStep : public AuthStep { class OfflineStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit OfflineStep(AccountData* data); explicit OfflineStep(AccountData* data);
virtual ~OfflineStep() noexcept; virtual ~OfflineStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
}; };

View File

@ -4,27 +4,22 @@
#include <QJsonParseError> #include <QJsonParseError>
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Application.h"
#include "Logging.h" #include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h"
#include "net/Upload.h"
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind) XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind)
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
{} {}
XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default;
QString XboxAuthorizationStep::describe() QString XboxAuthorizationStep::describe()
{ {
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind); return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
} }
void XboxAuthorizationStep::rehydrate()
{
// FIXME: check if the tokens are good?
}
void XboxAuthorizationStep::perform() void XboxAuthorizationStep::perform()
{ {
QString xbox_auth_template = R"XXX( QString xbox_auth_template = R"XXX(
@ -41,40 +36,44 @@ void XboxAuthorizationStep::perform()
)XXX"; )XXX";
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty); auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
// http://xboxlive.com // http://xboxlive.com
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize");
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); auto headers = QList<Net::HeaderPair>{
request.setRawHeader("Accept", "application/json"); { "Content-Type", "application/json" },
AuthRequest* requestor = new AuthRequest(this); { "Accept", "application/json" },
connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone); };
requestor->post(request, xbox_auth_data.toUtf8()); m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_task.get(), &Task::finished, this, &XboxAuthorizationStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting authorization token for " << m_relyingParty; qDebug() << "Getting authorization token for " << m_relyingParty;
} }
void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) void XboxAuthorizationStep::onRequestDone()
{ {
auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); qCDebug(authCredentials()) << *m_response;
requestor->deleteLater(); if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_task->error();
qCDebug(authCredentials()) << data; if (Net::isApplicationError(m_task->error())) {
if (error != QNetworkReply::NoError) { if (!processSTSError()) {
qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) {
if (!processSTSError(error, data, headers)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error)); tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_task->error()));
} else { } else {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)); tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
} }
} else { } else {
emit finished(AccountTaskState::STATE_OFFLINE, emit finished(AccountTaskState::STATE_OFFLINE,
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)); tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
} }
return; return;
} }
Katabasis::Token temp; Katabasis::Token temp;
if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) { if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)); tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
return; return;
@ -91,11 +90,11 @@ void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QBy
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty)); emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
} }
bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) bool XboxAuthorizationStep::processSTSError()
{ {
if (error == QNetworkReply::AuthenticationRequiredError) { if (m_task->error() == QNetworkReply::AuthenticationRequiredError) {
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
if (jsonError.error) { if (jsonError.error) {
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString(); qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,

View File

@ -1,29 +1,32 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Upload.h"
class XboxAuthorizationStep : public AuthStep { class XboxAuthorizationStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind); explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind);
virtual ~XboxAuthorizationStep() noexcept; virtual ~XboxAuthorizationStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private: private:
bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers); bool processSTSError();
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private: private:
Katabasis::Token* m_token; Katabasis::Token* m_token;
QString m_relyingParty; QString m_relyingParty;
QString m_authorizationKind; QString m_authorizationKind;
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
}; };

View File

@ -3,28 +3,21 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrlQuery> #include <QUrlQuery>
#include "Application.h"
#include "Logging.h" #include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h"
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {} XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
XboxProfileStep::~XboxProfileStep() noexcept = default;
QString XboxProfileStep::describe() QString XboxProfileStep::describe()
{ {
return tr("Fetching Xbox profile."); return tr("Fetching Xbox profile.");
} }
void XboxProfileStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void XboxProfileStep::perform() void XboxProfileStep::perform()
{ {
auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings"); QUrl url("https://profile.xboxlive.com/users/me/profile/settings");
QUrlQuery q; QUrlQuery q;
q.addQueryItem("settings", q.addQueryItem("settings",
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw," "GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
@ -33,36 +26,38 @@ void XboxProfileStep::perform()
"PreferredColor,Location,Bio,Watermarks," "PreferredColor,Location,Bio,Watermarks,"
"RealName,RealNameOverride,IsQuarantined"); "RealName,RealNameOverride,IsQuarantined");
url.setQuery(q); url.setQuery(q);
auto headers = QList<Net::HeaderPair>{
{ "Content-Type", "application/json" },
{ "Accept", "application/json" },
{ "x-xbl-contract-version", "3" },
{ "Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8() }
};
QNetworkRequest request = QNetworkRequest(url); m_response.reset(new QByteArray());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); m_task = Net::Download::makeByteArray(url, m_response);
request.setRawHeader("Accept", "application/json"); m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
request.setRawHeader("x-xbl-contract-version", "3");
request.setRawHeader("Authorization", connect(m_task.get(), &Task::finished, this, &XboxProfileStep::onRequestDone);
QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
AuthRequest* requestor = new AuthRequest(this); m_task->setNetwork(APPLICATION->network());
connect(requestor, &AuthRequest::finished, this, &XboxProfileStep::onRequestDone); m_task->start();
requestor->get(request);
qDebug() << "Getting Xbox profile..."; qDebug() << "Getting Xbox profile...";
} }
void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) void XboxProfileStep::onRequestDone()
{ {
auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); if (m_task->error() != QNetworkReply::NoError) {
requestor->deleteLater(); qWarning() << "Reply error:" << m_task->error();
qCDebug(authCredentials()) << *m_response;
if (error != QNetworkReply::NoError) { if (Net::isApplicationError(m_task->error())) {
qWarning() << "Reply error:" << error; emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
qCDebug(authCredentials()) << data;
if (Net::isApplicationError(error)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
} else { } else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
} }
return; return;
} }
qCDebug(authCredentials()) << "XBox profile: " << data; qCDebug(authCredentials()) << "XBox profile: " << *m_response;
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
} }

View File

@ -1,21 +1,25 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
class XboxProfileStep : public AuthStep { class XboxProfileStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit XboxProfileStep(AccountData* data); explicit XboxProfileStep(AccountData* data);
virtual ~XboxProfileStep() noexcept; virtual ~XboxProfileStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
}; };

View File

@ -2,24 +2,18 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "minecraft/auth/AuthRequest.h" #include "Application.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h"
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {} XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
XboxUserStep::~XboxUserStep() noexcept = default;
QString XboxUserStep::describe() QString XboxUserStep::describe()
{ {
return tr("Logging in as an Xbox user."); return tr("Logging in as an Xbox user.");
} }
void XboxUserStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void XboxUserStep::perform() void XboxUserStep::perform()
{ {
QString xbox_auth_template = R"XXX( QString xbox_auth_template = R"XXX(
@ -35,36 +29,39 @@ void XboxUserStep::perform()
)XXX"; )XXX";
auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token); auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate")); QUrl url("https://user.auth.xboxlive.com/user/authenticate");
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); auto headers = QList<Net::HeaderPair>{
request.setRawHeader("Accept", "application/json"); { "Content-Type", "application/json" },
// set contract-version header (prevent err 400 bad-request?) { "Accept", "application/json" },
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders // set contract-version header (prevent err 400 bad-request?)
request.setRawHeader("x-xbl-contract-version", "1"); // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
{ "x-xbl-contract-version", "1" }
};
m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
auto* requestor = new AuthRequest(this); connect(m_task.get(), &Task::finished, this, &XboxUserStep::onRequestDone);
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
requestor->post(request, xbox_auth_data.toUtf8()); m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "First layer of XBox auth ... commencing."; qDebug() << "First layer of XBox auth ... commencing.";
} }
void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) void XboxUserStep::onRequestDone()
{ {
auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); if (m_task->error() != QNetworkReply::NoError) {
requestor->deleteLater(); qWarning() << "Reply error:" << m_task->error();
if (Net::isApplicationError(m_task->error())) {
if (error != QNetworkReply::NoError) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
} else { } else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_)); emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
} }
return; return;
} }
Katabasis::Token temp; Katabasis::Token temp;
if (!Parsers::parseXTokenResponse(data, temp, "UToken")) { if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) {
qWarning() << "Could not parse user authentication response..."; qWarning() << "Could not parse user authentication response...";
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood.")); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
return; return;

View File

@ -1,21 +1,25 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <memory>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/Upload.h"
class XboxUserStep : public AuthStep { class XboxUserStep : public AuthStep {
Q_OBJECT Q_OBJECT
public: public:
explicit XboxUserStep(AccountData* data); explicit XboxUserStep(AccountData* data);
virtual ~XboxUserStep() noexcept; virtual ~XboxUserStep() noexcept = default;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private:
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
}; };

View File

@ -45,8 +45,8 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
int network_error_code = -1; int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); network_error_code = failed_action->replyStatusCode();
callbacks.on_fail(reason, network_error_code); callbacks.on_fail(reason, network_error_code);
}); });
@ -104,8 +104,8 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
}); });
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
int network_error_code = -1; int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); network_error_code = failed_action->replyStatusCode();
callbacks.on_fail(reason, network_error_code); callbacks.on_fail(reason, network_error_code);
}); });
@ -155,8 +155,8 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
}); });
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
int network_error_code = -1; int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); network_error_code = failed_action->replyStatusCode();
callbacks.on_fail(reason, network_error_code); callbacks.on_fail(reason, network_error_code);
}); });

View File

@ -261,7 +261,7 @@ bool ModrinthCreationTask::createInstance()
// FIXME: This really needs to be put into a ConcurrentTask of // FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :) // MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef(); auto param = dl.toWeakRef();
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] { connect(dl.get(), &Task::failed, [this, &file, file_path, param] {
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(ndl); m_files_job->addNetAction(ndl);

View File

@ -21,7 +21,6 @@
#include "ByteArraySink.h" #include "ByteArraySink.h"
#include "ChecksumValidator.h" #include "ChecksumValidator.h"
#include "MetaCacheSink.h" #include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net { namespace Net {

View File

@ -19,9 +19,6 @@
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
#include "ByteArraySink.h" #include "ByteArraySink.h"
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net { namespace Net {

View File

@ -47,8 +47,6 @@
#include "ChecksumValidator.h" #include "ChecksumValidator.h"
#include "MetaCacheSink.h" #include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net { namespace Net {
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)

View File

@ -1,100 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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, version 3.
*
* 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 <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QNetworkReply>
#include <QUrl>
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "HeaderProxy.h"
class NetAction : public Task {
Q_OBJECT
protected:
explicit NetAction() : Task() {}
public:
using Ptr = shared_qobject_ptr<NetAction>;
virtual ~NetAction() = default;
QUrl url() { return m_url; }
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
virtual void init() = 0;
protected slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0;
virtual void sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (auto error : errors) {
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
}
public slots:
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
{
m_network = network;
executeTask();
}
protected:
void executeTask() override {}
public:
shared_qobject_ptr<QNetworkAccessManager> m_network;
/// the network reply
unique_qobject_ptr<QNetworkReply> m_reply;
/// source URL
QUrl m_url;
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
};

View File

@ -36,6 +36,7 @@
*/ */
#include "NetJob.h" #include "NetJob.h"
#include "net/NetRequest.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
#include "Application.h" #include "Application.h"
@ -48,7 +49,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> netwo
#endif #endif
} }
auto NetJob::addNetAction(NetAction::Ptr action) -> bool auto NetJob::addNetAction(Net::NetRequest::Ptr action) -> bool
{ {
action->setNetwork(m_network); action->setNetwork(m_network);
@ -111,11 +112,11 @@ auto NetJob::abort() -> bool
return fullyAborted; return fullyAborted;
} }
auto NetJob::getFailedActions() -> QList<NetAction*> auto NetJob::getFailedActions() -> QList<Net::NetRequest*>
{ {
QList<NetAction*> failed; QList<Net::NetRequest*> failed;
for (auto index : m_failed) { for (auto index : m_failed) {
failed.push_back(dynamic_cast<NetAction*>(index.get())); failed.push_back(dynamic_cast<Net::NetRequest*>(index.get()));
} }
return failed; return failed;
} }
@ -124,7 +125,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
{ {
QList<QString> failed; QList<QString> failed;
for (auto index : m_failed) { for (auto index : m_failed) {
failed.append(static_cast<NetAction*>(index.get())->url().toString()); failed.append(static_cast<Net::NetRequest*>(index.get())->url().toString());
} }
return failed; return failed;
} }

View File

@ -39,7 +39,7 @@
#include <QtNetwork> #include <QtNetwork>
#include <QObject> #include <QObject>
#include "NetAction.h" #include "net/NetRequest.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
// Those are included so that they are also included by anyone using NetJob // Those are included so that they are also included by anyone using NetJob
@ -58,9 +58,9 @@ class NetJob : public ConcurrentTask {
auto size() const -> int; auto size() const -> int;
auto canAbort() const -> bool override; auto canAbort() const -> bool override;
auto addNetAction(NetAction::Ptr action) -> bool; auto addNetAction(Net::NetRequest::Ptr action) -> bool;
auto getFailedActions() -> QList<NetAction*>; auto getFailedActions() -> QList<Net::NetRequest*>;
auto getFailedFiles() -> QList<QString>; auto getFailedFiles() -> QList<QString>;
public slots: public slots:

View File

@ -37,6 +37,7 @@
*/ */
#include "NetRequest.h" #include "NetRequest.h"
#include <qnetworkreply.h>
#include <QUrl> #include <QUrl>
#include <QDateTime> #include <QDateTime>
@ -48,8 +49,6 @@
#endif #endif
#include "BuildConfig.h" #include "BuildConfig.h"
#include "net/NetAction.h"
#include "MMCTime.h" #include "MMCTime.h"
#include "StringUtils.h" #include "StringUtils.h"
@ -105,20 +104,18 @@ void NetRequest::executeTask()
for (auto& header_proxy : m_headerProxies) { for (auto& header_proxy : m_headerProxies) {
header_proxy->writeHeaders(request); header_proxy->writeHeaders(request);
} }
// TODO remove duplication
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) request.setTransferTimeout(QNetworkRequest::DefaultTransferTimeoutConstant);
request.setTransferTimeout();
#endif
m_last_progress_time = m_clock.now(); m_last_progress_time = m_clock.now();
m_last_progress_bytes = 0; m_last_progress_bytes = 0;
QNetworkReply* rep = getReply(request); auto rep = getReply(request);
if (rep == nullptr) // it failed if (rep == nullptr) // it failed
return; return;
m_reply.reset(rep); m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress); connect(rep, &QNetworkReply::uploadProgress, this, &NetRequest::onProgress);
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::onProgress);
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError); connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
@ -129,7 +126,7 @@ void NetRequest::executeTask()
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead); connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
} }
void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void NetRequest::onProgress(qint64 bytesReceived, qint64 bytesTotal)
{ {
auto now = m_clock.now(); auto now = m_clock.now();
auto elapsed = now - m_last_progress_time; auto elapsed = now - m_last_progress_time;
@ -237,7 +234,7 @@ auto NetRequest::handleRedirect() -> bool
m_url = QUrl(redirect.toString()); m_url = QUrl(redirect.toString());
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString(); qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network); executeTask();
return true; return true;
} }
@ -334,4 +331,23 @@ auto NetRequest::abort() -> bool
return true; return true;
} }
int NetRequest::replyStatusCode() const
{
return m_reply ? m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : -1;
}
QNetworkReply::NetworkError NetRequest::error() const
{
return m_reply ? m_reply->error() : QNetworkReply::NoError;
}
QUrl NetRequest::url() const
{
return m_url;
}
QString NetRequest::errorString() const
{
return m_reply ? m_reply->errorString() : "";
}
} // namespace Net } // namespace Net

View File

@ -39,20 +39,23 @@
#pragma once #pragma once
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <QNetworkReply>
#include <QUrl>
#include <chrono> #include <chrono>
#include "NetAction.h" #include "HeaderProxy.h"
#include "Sink.h" #include "Sink.h"
#include "Validator.h" #include "Validator.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "net/Logging.h" #include "net/Logging.h"
#include "tasks/Task.h"
namespace Net { namespace Net {
class NetRequest : public NetAction { class NetRequest : public Task {
Q_OBJECT Q_OBJECT
protected: protected:
explicit NetRequest() : NetAction() {} explicit NetRequest() : Task() {}
public: public:
using Ptr = shared_qobject_ptr<class NetRequest>; using Ptr = shared_qobject_ptr<class NetRequest>;
@ -61,26 +64,30 @@ class NetRequest : public NetAction {
public: public:
~NetRequest() override = default; ~NetRequest() override = default;
void init() override {}
public:
void addValidator(Validator* v); void addValidator(Validator* v);
auto abort() -> bool override; auto abort() -> bool override;
auto canAbort() const -> bool override { return true; } auto canAbort() const -> bool override { return true; }
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
virtual void init() {}
QUrl url() const;
int replyStatusCode() const;
QNetworkReply::NetworkError error() const;
QString errorString() const;
private: private:
auto handleRedirect() -> bool; auto handleRedirect() -> bool;
virtual QNetworkReply* getReply(QNetworkRequest&) = 0; virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
protected slots: protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void onProgress(qint64 bytesReceived, qint64 bytesTotal);
void downloadError(QNetworkReply::NetworkError error) override; void downloadError(QNetworkReply::NetworkError error);
void sslErrors(const QList<QSslError>& errors) override; void sslErrors(const QList<QSslError>& errors);
void downloadFinished() override; void downloadFinished();
void downloadReadyRead() override; void downloadReadyRead();
public slots:
void executeTask() override; void executeTask() override;
protected: protected:
@ -93,6 +100,15 @@ class NetRequest : public NetAction {
std::chrono::steady_clock m_clock; std::chrono::steady_clock m_clock;
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time; std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
qint64 m_last_progress_bytes; qint64 m_last_progress_bytes;
shared_qobject_ptr<QNetworkAccessManager> m_network;
/// the network reply
unique_qobject_ptr<QNetworkReply> m_reply;
/// source URL
QUrl m_url;
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
}; };
} // namespace Net } // namespace Net

View File

@ -35,9 +35,8 @@
#pragma once #pragma once
#include "net/NetAction.h"
#include "Validator.h" #include "Validator.h"
#include "tasks/Task.h"
namespace Net { namespace Net {
class Sink { class Sink {

View File

@ -34,7 +34,7 @@
#pragma once #pragma once
#include "net/NetAction.h" #include <QNetworkReply>
namespace Net { namespace Net {
class Validator { class Validator {

View File

@ -45,8 +45,9 @@
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include <Application.h> #include <Application.h>
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/StaticHeaderProxy.h"
#include "net/Upload.h"
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent) ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog) : QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
@ -150,28 +151,27 @@ void ProfileSetupDialog::checkName(const QString& name)
currentCheck = name; currentCheck = name;
isChecking = true; isChecking = true;
auto token = m_accountToSetup->accessToken(); QUrl url(QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name));
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
{ "Accept", "application/json" },
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name); m_check_response.reset(new QByteArray());
QNetworkRequest request = QNetworkRequest(url); if (m_check_task)
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); disconnect(m_check_task.get(), nullptr, this, nullptr);
request.setRawHeader("Accept", "application/json"); m_check_task = Net::Download::makeByteArray(url, m_check_response);
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8()); m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
AuthRequest* requestor = new AuthRequest(this); connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished);
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
requestor->get(request); m_check_task->setNetwork(APPLICATION->network());
m_check_task->start();
} }
void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error, void ProfileSetupDialog::checkFinished()
QByteArray profileData,
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{ {
auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); if (m_check_task->error() == QNetworkReply::NoError) {
requestor->deleteLater(); auto doc = QJsonDocument::fromJson(*m_check_response);
if (error == QNetworkReply::NoError) {
auto doc = QJsonDocument::fromJson(profileData);
auto root = doc.object(); auto root = doc.object();
auto statusValue = root.value("status").toString("INVALID"); auto statusValue = root.value("status").toString("INVALID");
if (statusValue == "AVAILABLE") { if (statusValue == "AVAILABLE") {
@ -195,20 +195,22 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
return; return;
} }
auto token = m_accountToSetup->accessToken();
auto url = QString("https://api.minecraftservices.com/minecraft/profile");
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
QString payloadTemplate("{\"profileName\":\"%1\"}"); QString payloadTemplate("{\"profileName\":\"%1\"}");
auto profileData = payloadTemplate.arg(profileName).toUtf8();
AuthRequest* requestor = new AuthRequest(this); QUrl url("https://api.minecraftservices.com/minecraft/profile");
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished); auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
requestor->post(request, profileData); { "Accept", "application/json" },
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
m_profile_response.reset(new QByteArray());
m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8());
m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished);
m_profile_task->setNetwork(APPLICATION->network());
m_profile_task->start();
isWorking = true; isWorking = true;
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel); auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
@ -244,22 +246,17 @@ struct MojangError {
} // namespace } // namespace
void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error, void ProfileSetupDialog::setupProfileFinished()
QByteArray errorData,
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{ {
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
isWorking = false; isWorking = false;
if (error == QNetworkReply::NoError) { if (m_profile_task->error() == QNetworkReply::NoError) {
/* /*
* data contains the profile in the response * data contains the profile in the response
* ... we could parse it and update the account, but let's just return back to the normal login flow instead... * ... we could parse it and update the account, but let's just return back to the normal login flow instead...
*/ */
accept(); accept();
} else { } else {
auto parsedError = MojangError::fromJSON(errorData); auto parsedError = MojangError::fromJSON(*m_profile_response);
ui->errorLabel->setVisible(true); ui->errorLabel->setVisible(true);
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage); ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
qDebug() << parsedError.rawError; qDebug() << parsedError.rawError;

View File

@ -22,6 +22,8 @@
#include <minecraft/auth/MinecraftAccount.h> #include <minecraft/auth/MinecraftAccount.h>
#include <memory> #include <memory>
#include "net/Download.h"
#include "net/Upload.h"
namespace Ui { namespace Ui {
class ProfileSetupDialog; class ProfileSetupDialog;
@ -40,10 +42,10 @@ class ProfileSetupDialog : public QDialog {
void on_buttonBox_rejected(); void on_buttonBox_rejected();
void nameEdited(const QString& name); void nameEdited(const QString& name);
void checkFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
void startCheck(); void startCheck();
void setupProfileFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers); void checkFinished();
void setupProfileFinished();
protected: protected:
void scheduleCheck(const QString& name); void scheduleCheck(const QString& name);
@ -67,4 +69,10 @@ class ProfileSetupDialog : public QDialog {
QString currentCheck; QString currentCheck;
QTimer checkStartTimer; QTimer checkStartTimer;
std::shared_ptr<QByteArray> m_check_response;
Net::Download::Ptr m_check_task;
std::shared_ptr<QByteArray> m_profile_response;
Net::Upload::Ptr m_profile_task;
}; };

View File

@ -331,7 +331,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
auto full_file_path = cache_entry->getFullPath(); auto full_file_path = cache_entry->getFullPath();
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { connect(icon_fetch_action.get(), &Task::succeeded, this, [=] {
auto icon = QIcon(full_file_path); auto icon = QIcon(full_file_path);
QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
@ -339,7 +339,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
emit dataChanged(index, index, { Qt::DecorationRole }); emit dataChanged(index, index, { Qt::DecorationRole });
}); });
connect(icon_fetch_action.get(), &NetAction::failed, this, [=] { connect(icon_fetch_action.get(), &Task::failed, this, [=] {
m_currently_running_icon_actions.remove(url); m_currently_running_icon_actions.remove(url);
m_failed_icon_actions.insert(url); m_failed_icon_actions.insert(url);
}); });

View File

@ -352,10 +352,10 @@ void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
void ModpackListModel::searchRequestFailed(QString reason) void ModpackListModel::searchRequestFailed(QString reason)
{ {
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0); auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) { if (failed_action->replyStatusCode() == -1) {
// Network error // Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
} else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { } else if (failed_action->replyStatusCode() == 409) {
// 409 Gone, notify user to update // 409 Gone, notify user to update
QMessageBox::critical(nullptr, tr("Error"), QMessageBox::critical(nullptr, tr("Error"),
//: %1 refers to the launcher itself //: %1 refers to the launcher itself

View File

@ -95,8 +95,8 @@ class LibraryTest : public QObject {
auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString()); auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 1); QCOMPARE(downloads.size(), 1);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
NetAction::Ptr dl = downloads[0]; Net::NetRequest::Ptr dl = downloads[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); QCOMPARE(dl->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
} }
void test_legacy_url_local_broken() void test_legacy_url_local_broken()
{ {
@ -147,7 +147,7 @@ class LibraryTest : public QObject {
QCOMPARE(dls.size(), 1); QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
auto dl = dls[0]; auto dl = dls[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); QCOMPARE(dl->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
} }
} }
void test_legacy_native_arch() void test_legacy_native_arch()
@ -170,8 +170,8 @@ class LibraryTest : public QObject {
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2); QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
} }
r.system = "windows"; r.system = "windows";
{ {
@ -185,8 +185,8 @@ class LibraryTest : public QObject {
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2); QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
} }
r.system = "osx"; r.system = "osx";
{ {
@ -200,8 +200,8 @@ class LibraryTest : public QObject {
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2); QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
} }
} }
void test_legacy_native_arch_local_override() void test_legacy_native_arch_local_override()
@ -244,7 +244,7 @@ class LibraryTest : public QObject {
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1); QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
} }
r.system = "osx"; r.system = "osx";
test->setHint("local"); test->setHint("local");
@ -300,7 +300,7 @@ class LibraryTest : public QObject {
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1); QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/" QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/"
"lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); "lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
} }
void test_onenine_native_arch() void test_onenine_native_arch()
@ -317,9 +317,9 @@ class LibraryTest : public QObject {
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2); QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {}); QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QCOMPARE(dls[0]->url(),
QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QCOMPARE(dls[1]->url(),
QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
} }