@@ -238,7 +238,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
AppendRecord(server.Identifier(), record); | AppendRecord(server.Identifier(), record); | ||||
} | } | ||||
logger.Debug($"Ping {server.FriendlyName()} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms"); | |||||
logger.Debug($"Ping {server} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms"); | |||||
if (Interlocked.Decrement(ref state.counter) == 0) | if (Interlocked.Decrement(ref state.counter) == 0) | ||||
{ | { | ||||
Save(); | Save(); | ||||
@@ -450,7 +450,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
logger.Debug($"Ping {server.FriendlyName()}"); | |||||
logger.Debug($"Ping {server}"); | |||||
if (ip == null) | if (ip == null) | ||||
{ | { | ||||
ip = Dns.GetHostAddresses(server.server) | ip = Dns.GetHostAddresses(server.server) | ||||
@@ -466,7 +466,7 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
logger.Error($"An exception occured while eveluating {server.FriendlyName()}"); | |||||
logger.Error($"An exception occured while eveluating {server}"); | |||||
logger.LogUsefulException(e); | logger.LogUsefulException(e); | ||||
FireCompleted(e, userstate); | FireCompleted(e, userstate); | ||||
} | } | ||||
@@ -478,19 +478,19 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
if (e.Reply.Status == IPStatus.Success) | if (e.Reply.Status == IPStatus.Success) | ||||
{ | { | ||||
logger.Debug($"Ping {server.FriendlyName()} {e.Reply.RoundtripTime} ms"); | |||||
logger.Debug($"Ping {server} {e.Reply.RoundtripTime} ms"); | |||||
RoundtripTime.Add((int?)e.Reply.RoundtripTime); | RoundtripTime.Add((int?)e.Reply.RoundtripTime); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
logger.Debug($"Ping {server.FriendlyName()} timeout"); | |||||
logger.Debug($"Ping {server} timeout"); | |||||
RoundtripTime.Add(null); | RoundtripTime.Add(null); | ||||
} | } | ||||
TestNext(e.UserState); | TestNext(e.UserState); | ||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
{ | { | ||||
logger.Error($"An exception occured while eveluating {server.FriendlyName()}"); | |||||
logger.Error($"An exception occured while eveluating {server}"); | |||||
logger.LogUsefulException(ex); | logger.LogUsefulException(ex); | ||||
FireCompleted(ex, e.UserState); | FireCompleted(ex, e.UserState); | ||||
} | } | ||||
@@ -803,7 +803,7 @@ namespace Shadowsocks.Controller | |||||
AsyncSession session = timer.Session; | AsyncSession session = timer.Session; | ||||
Server server = timer.Server; | Server server = timer.Server; | ||||
OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); | OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); | ||||
Logger.Info($"{server.FriendlyName()} timed out"); | |||||
Logger.Info($"{server.ToString()} timed out"); | |||||
session.Remote.Close(); | session.Remote.Close(); | ||||
Close(); | Close(); | ||||
} | } | ||||
@@ -830,7 +830,7 @@ namespace Shadowsocks.Controller | |||||
_destConnected = true; | _destConnected = true; | ||||
Logger.Debug($"Socket connected to ss server: {_server.FriendlyName()}"); | |||||
Logger.Debug($"Socket connected to ss server: {_server.ToString()}"); | |||||
TimeSpan latency = DateTime.Now - _startConnectTime; | TimeSpan latency = DateTime.Now - _startConnectTime; | ||||
@@ -371,48 +371,7 @@ namespace Shadowsocks.Controller | |||||
public string GetServerURLForCurrentServer() | public string GetServerURLForCurrentServer() | ||||
{ | { | ||||
Server server = GetCurrentServer(); | |||||
return GetServerURL(server); | |||||
} | |||||
public static string GetServerURL(Server server) | |||||
{ | |||||
string tag = string.Empty; | |||||
string url = string.Empty; | |||||
if (string.IsNullOrWhiteSpace(server.plugin)) | |||||
{ | |||||
// For backwards compatiblity, if no plugin, use old url format | |||||
string parts = $"{server.method}:{server.password}@{server.server}:{server.server_port}"; | |||||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||||
url = base64; | |||||
} | |||||
else | |||||
{ | |||||
// SIP002 | |||||
string parts = $"{server.method}:{server.password}"; | |||||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||||
string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); | |||||
string pluginPart = server.plugin; | |||||
if (!string.IsNullOrWhiteSpace(server.plugin_opts)) | |||||
{ | |||||
pluginPart += ";" + server.plugin_opts; | |||||
} | |||||
url = string.Format( | |||||
"{0}@{1}:{2}/?plugin={3}", | |||||
websafeBase64, | |||||
server.FormatHostName(server.server), | |||||
server.server_port, | |||||
HttpUtility.UrlEncode(pluginPart, Encoding.UTF8)); | |||||
} | |||||
if (!server.remarks.IsNullOrEmpty()) | |||||
{ | |||||
tag = $"#{HttpUtility.UrlEncode(server.remarks, Encoding.UTF8)}"; | |||||
} | |||||
return $"ss://{url}{tag}"; | |||||
return GetCurrentServer().GetURL(_config.generateLegacyUrl); | |||||
} | } | ||||
public void UpdateStatisticsConfiguration(bool enabled) | public void UpdateStatisticsConfiguration(bool enabled) | ||||
@@ -114,7 +114,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds) | 100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds) | ||||
-2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) + | -2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) + | ||||
-0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds)); | -0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds)); | ||||
logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.FriendlyName(), status.latency, status.score)); | |||||
logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.ToString(), status.latency, status.score)); | |||||
} | } | ||||
ServerStatus max = null; | ServerStatus max = null; | ||||
foreach (var status in servers) | foreach (var status in servers) | ||||
@@ -136,14 +136,14 @@ namespace Shadowsocks.Controller.Strategy | |||||
if (_currentServer == null || max.score - _currentServer.score > 200) | if (_currentServer == null || max.score - _currentServer.score > 200) | ||||
{ | { | ||||
_currentServer = max; | _currentServer = max; | ||||
logger.Info($"HA switching to server: {_currentServer.server.FriendlyName()}"); | |||||
logger.Info($"HA switching to server: {_currentServer.server.ToString()}"); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
public void UpdateLatency(Model.Server server, TimeSpan latency) | public void UpdateLatency(Model.Server server, TimeSpan latency) | ||||
{ | { | ||||
logger.Debug($"latency: {server.FriendlyName()} {latency}"); | |||||
logger.Debug($"latency: {server.ToString()} {latency}"); | |||||
ServerStatus status; | ServerStatus status; | ||||
if (_serverStatus.TryGetValue(server, out status)) | if (_serverStatus.TryGetValue(server, out status)) | ||||
@@ -155,7 +155,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
public void UpdateLastRead(Model.Server server) | public void UpdateLastRead(Model.Server server) | ||||
{ | { | ||||
logger.Debug($"last read: {server.FriendlyName()}"); | |||||
logger.Debug($"last read: {server.ToString()}"); | |||||
ServerStatus status; | ServerStatus status; | ||||
if (_serverStatus.TryGetValue(server, out status)) | if (_serverStatus.TryGetValue(server, out status)) | ||||
@@ -166,7 +166,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
public void UpdateLastWrite(Model.Server server) | public void UpdateLastWrite(Model.Server server) | ||||
{ | { | ||||
logger.Debug($"last write: {server.FriendlyName()}"); | |||||
logger.Debug($"last write: {server.ToString()}"); | |||||
ServerStatus status; | ServerStatus status; | ||||
if (_serverStatus.TryGetValue(server, out status)) | if (_serverStatus.TryGetValue(server, out status)) | ||||
@@ -177,7 +177,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
public void SetFailure(Model.Server server) | public void SetFailure(Model.Server server) | ||||
{ | { | ||||
logger.Debug($"failure: {server.FriendlyName()}"); | |||||
logger.Debug($"failure: {server.ToString()}"); | |||||
ServerStatus status; | ServerStatus status; | ||||
if (_serverStatus.TryGetValue(server, out status)) | if (_serverStatus.TryGetValue(server, out status)) | ||||
@@ -109,7 +109,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
var bestResult = serversWithStatistics | var bestResult = serversWithStatistics | ||||
.Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2); | .Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2); | ||||
LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by statistics: score {bestResult.score}"); | |||||
LogWhenEnabled($"Switch to server: {bestResult.server.ToString()} by statistics: score {bestResult.score}"); | |||||
_currentServer = bestResult.server; | _currentServer = bestResult.server; | ||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
@@ -147,7 +147,7 @@ namespace Shadowsocks.Controller.Strategy | |||||
public void SetFailure(Server server) | public void SetFailure(Server server) | ||||
{ | { | ||||
logger.Debug($"failure: {server.FriendlyName()}"); | |||||
logger.Debug($"failure: {server.ToString()}"); | |||||
} | } | ||||
public void UpdateLastRead(Server server) | public void UpdateLastRead(Server server) | ||||
@@ -11,7 +11,7 @@ namespace Shadowsocks.Model | |||||
public class Configuration | public class Configuration | ||||
{ | { | ||||
[JsonIgnore] | [JsonIgnore] | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public string version; | public string version; | ||||
@@ -24,14 +24,10 @@ namespace Shadowsocks.Model | |||||
public bool enabled; | public bool enabled; | ||||
public bool shareOverLan; | public bool shareOverLan; | ||||
public bool isDefault; | public bool isDefault; | ||||
public bool isIPv6Enabled = false; | |||||
public int localPort; | public int localPort; | ||||
public bool portableMode = true; | public bool portableMode = true; | ||||
public bool showPluginOutput; | public bool showPluginOutput; | ||||
public string pacUrl; | public string pacUrl; | ||||
public string geositeUrl; | |||||
public string geositeGroup = "geolocation-!cn"; | |||||
public bool geositeBlacklistMode = true; | |||||
public bool useOnlinePac; | public bool useOnlinePac; | ||||
public bool secureLocalPac = true; | public bool secureLocalPac = true; | ||||
@@ -39,6 +35,15 @@ namespace Shadowsocks.Model | |||||
public bool autoCheckUpdate; | public bool autoCheckUpdate; | ||||
public bool checkPreRelease; | public bool checkPreRelease; | ||||
public bool isVerboseLogging; | public bool isVerboseLogging; | ||||
// hidden options | |||||
public bool isIPv6Enabled = false; // for experimental ipv6 support | |||||
public bool generateLegacyUrl = false; // for pre-sip002 url compatibility | |||||
public string geositeUrl; // for custom geosite source (and rule group) | |||||
public string geositeGroup = "geolocation-!cn"; | |||||
public bool geositeBlacklistMode = true; | |||||
//public NLogConfig.LogLevel logLevel; | //public NLogConfig.LogLevel logLevel; | ||||
public LogViewerConfig logViewer; | public LogViewerConfig logViewer; | ||||
public ProxyConfig proxy; | public ProxyConfig proxy; | ||||
@@ -48,11 +53,10 @@ namespace Shadowsocks.Model | |||||
NLogConfig nLogConfig; | NLogConfig nLogConfig; | ||||
private static readonly string CONFIG_FILE = "gui-config.json"; | private static readonly string CONFIG_FILE = "gui-config.json"; | ||||
private static readonly NLogConfig.LogLevel verboseLogLevel = | |||||
#if DEBUG | #if DEBUG | ||||
NLogConfig.LogLevel.Trace; | |||||
private static readonly NLogConfig.LogLevel verboseLogLevel = NLogConfig.LogLevel.Trace; | |||||
#else | #else | ||||
NLogConfig.LogLevel.Debug; | |||||
private static readonly NLogConfig.LogLevel verboseLogLevel = NLogConfig.LogLevel.Debug; | |||||
#endif | #endif | ||||
@@ -194,9 +198,9 @@ namespace Shadowsocks.Model | |||||
sw.Flush(); | sw.Flush(); | ||||
} | } | ||||
try | try | ||||
{ | |||||
// apply changs to NLog.config | |||||
config.nLogConfig.SetLogLevel(config.isVerboseLogging? verboseLogLevel : NLogConfig.LogLevel.Info); | |||||
{ | |||||
// apply changes to NLog.config | |||||
config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info); | |||||
NLogConfig.SaveXML(config.nLogConfig); | NLogConfig.SaveXML(config.nLogConfig); | ||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
@@ -212,7 +216,7 @@ namespace Shadowsocks.Model | |||||
public static Server AddDefaultServerOrServer(Configuration config, Server server = null, int? index = null) | public static Server AddDefaultServerOrServer(Configuration config, Server server = null, int? index = null) | ||||
{ | { | ||||
if (config != null && config.configs != null) | |||||
if (config?.configs != null) | |||||
{ | { | ||||
server = (server ?? GetDefaultServer()); | server = (server ?? GetDefaultServer()); | ||||
@@ -231,12 +235,6 @@ namespace Shadowsocks.Model | |||||
return new Server(); | return new Server(); | ||||
} | } | ||||
private static void Assert(bool condition) | |||||
{ | |||||
if (!condition) | |||||
throw new Exception(I18N.GetString("assertion failure")); | |||||
} | |||||
public static void CheckPort(int port) | public static void CheckPort(int port) | ||||
{ | { | ||||
if (port <= 0 || port > 65535) | if (port <= 0 || port > 65535) | ||||
@@ -5,6 +5,7 @@ using System.Text; | |||||
using System.Web; | using System.Web; | ||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
using System.Linq; | |||||
namespace Shadowsocks.Model | namespace Shadowsocks.Model | ||||
{ | { | ||||
@@ -15,9 +16,8 @@ namespace Shadowsocks.Model | |||||
public const int DefaultPort = 8388; | public const int DefaultPort = 8388; | ||||
#region ParseLegacyURL | #region ParseLegacyURL | ||||
public static readonly Regex | |||||
UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase), | |||||
DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase); | |||||
private static readonly Regex UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase); | |||||
private static readonly Regex DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase); | |||||
#endregion ParseLegacyURL | #endregion ParseLegacyURL | ||||
private const int DefaultServerTimeoutSec = 5; | private const int DefaultServerTimeoutSec = 5; | ||||
@@ -44,28 +44,77 @@ namespace Shadowsocks.Model | |||||
return server == o2.server && server_port == o2.server_port; | return server == o2.server && server_port == o2.server_port; | ||||
} | } | ||||
public string FriendlyName() | |||||
public override string ToString() | |||||
{ | { | ||||
if (server.IsNullOrEmpty()) | if (server.IsNullOrEmpty()) | ||||
{ | { | ||||
return I18N.GetString("New server"); | return I18N.GetString("New server"); | ||||
} | } | ||||
string serverStr = $"{FormatHostName(server)}:{server_port}"; | |||||
string serverStr = $"{FormalHostName}:{server_port}"; | |||||
return remarks.IsNullOrEmpty() | return remarks.IsNullOrEmpty() | ||||
? serverStr | ? serverStr | ||||
: $"{remarks} ({serverStr})"; | : $"{remarks} ({serverStr})"; | ||||
} | } | ||||
public string FormatHostName(string hostName) | |||||
public string GetURL(bool legacyUrl = false) | |||||
{ | { | ||||
// CheckHostName() won't do a real DNS lookup | |||||
switch (Uri.CheckHostName(hostName)) | |||||
string tag = string.Empty; | |||||
string url = string.Empty; | |||||
if (legacyUrl && string.IsNullOrWhiteSpace(plugin)) | |||||
{ | |||||
// For backwards compatiblity, if no plugin, use old url format | |||||
string parts = $"{method}:{password}@{server}:{server_port}"; | |||||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||||
url = base64; | |||||
} | |||||
else | |||||
{ | |||||
// SIP002 | |||||
string parts = $"{method}:{password}"; | |||||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||||
string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); | |||||
url = string.Format( | |||||
"{0}@{1}:{2}/", | |||||
websafeBase64, | |||||
FormalHostName, | |||||
server_port | |||||
); | |||||
if (!plugin.IsNullOrWhiteSpace()) | |||||
{ | |||||
string pluginPart = plugin; | |||||
if (!string.IsNullOrWhiteSpace(plugin_opts)) | |||||
{ | |||||
pluginPart += ";" + plugin_opts; | |||||
} | |||||
string pluginQuery = "?plugin=" + HttpUtility.UrlEncode(pluginPart, Encoding.UTF8); | |||||
url += pluginQuery; | |||||
} | |||||
} | |||||
if (!remarks.IsNullOrEmpty()) | |||||
{ | |||||
tag = $"#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; | |||||
} | |||||
return $"ss://{url}{tag}"; | |||||
} | |||||
public string FormalHostName | |||||
{ | |||||
get | |||||
{ | { | ||||
case UriHostNameType.IPv6: // Add square bracket when IPv6 (RFC3986) | |||||
return $"[{hostName}]"; | |||||
default: // IPv4 or domain name | |||||
return hostName; | |||||
// CheckHostName() won't do a real DNS lookup | |||||
switch (Uri.CheckHostName(server)) | |||||
{ | |||||
case UriHostNameType.IPv6: // Add square bracket when IPv6 (RFC3986) | |||||
return $"[{server}]"; | |||||
default: // IPv4 or domain name | |||||
return server; | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -114,79 +163,81 @@ namespace Shadowsocks.Model | |||||
return server; | return server; | ||||
} | } | ||||
public static List<Server> GetServers(string ssURL) | |||||
public static Server ParseURL(string serverUrl) | |||||
{ | { | ||||
var serverUrls = ssURL.Split('\r', '\n', ' '); | |||||
string _serverUrl = serverUrl.Trim(); | |||||
if (!_serverUrl.BeginWith("ss://", StringComparison.InvariantCultureIgnoreCase)) | |||||
{ | |||||
return null; | |||||
} | |||||
List<Server> servers = new List<Server>(); | |||||
foreach (string serverUrl in serverUrls) | |||||
Server legacyServer = ParseLegacyURL(serverUrl); | |||||
if (legacyServer != null) //legacy | |||||
{ | |||||
return legacyServer; | |||||
} | |||||
else //SIP002 | |||||
{ | { | ||||
string _serverUrl = serverUrl.Trim(); | |||||
if (!_serverUrl.BeginWith("ss://", StringComparison.InvariantCultureIgnoreCase)) | |||||
Uri parsedUrl; | |||||
try | |||||
{ | { | ||||
continue; | |||||
parsedUrl = new Uri(serverUrl); | |||||
} | } | ||||
Server legacyServer = ParseLegacyURL(serverUrl); | |||||
if (legacyServer != null) //legacy | |||||
catch (UriFormatException) | |||||
{ | { | ||||
servers.Add(legacyServer); | |||||
return null; | |||||
} | } | ||||
else //SIP002 | |||||
Server server = new Server | |||||
{ | { | ||||
Uri parsedUrl; | |||||
try | |||||
{ | |||||
parsedUrl = new Uri(serverUrl); | |||||
} | |||||
catch (UriFormatException) | |||||
{ | |||||
continue; | |||||
} | |||||
Server server = new Server | |||||
{ | |||||
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), | |||||
server = parsedUrl.IdnHost, | |||||
server_port = parsedUrl.Port, | |||||
}; | |||||
// parse base64 UserInfo | |||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); | |||||
string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 | |||||
string userInfo = ""; | |||||
try | |||||
{ | |||||
userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( | |||||
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); | |||||
} | |||||
catch (FormatException) | |||||
{ | |||||
continue; | |||||
} | |||||
string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); | |||||
if (userInfoParts.Length != 2) | |||||
{ | |||||
continue; | |||||
} | |||||
server.method = userInfoParts[0]; | |||||
server.password = userInfoParts[1]; | |||||
NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); | |||||
string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); | |||||
if (pluginParts.Length > 0) | |||||
{ | |||||
server.plugin = pluginParts[0] ?? ""; | |||||
} | |||||
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), | |||||
server = parsedUrl.IdnHost, | |||||
server_port = parsedUrl.Port, | |||||
}; | |||||
// parse base64 UserInfo | |||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); | |||||
string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 | |||||
string userInfo = ""; | |||||
try | |||||
{ | |||||
userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( | |||||
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); | |||||
} | |||||
catch (FormatException) | |||||
{ | |||||
return null; | |||||
} | |||||
string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); | |||||
if (userInfoParts.Length != 2) | |||||
{ | |||||
return null; | |||||
} | |||||
server.method = userInfoParts[0]; | |||||
server.password = userInfoParts[1]; | |||||
if (pluginParts.Length > 1) | |||||
{ | |||||
server.plugin_opts = pluginParts[1] ?? ""; | |||||
} | |||||
NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); | |||||
string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); | |||||
if (pluginParts.Length > 0) | |||||
{ | |||||
server.plugin = pluginParts[0] ?? ""; | |||||
} | |||||
servers.Add(server); | |||||
if (pluginParts.Length > 1) | |||||
{ | |||||
server.plugin_opts = pluginParts[1] ?? ""; | |||||
} | } | ||||
return server; | |||||
} | } | ||||
return servers; | |||||
} | |||||
public static List<Server> GetServers(string ssURL) | |||||
{ | |||||
return ssURL | |||||
.Split('\r', '\n', ' ') | |||||
.Select(u => ParseURL(u)) | |||||
.Where(s => s != null) | |||||
.ToList(); | |||||
} | } | ||||
public string Identifier() | public string Identifier() | ||||
@@ -427,7 +427,7 @@ namespace Shadowsocks.View | |||||
ServersListBox.Items.Clear(); | ServersListBox.Items.Clear(); | ||||
foreach (Server server in configuration.configs) | foreach (Server server in configuration.configs) | ||||
{ | { | ||||
ServersListBox.Items.Add(server.FriendlyName()); | |||||
ServersListBox.Items.Add(server.ToString()); | |||||
} | } | ||||
} | } | ||||
@@ -502,7 +502,7 @@ namespace Shadowsocks.View | |||||
} | } | ||||
if (_lastSelectedIndex >= 0 && _lastSelectedIndex < _modifiedConfiguration.configs.Count) | if (_lastSelectedIndex >= 0 && _lastSelectedIndex < _modifiedConfiguration.configs.Count) | ||||
{ | { | ||||
ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); | |||||
ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].ToString(); | |||||
} | } | ||||
UpdateButtons(); | UpdateButtons(); | ||||
LoadSelectedServerDetails(); | LoadSelectedServerDetails(); | ||||
@@ -172,7 +172,7 @@ namespace Shadowsocks.View | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
serverInfo = config.GetCurrentServer().FriendlyName(); | |||||
serverInfo = config.GetCurrentServer().ToString(); | |||||
} | } | ||||
// show more info by hacking the P/Invoke declaration for NOTIFYICONDATA inside Windows Forms | // show more info by hacking the P/Invoke declaration for NOTIFYICONDATA inside Windows Forms | ||||
string text = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version + "\n" + | string text = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version + "\n" + | ||||
@@ -478,7 +478,7 @@ namespace Shadowsocks.View | |||||
{ | { | ||||
if (Configuration.ChecksServer(server)) | if (Configuration.ChecksServer(server)) | ||||
{ | { | ||||
MenuItem item = new MenuItem(server.FriendlyName()); | |||||
MenuItem item = new MenuItem(server.ToString()); | |||||
item.Tag = configuration.configs.FindIndex(s => s == server); | item.Tag = configuration.configs.FindIndex(s => s == server); | ||||
item.Click += AServerItem_Click; | item.Click += AServerItem_Click; | ||||
items.Add(strategyCount + serverCount, item); | items.Add(strategyCount + serverCount, item); | ||||
@@ -64,14 +64,14 @@ namespace Shadowsocks.View | |||||
private void QRCodeForm_Load(object sender, EventArgs e) | private void QRCodeForm_Load(object sender, EventArgs e) | ||||
{ | { | ||||
var servers = Configuration.Load(); | |||||
var serverDatas = servers.configs.Select( | |||||
Configuration config = Configuration.Load(); | |||||
List<KeyValuePair<string, string>> serverDatas = config.configs.Select( | |||||
server => | server => | ||||
new KeyValuePair<string, string>(ShadowsocksController.GetServerURL(server), server.FriendlyName()) | |||||
new KeyValuePair<string, string>(server.GetURL(config.generateLegacyUrl), server.ToString()) | |||||
).ToList(); | ).ToList(); | ||||
listBox1.DataSource = serverDatas; | listBox1.DataSource = serverDatas; | ||||
var selectIndex = serverDatas.FindIndex(serverData => serverData.Key.StartsWith(code)); | |||||
int selectIndex = serverDatas.FindIndex(serverData => serverData.Key.StartsWith(code)); | |||||
if (selectIndex >= 0) listBox1.SetSelected(selectIndex, true); | if (selectIndex >= 0) listBox1.SetSelected(selectIndex, true); | ||||
} | } | ||||
@@ -243,7 +243,7 @@ namespace Shadowsocks.Test | |||||
string expected = testCase.Key; | string expected = testCase.Key; | ||||
Server config = testCase.Value; | Server config = testCase.Value; | ||||
var actual = ShadowsocksController.GetServerURL(config); | |||||
var actual = config.GetURL(true); | |||||
Assert.AreEqual(expected, actual); | Assert.AreEqual(expected, actual); | ||||
} | } | ||||
} | } | ||||