Browse Source

Merge pull request #2855 from database64128/master

New option for ss:// URL association
tags/4.2.0.0
Allen Zhu GitHub 4 years ago
parent
commit
b706cdf257
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 292 additions and 42 deletions
  1. +47
    -0
      shadowsocks-csharp/Controller/Service/NamedPipeServer.cs
  2. +19
    -0
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  3. +107
    -0
      shadowsocks-csharp/Controller/System/ProtocolHandler.cs
  4. +4
    -0
      shadowsocks-csharp/Data/i18n.csv
  5. +99
    -40
      shadowsocks-csharp/Program.cs
  6. +14
    -2
      shadowsocks-csharp/View/MenuViewController.cs
  7. +2
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 47
- 0
shadowsocks-csharp/Controller/Service/NamedPipeServer.cs View File

@@ -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
- 0
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -218,6 +218,25 @@ namespace Shadowsocks.Controller
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


+ 107
- 0
shadowsocks-csharp/Controller/System/ProtocolHandler.cs View File

@@ -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
- 0
shadowsocks-csharp/Data/i18n.csv View File

@@ -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
@@ -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가 종료될 것입니다. 오류를 제보해주세요:


+ 99
- 40
shadowsocks-csharp/Program.cs View File

@@ -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; }
@@ -24,15 +29,15 @@ namespace Shadowsocks
/// </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;
@@ -50,27 +55,58 @@ namespace Shadowsocks
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)
@@ -83,21 +119,44 @@ namespace Shadowsocks
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;
@@ -135,7 +194,7 @@ namespace Shadowsocks
logger.Info("os wake up");
if (MainController != null)
{
System.Threading.Tasks.Task.Factory.StartNew(() =>
Task.Factory.StartNew(() =>
{
Thread.Sleep(10 * 1000);
try


+ 14
- 2
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -36,6 +36,7 @@ namespace Shadowsocks.View
private ContextMenu contextMenu1;
private MenuItem disableItem;
private MenuItem AutoStartupItem;
private MenuItem ProtocolHandlerItem;
private MenuItem ShareOverLANItem;
private MenuItem SeperatorItem;
private MenuItem ConfigItem;
@@ -315,6 +316,7 @@ namespace Shadowsocks.View
this.proxyItem = CreateMenuItem("Forward Proxy...", new EventHandler(this.proxyItem_Click)),
new MenuItem("-"),
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)),
this.ProtocolHandlerItem = CreateMenuItem("Associate ss:// Links", new EventHandler(this.ProtocolHandlerItem_Click)),
this.ShareOverLANItem = CreateMenuItem("Allow other Devices to connect", new EventHandler(this.ShareOverLANItem_Click)),
new MenuItem("-"),
this.hotKeyItem = CreateMenuItem("Edit Hotkeys...", new EventHandler(this.hotKeyItem_Click)),
@@ -442,6 +444,7 @@ namespace Shadowsocks.View
VerboseLoggingToggleItem.Checked = config.isVerboseLogging;
ShowPluginOutputToggleItem.Checked = config.showPluginOutput;
AutoStartupItem.Checked = AutoStartup.Check();
ProtocolHandlerItem.Checked = ProtocolHandler.Check();
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac;
localPACItem.Checked = !onlinePACItem.Checked;
secureLocalPacUrlToggleItem.Checked = config.secureLocalPac;
@@ -821,8 +824,7 @@ namespace Shadowsocks.View
private void ImportURLItem_Click(object sender, EventArgs e)
{
var success = controller.AddServerBySSURL(Clipboard.GetText(TextDataFormat.Text));
if (success)
if (controller.AskAddServerBySSURL(Clipboard.GetText(TextDataFormat.Text)))
{
ShowConfigForm();
}
@@ -845,6 +847,16 @@ namespace Shadowsocks.View
{
MessageBox.Show(I18N.GetString("Failed to update registry"));
}
LoadCurrentConfiguration();
}
private void ProtocolHandlerItem_Click(object sender, EventArgs e)
{
ProtocolHandlerItem.Checked = !ProtocolHandlerItem.Checked;
if (!ProtocolHandler.Set(ProtocolHandlerItem.Checked))
{
MessageBox.Show(I18N.GetString("Failed to update registry"));
}
LoadCurrentConfiguration();
}
private void LocalPACItem_Click(object sender, EventArgs e)


+ 2
- 0
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -113,6 +113,8 @@
<Compile Include="Controller\HotkeyReg.cs" />
<Compile Include="Controller\LoggerExtension.cs" />
<Compile Include="Controller\Service\PACDaemon.cs" />
<Compile Include="Controller\Service\NamedPipeServer.cs" />
<Compile Include="Controller\System\ProtocolHandler.cs" />
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" />
<Compile Include="Encryption\AEAD\AEADEncryptor.cs" />
<Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" />


Loading…
Cancel
Save