Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New option for ss:// URL association #2855

Merged
merged 9 commits into from
Apr 22, 2020
47 changes: 47 additions & 0 deletions shadowsocks-csharp/Controller/Service/NamedPipeServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Net;
using System.Text;

namespace Shadowsocks.Controller
{
class RequestAddUrlEventArgs : EventArgs
{
public readonly string Url;

public RequestAddUrlEventArgs(string url)
{
this.Url = url;
}
}

internal class NamedPipeServer
{
public event EventHandler<RequestAddUrlEventArgs> AddUrlRequested;
public async void Run(string path)
{
byte[] buf = new byte[4096];
while (true)
{
using (NamedPipeServerStream stream = new NamedPipeServerStream(path))
{
stream.WaitForConnection();
await stream.ReadAsync(buf, 0, 4);
int opcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buf, 0));
if (opcode == 1)
{
await stream.ReadAsync(buf, 0, 4);
int strlen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buf, 0));

await stream.ReadAsync(buf, 0, strlen);
string url = Encoding.UTF8.GetString(buf, 0, strlen);

AddUrlRequested?.Invoke(this, new RequestAddUrlEventArgs(url));
}
stream.Close();
}
}
}
}
}
19 changes: 19 additions & 0 deletions shadowsocks-csharp/Controller/ShadowsocksController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,25 @@ public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configura
StatisticsStrategyConfiguration.Save(configuration);
}

public bool AskAddServerBySSURL(string ssURL)
{
var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes)
{
if (AddServerBySSURL(ssURL))
{
MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL));
return true;
}
else
{
MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid."));
}
}
return false;
}


public bool AddServerBySSURL(string ssURL)
{
try
Expand Down
107 changes: 107 additions & 0 deletions shadowsocks-csharp/Controller/System/ProtocolHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using Microsoft.Win32;
using NLog;
using Shadowsocks.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Shadowsocks.Controller
{
static class ProtocolHandler
{
const string ssURLRegKey = @"SOFTWARE\Classes\ss";

private static Logger logger = LogManager.GetCurrentClassLogger();

// Don't use Application.ExecutablePath
// see https://stackoverflow.com/questions/12945805/odd-c-sharp-path-issue
private static readonly string ExecutablePath = Assembly.GetEntryAssembly().Location;

// TODO: Elevate when necessary
public static bool Set(bool enabled)
{
RegistryKey ssURLAssociation = null;

try
{
ssURLAssociation = Registry.CurrentUser.CreateSubKey(ssURLRegKey, RegistryKeyPermissionCheck.ReadWriteSubTree);
if (ssURLAssociation == null)
{
logger.Error(@"Failed to create HKCU\SOFTWARE\Classes\ss to register ss:// association.");
return false;
}
if (enabled)
{
ssURLAssociation.SetValue("", "URL:Shadowsocks");
ssURLAssociation.SetValue("URL Protocol", "");
var shellOpen = ssURLAssociation.CreateSubKey("shell").CreateSubKey("open").CreateSubKey("command");
shellOpen.SetValue("", $"{ExecutablePath} --open-url %1");
logger.Info(@"Successfully added ss:// association.");
}
else
{
Registry.CurrentUser.DeleteSubKeyTree(ssURLRegKey);
logger.Info(@"Successfully removed ss:// association.");
}
return true;
}
catch (Exception e)
{
logger.LogUsefulException(e);
return false;
}
finally
{
if (ssURLAssociation != null)
{
try
{
ssURLAssociation.Close();
ssURLAssociation.Dispose();
}
catch (Exception e)
{ logger.LogUsefulException(e); }
}
}
}

public static bool Check()
{
RegistryKey ssURLAssociation = null;
try
{
ssURLAssociation = Registry.CurrentUser.OpenSubKey(ssURLRegKey, true);
if (ssURLAssociation == null)
{
//logger.Info(@"ss:// links not associated.");
return false;
}

var shellOpen = ssURLAssociation.OpenSubKey("shell").OpenSubKey("open").OpenSubKey("command");
return (string)shellOpen.GetValue("") == $"{ExecutablePath} --open-url %1";
}
catch (Exception e)
{
logger.LogUsefulException(e);
return false;
}
finally
{
if (ssURLAssociation != null)
{
try
{
ssURLAssociation.Close();
ssURLAssociation.Dispose();
}
catch (Exception e)
{ logger.LogUsefulException(e); }
}
}
}

}
}
4 changes: 4 additions & 0 deletions shadowsocks-csharp/Data/i18n.csv
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Servers,Серверы,服务器,伺服器,サーバー,서버
Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…
Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정
Start on Boot,Автозагрузка,开机启动,開機啟動,システムと同時に起動,시스템 시작 시에 시작하기
Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,,
Forward Proxy...,Прямой прокси…,正向代理设置...,正向 Proxy 設定...,フォワードプロキシの設定...,포워드 프록시
Allow other Devices to connect,Общий доступ к подключению,允许其他设备连入,允許其他裝置連入,他のデバイスからの接続を許可する,다른 기기에서 연결 허용
Local PAC,Локальный PAC,使用本地 PAC,使用本機 PAC,ローカル PAC,로컬 PAC
Expand Down Expand Up @@ -184,6 +185,9 @@ Find Shadowsocks icon in your notify tray.,Значок Shadowsocks можно
"If you want to start multiple Shadowsocks, make a copy in another directory.","Если вы хотите запустить несколько копий Shadowsocks одновременно, создайте отдельную папку на каждую копию.",如果想同时启动多个,可以另外复制一份到别的目录。,如果想同時啟動多個,可以另外複製一份至別的目錄。,複数起動したい場合は、プログラムファイルを別のフォルダーにコピーしてから、もう一度実行して下さい。,"만약 여러 개의 Shadowsocks를 사용하고 싶으시다면, 파일을 다른 곳에 복사해주세요."
Failed to decode QRCode,Не удалось распознать QRCode,无法解析二维码,QR 碼解碼失敗,QR コードの読み取りに失敗しました。,QR코드를 해석하는데에 실패했습니다.
Failed to update registry,Не удалось обновить запись в реестре,无法修改注册表,無法修改登錄檔,レジストリの更新に失敗しました。,레지스트리를 업데이트하는데에 실패했습니다.
Import from URL: {0} ?,импортировать из адреса: {0} ?,从URL导入: {0} ?,從URL匯入: {0} ?,,
Successfully imported from {0},Успешно импортировано из {0},导入成功:{0},導入成功:{0},,
Failed to import. Please check if the link is valid.,,导入失败,请检查链接是否有效。,導入失敗,請檢查鏈接是否有效。,,
System Proxy On: ,Системный прокси:,系统代理已启用:,系統 Proxy 已啟用:,システム プロキシが有効:,시스템 프록시 활성화됨:
Running: Port {0},Запущен на порту {0},正在运行:端口 {0},正在執行:連接埠號碼 {0},実行中:ポート {0},실행 중: 포트 {0}번
"Unexpected error, shadowsocks will exit. Please report to","Непредвиденная ошибка, пожалуйста сообщите на",非预期错误,Shadowsocks将退出。请提交此错误到,非預期錯誤,Shadowsocks 將結束。請報告此錯誤至,予想外のエラーが発生したため、Shadowsocks を終了します。詳しくは下記までお問い合わせ下さい:,알 수 없는 오류로 Shadowsocks가 종료될 것입니다. 오류를 제보해주세요:
Expand Down
139 changes: 99 additions & 40 deletions shadowsocks-csharp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;
using NLog;
using Microsoft.Win32;

using Shadowsocks.Controller;
using Shadowsocks.Controller.Hotkeys;
using Shadowsocks.Util;
using Shadowsocks.View;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Shadowsocks
{
static class Program
internal static class Program
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public static ShadowsocksController MainController { get; private set; }
public static MenuViewController MenuController { get; private set; }
public static string[] Args { get; private set; }
Expand All @@ -24,15 +29,15 @@ static class Program
/// </summary>
/// </summary>
[STAThread]
static void Main(string[] args)
private static void Main(string[] args)
{
Directory.SetCurrentDirectory(Application.StartupPath);
// todo: initialize the NLog configuartion
Model.NLogConfig.TouchAndApplyNLogConfig();

// .NET Framework 4.7.2 on Win7 compatibility
System.Net.ServicePointManager.SecurityProtocol |=
System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol |=
SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

// store args for further use
Args = args;
Expand All @@ -50,27 +55,58 @@ static void Main(string[] args)
if (DialogResult.OK == MessageBox.Show(I18N.GetString("Unsupported .NET Framework, please update to {0} or later.", "4.7.2"),
"Shadowsocks Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Error))
{
//Process.Start("https://www.microsoft.com/download/details.aspx?id=53344"); // 4.6.2
Process.Start("https://dotnet.microsoft.com/download/dotnet-framework/net472");
}
return;
}
string pipename = $"Shadowsocks\\{Application.StartupPath.GetHashCode()}";

Utils.ReleaseMemory(true);
using (Mutex mutex = new Mutex(false, $"Global\\Shadowsocks_{Application.StartupPath.GetHashCode()}"))
string addedUrl = null;

using (NamedPipeClientStream pipe = new NamedPipeClientStream(pipename))
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// handle UI exceptions
Application.ThreadException += Application_ThreadException;
// handle non-UI exceptions
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Application.ApplicationExit += Application_ApplicationExit;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
AutoStartup.RegisterForRestart(true);

if (!mutex.WaitOne(0, false))
bool pipeExist = false;
try
{
pipe.Connect(10);
pipeExist = true;
}
catch (TimeoutException)
{
pipeExist = false;
}

// TODO: switch to better argv parser when it's getting complicate
List<string> alist = Args.ToList();
// check --open-url param
int urlidx = alist.IndexOf("--open-url") + 1;
if (urlidx > 0)
{
if (Args.Length <= urlidx)
{
return;
}

// --open-url exist, and no other instance, add it later
if (!pipeExist)
{
addedUrl = Args[urlidx];
}
// has other instance, send url via pipe then exit
else
{
byte[] b = Encoding.UTF8.GetBytes(Args[urlidx]);
byte[] opAddUrl = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(1));
byte[] blen = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(b.Length));
pipe.Write(opAddUrl, 0, 4); // opcode addurl
pipe.Write(blen, 0, 4);
pipe.Write(b, 0, b.Length);
pipe.Close();
return;
}
}
// has another instance, and no need to communicate with it return
else if (pipeExist)
{
Process[] oldProcesses = Process.GetProcessesByName("Shadowsocks");
if (oldProcesses.Length > 0)
Expand All @@ -83,21 +119,44 @@ static void Main(string[] args)
I18N.GetString("Shadowsocks is already running."));
return;
}
Directory.SetCurrentDirectory(Application.StartupPath);

}

Utils.ReleaseMemory(true);

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// handle UI exceptions
Application.ThreadException += Application_ThreadException;
// handle non-UI exceptions
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Application.ApplicationExit += Application_ApplicationExit;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
AutoStartup.RegisterForRestart(true);

Directory.SetCurrentDirectory(Application.StartupPath);

#if DEBUG
// truncate privoxy log file while debugging
string privoxyLogFilename = Utils.GetTempPath("privoxy.log");
if (File.Exists(privoxyLogFilename))
using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { }
// truncate privoxy log file while debugging
string privoxyLogFilename = Utils.GetTempPath("privoxy.log");
if (File.Exists(privoxyLogFilename))
using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { }
#endif
MainController = new ShadowsocksController();
MenuController = new MenuViewController(MainController);
MainController = new ShadowsocksController();
MenuController = new MenuViewController(MainController);

HotKeys.Init(MainController);
MainController.Start();
Application.Run();
}
HotKeys.Init(MainController);
MainController.Start();

NamedPipeServer namedPipeServer = new NamedPipeServer();
Task.Run(() => namedPipeServer.Run(pipename));
namedPipeServer.AddUrlRequested += (_1, e) => MainController.AskAddServerBySSURL(e.Url);
if (!addedUrl.IsNullOrEmpty())
{
MainController.AskAddServerBySSURL(addedUrl);
}

Application.Run();
}

private static int exited = 0;
Expand Down Expand Up @@ -135,7 +194,7 @@ private static void SystemEvents_PowerModeChanged(object sender, PowerModeChange
logger.Info("os wake up");
if (MainController != null)
{
System.Threading.Tasks.Task.Factory.StartNew(() =>
Task.Factory.StartNew(() =>
{
Thread.Sleep(10 * 1000);
try
Expand Down
Loading