mirror of
https://github.com/2dust/v2rayN.git
synced 2026-11-06 20:45:00 +03:00
2010477f38
NaiveFmt.ToUri percent-encodes the username and password, so a password-only profile with the password a:b is exported as naive+https://a%3Ab@host. Resolve decoded the user info before looking for the ':' separator and imported it as username "a", password "b". Split the raw user info first and decode each part. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.2 KiB
C#
96 lines
3.2 KiB
C#
namespace ServiceLib.Handler.Fmt;
|
|
|
|
public class NaiveFmt : BaseFmt
|
|
{
|
|
public static ProfileItem? Resolve(string str, out string msg)
|
|
{
|
|
msg = ResUI.ConfigurationFormatIncorrect;
|
|
|
|
var parsedUrl = Utils.TryUri(str);
|
|
if (parsedUrl == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
ProfileItem item = new()
|
|
{
|
|
ConfigType = EConfigType.Naive,
|
|
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
|
Address = parsedUrl.IdnHost,
|
|
// naive+https:// and naive+quic:// links leave the port out when it is 443
|
|
// (https://gist.github.com/DuckSoft/ca03913b0a26fc77a1da4d01cc6ab2f1). Uri.Port answers
|
|
// -1 for an unregistered scheme carrying no port; an explicit ":0" stays 0.
|
|
Port = parsedUrl.Port == -1 ? 443 : parsedUrl.Port,
|
|
};
|
|
var protocolExtra = item.GetProtocolExtra();
|
|
if (parsedUrl.Scheme.Contains("quic"))
|
|
{
|
|
protocolExtra = protocolExtra with
|
|
{
|
|
NaiveQuic = true,
|
|
};
|
|
}
|
|
// Split before decoding: ToUri percent-encodes a ':' inside the username or password.
|
|
var rawUserInfo = parsedUrl.UserInfo;
|
|
if (rawUserInfo.Contains(':'))
|
|
{
|
|
var split = rawUserInfo.Split(':', 2);
|
|
item.Username = Utils.UrlDecode(split[0]);
|
|
item.Password = Utils.UrlDecode(split[1]);
|
|
}
|
|
else
|
|
{
|
|
item.Password = Utils.UrlDecode(rawUserInfo);
|
|
}
|
|
|
|
var query = Utils.ParseQueryString(parsedUrl.Query);
|
|
ResolveUriQuery(query, ref item);
|
|
var insecureConcurrency = int.TryParse(GetQueryValue(query, "insecure-concurrency"), out var ic) ? ic : 0;
|
|
if (insecureConcurrency > 0)
|
|
{
|
|
protocolExtra = protocolExtra with
|
|
{
|
|
InsecureConcurrency = insecureConcurrency,
|
|
};
|
|
}
|
|
|
|
item.SetProtocolExtra(protocolExtra);
|
|
return item;
|
|
}
|
|
|
|
public static string? ToUri(ProfileItem? item)
|
|
{
|
|
if (item == null)
|
|
{
|
|
return null;
|
|
}
|
|
var remark = string.Empty;
|
|
if (item.Remarks.IsNotEmpty())
|
|
{
|
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
|
}
|
|
var userInfo = item.Username.IsNotEmpty() ? $"{Utils.UrlEncode(item.Username)}:{Utils.UrlEncode(item.Password)}" : Utils.UrlEncode(item.Password);
|
|
var dicQuery = new Dictionary<string, string>();
|
|
ToUriQuery(item, Global.None, ref dicQuery);
|
|
var protocolExtra = item.GetProtocolExtra();
|
|
if (protocolExtra.InsecureConcurrency > 0)
|
|
{
|
|
dicQuery.Add("insecure-concurrency", protocolExtra?.InsecureConcurrency.ToString());
|
|
}
|
|
|
|
var query = dicQuery.Count > 0
|
|
? ("?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray()))
|
|
: string.Empty;
|
|
var url = $"{userInfo}@{GetIpv6(item.Address)}:{item.Port}";
|
|
|
|
if (protocolExtra.NaiveQuic == true)
|
|
{
|
|
return $"{Global.NaiveQuicProtocolShare}{url}{query}{remark}";
|
|
}
|
|
else
|
|
{
|
|
return $"{Global.NaiveHttpsProtocolShare}{url}{query}{remark}";
|
|
}
|
|
}
|
|
}
|