nekoray_Mahdi-zarei/src/configs/outbounds/shadowsocks.cpp
2025-12-03 14:22:56 +08:00

151 lines
6.3 KiB
C++

#include "include/configs/outbounds/shadowsocks.h"
#include <QUrlQuery>
#include <include/global/Utils.hpp>
#include "include/configs/common/utils.h"
namespace Configs {
bool shadowsocks::ParseFromLink(const QString& link)
{
QUrl url;
if (SubStrBefore(link, "#").contains("@")) {
url = QUrl(link);
} else {
// v2rayN format: base64 encoded full URL
QString linkN = DecodeB64IfValid(SubStrBefore(SubStrAfter(link, "://"), "#"), QByteArray::Base64Option::Base64UrlEncoding);
if (linkN.isEmpty()) return false;
if (link.contains("#")) linkN += "#" + SubStrAfter(link, "#");
url = QUrl("https://" + linkN);
}
if (!url.isValid()) return false;
auto query = QUrlQuery(url.query(QUrl::ComponentFormattingOption::FullyDecoded));
outbound::ParseFromLink(url.toString());
// Traditional SS format
if (url.password().isEmpty()) {
// Traditional format: method:password base64 encoded in username
auto method_password = DecodeB64IfValid(url.userName(), QByteArray::Base64Option::Base64UrlEncoding);
if (method_password.isEmpty()) return false;
method = SubStrBefore(method_password, ":");
password = SubStrAfter(method_password, ":");
} else {
// 2022 format: method in username, password in password
method = url.userName();
password = url.password();
}
plugin = query.queryItemValue("plugin").replace("simple-obfs;", "obfs-local;");
plugin_opts = SubStrAfter(plugin, ";");
plugin = SubStrBefore(plugin, ";");
if (query.hasQueryItem("plugin-opts")) plugin_opts = query.queryItemValue("plugin-opts");
if (query.hasQueryItem("uot")) uot = query.queryItemValue("uot") == "true" || query.queryItemValue("uot").toInt() > 0;
multiplex->ParseFromLink(link);
return !(server.isEmpty() || method.isEmpty() || password.isEmpty());
}
bool shadowsocks::ParseFromJson(const QJsonObject& object)
{
if (object.isEmpty() || object["type"].toString() != "shadowsocks") return false;
outbound::ParseFromJson(object);
if (object.contains("method")) method = object["method"].toString();
if (object.contains("password")) password = object["password"].toString();
if (object.contains("plugin")) plugin = object["plugin"].toString();
if (object.contains("plugin_opts")) plugin_opts = object["plugin_opts"].toString();
if (object.contains("uot"))
{
if (object["uot"].isBool()) uot = object["uot"].toBool();
if (object["uot"].isObject()) uot = object["uot"].toObject()["enabled"].toBool();
}
if (object.contains("multiplex")) multiplex->ParseFromJson(object["multiplex"].toObject());
return true;
}
bool shadowsocks::ParseFromSIP008(const QJsonObject& object)
{
if (object.isEmpty()) return false;
outbound::ParseFromJson(object);
if (object.contains("remarks")) name = object["remarks"].toString();
if (object.contains("method")) method = object["method"].toString();
if (object.contains("password")) password = object["password"].toString();
if (object.contains("plugin")) plugin = object["plugin"].toString().replace("simple-obfs", "obfs-local");
if (object.contains("plugin_opts")) plugin_opts = object["plugin_opts"].toString();
if (object.contains("uot"))
{
if (object["uot"].isBool()) uot = object["uot"].toBool();
if (object["uot"].isObject()) uot = object["uot"].toObject()["enabled"].toBool();
}
if (object.contains("multiplex")) multiplex->ParseFromJson(object["multiplex"].toObject());
return !(server.isEmpty() || method.isEmpty() || password.isEmpty());
}
QString shadowsocks::ExportToLink()
{
QUrl url;
QUrlQuery query;
url.setScheme("ss");
if (method.startsWith("2022-")) {
// 2022 format: method:password directly
url.setUserName(method);
url.setPassword(password);
} else {
// Traditional format: base64 encode method:password
auto method_password = method + ":" + password;
url.setUserName(method_password.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding));
}
url.setHost(server);
url.setPort(server_port);
if (!name.isEmpty()) url.setFragment(name);
if (!plugin.isEmpty()) query.addQueryItem("plugin", plugin);
if (!plugin_opts.isEmpty()) query.addQueryItem("plugin-opts", plugin_opts);
if (uot) query.addQueryItem("uot", "true");
mergeUrlQuery(query, multiplex->ExportToLink());
mergeUrlQuery(query, outbound::ExportToLink());
if (!query.isEmpty()) url.setQuery(query);
return url.toString();
}
QJsonObject shadowsocks::ExportToJson()
{
QJsonObject object;
object["type"] = "shadowsocks";
mergeJsonObjects(object, outbound::ExportToJson());
if (!method.isEmpty()) object["method"] = method;
if (!password.isEmpty()) object["password"] = password;
if (!plugin.isEmpty()) object["plugin"] = plugin;
if (!plugin_opts.isEmpty()) object["plugin_opts"] = plugin_opts;
if (uot) object["uot"] = uot;
if (multiplex->enabled) object["multiplex"] = multiplex->ExportToJson();
return object;
}
BuildResult shadowsocks::Build()
{
if (plugin.contains(";")) {
plugin_opts = SubStrAfter(plugin, ";");
plugin = SubStrBefore(plugin, ";");
}
QJsonObject object;
object["type"] = "shadowsocks";
mergeJsonObjects(object, outbound::Build().object);
if (!method.isEmpty()) object["method"] = method;
if (!password.isEmpty()) object["password"] = password;
if (!plugin.isEmpty()) object["plugin"] = plugin;
if (!plugin_opts.isEmpty()) object["plugin_opts"] = plugin_opts;
if (uot) object["uot"] = uot;
if (auto obj = multiplex->Build().object; !obj.isEmpty()) object["multiplex"] = obj;
return {object, ""};
}
QString shadowsocks::DisplayType()
{
return "Shadowsocks";
}
}