mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2025-12-19 05:30:06 +08:00
151 lines
6.3 KiB
C++
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";
|
|
}
|
|
}
|