mirror of
https://github.com/qinyuhang/ShadowsocksX-NG-R.git
synced 2026-10-20 14:34:30 +00:00
Merge branch 'develop' of https://github.com/shadowsocks/ShadowsocksX-NG into develop
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
08FCA0FF1E24BE1A0070984F /* example-gui-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 08FCA0FE1E24BE1A0070984F /* example-gui-config.json */; };
|
||||
258E511BA910B0521B24DAB8 /* Pods_ShadowsocksX_NG.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 283ED1A8E9B711AC65670031 /* Pods_ShadowsocksX_NG.framework */; };
|
||||
9B07EFA71D048BBB0052D9DF /* ss-local in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA61D048BBB0052D9DF /* ss-local */; };
|
||||
9B07EFAC1D048E880052D9DF /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA81D048E880052D9DF /* menu_icon@2x.png */; };
|
||||
@@ -59,6 +60,7 @@
|
||||
C6E28E951DA79705004F8330 /* HTTPPreferencesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C6E28E971DA79705004F8330 /* HTTPPreferencesWindowController.xib */; };
|
||||
C8E42A6C1D4F270A0074C7EA /* UserRulesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E42A6A1D4F270A0074C7EA /* UserRulesController.swift */; };
|
||||
C8E42A6E1D4F2CAF0074C7EA /* UserRulesController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8E42A701D4F2CAF0074C7EA /* UserRulesController.xib */; };
|
||||
D8E3630B1E2072980027449B /* ServerProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E3630A1E2072980027449B /* ServerProfileTests.swift */; };
|
||||
E0E57CCA7EB34B90F9D340F2 /* Pods_ShadowsocksX_NGTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 388120F062D7EB7DD0D8DDCA /* Pods_ShadowsocksX_NGTests.framework */; };
|
||||
F0809FF1595BE2966343D3C7 /* libPods-proxy_conf_helper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E7783AEDB4A3BDDC9FF16AC /* libPods-proxy_conf_helper.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -93,6 +95,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
08FCA0FE1E24BE1A0070984F /* example-gui-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "example-gui-config.json"; sourceTree = "<group>"; };
|
||||
19083CFCED87354F006967FF /* Pods_ShadowsocksX_NGUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShadowsocksX_NGUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1E7783AEDB4A3BDDC9FF16AC /* libPods-proxy_conf_helper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-proxy_conf_helper.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
283ED1A8E9B711AC65670031 /* Pods_ShadowsocksX_NG.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShadowsocksX_NG.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -176,6 +179,7 @@
|
||||
C8E42A6A1D4F270A0074C7EA /* UserRulesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserRulesController.swift; sourceTree = "<group>"; };
|
||||
C8E42A6F1D4F2CAF0074C7EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UserRulesController.xib; sourceTree = "<group>"; };
|
||||
C8E42A721D4F2CB10074C7EA /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/UserRulesController.strings"; sourceTree = "<group>"; };
|
||||
D8E3630A1E2072980027449B /* ServerProfileTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerProfileTests.swift; sourceTree = "<group>"; };
|
||||
E9E9FB3855DA55D0710EE7BD /* Pods-ShadowsocksX-NG.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NG.release.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NG/Pods-ShadowsocksX-NG.release.xcconfig"; sourceTree = "<group>"; };
|
||||
FE3237E9FB24D9B924A0E630 /* Pods-ShadowsocksX-NG.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NG.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NG/Pods-ShadowsocksX-NG.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -227,6 +231,7 @@
|
||||
9B07EFB01D048E900052D9DF /* Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
08FCA0FE1E24BE1A0070984F /* example-gui-config.json */,
|
||||
C6D4298E1DA75988002A5711 /* install_privoxy.sh */,
|
||||
C6D4298F1DA75988002A5711 /* privoxy */,
|
||||
C6D429901DA75988002A5711 /* reload_conf_privoxy.sh */,
|
||||
@@ -309,6 +314,7 @@
|
||||
9B0BFFF71D0460A70040E62B /* ShadowsocksX-NGTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8E3630A1E2072980027449B /* ServerProfileTests.swift */,
|
||||
9B0BFFF81D0460A70040E62B /* ShadowsocksX_NGTests.swift */,
|
||||
9B0BFFFA1D0460A70040E62B /* Info.plist */,
|
||||
);
|
||||
@@ -490,6 +496,7 @@
|
||||
C6D429951DA75988002A5711 /* reload_conf_privoxy.sh in Resources */,
|
||||
9B0BFFEB1D0460A70040E62B /* Assets.xcassets in Resources */,
|
||||
9B2491B71D0ACC3E003BBECC /* AdvPreferencesWindowController.xib in Resources */,
|
||||
08FCA0FF1E24BE1A0070984F /* example-gui-config.json in Resources */,
|
||||
9B3FFF271D0898EB0019A709 /* gfwlist.txt in Resources */,
|
||||
C6D429931DA75988002A5711 /* install_privoxy.sh in Resources */,
|
||||
9BC70EDC1D2E3E3100EDA4CA /* Localizable.strings in Resources */,
|
||||
@@ -666,6 +673,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9B0BFFF91D0460A70040E62B /* ShadowsocksX_NGTests.swift in Sources */,
|
||||
D8E3630B1E2072980027449B /* ServerProfileTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -914,6 +922,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.qiuyuzhou.ShadowsocksX-NGTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "ShadowsocksX-NG/ShadowsocksX-NG-Bridging-Header.h";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ShadowsocksX-NG.app/Contents/MacOS/ShadowsocksX-NG";
|
||||
};
|
||||
@@ -929,6 +938,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.qiuyuzhou.ShadowsocksX-NGTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "ShadowsocksX-NG/ShadowsocksX-NG-Bridging-Header.h";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ShadowsocksX-NG.app/Contents/MacOS/ShadowsocksX-NG";
|
||||
};
|
||||
|
||||
@@ -39,6 +39,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
@IBOutlet weak var serversMenuItem: NSMenuItem!
|
||||
@IBOutlet var showQRCodeMenuItem: NSMenuItem!
|
||||
@IBOutlet var scanQRCodeMenuItem: NSMenuItem!
|
||||
@IBOutlet var showBunchJsonExampleFileItem: NSMenuItem!
|
||||
@IBOutlet var importBunchJsonFileItem: NSMenuItem!
|
||||
@IBOutlet var exportAllServerProfileItem: NSMenuItem!
|
||||
@IBOutlet var serversPreferencesMenuItem: NSMenuItem!
|
||||
|
||||
@IBOutlet weak var lanchAtLoginMenuItem: NSMenuItem!
|
||||
@@ -115,6 +118,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
}
|
||||
self.updateServersMenu()
|
||||
self.updateRunningModeMenu()
|
||||
SyncSSLocal()
|
||||
}
|
||||
)
|
||||
@@ -141,9 +145,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
var isChanged = false
|
||||
|
||||
for url in urls {
|
||||
let profielDict = ParseSSURL(url)
|
||||
if let profielDict = profielDict {
|
||||
let profile = ServerProfile.fromDictionary(profielDict as [String : AnyObject])
|
||||
if let profile = ServerProfile(url: url) {
|
||||
mgr.profiles.append(profile)
|
||||
isChanged = true
|
||||
|
||||
@@ -337,7 +339,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
ScanQRCodeOnScreen()
|
||||
}
|
||||
|
||||
@IBAction func toggleLaunghAtLogin(_ sender: NSMenuItem) {
|
||||
@IBAction func showBunchJsonExampleFile(sender: NSMenuItem) {
|
||||
ServerProfileManager.showExampleConfigFile()
|
||||
}
|
||||
|
||||
@IBAction func importBunchJsonFile(sender: NSMenuItem) {
|
||||
ServerProfileManager.instance.importConfigFile()
|
||||
//updateServersMenu()//not working
|
||||
}
|
||||
|
||||
@IBAction func exportAllServerProfile(sender: NSMenuItem) {
|
||||
ServerProfileManager.instance.exportConfigFile()
|
||||
}
|
||||
|
||||
@IBAction func toggleLaunghAtLogin(sender: NSMenuItem) {
|
||||
launchAtLoginController.launchAtLogin = !launchAtLoginController.launchAtLogin;
|
||||
updateLaunchAtLoginMenu()
|
||||
}
|
||||
@@ -539,6 +554,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
let showQRItem = showQRCodeMenuItem
|
||||
let scanQRItem = scanQRCodeMenuItem
|
||||
let preferencesItem = serversPreferencesMenuItem
|
||||
let showBunch = showBunchJsonExampleFileItem
|
||||
let importBuntch = importBunchJsonFileItem
|
||||
let exportAllServer = exportAllServerProfileItem
|
||||
|
||||
var i = 0
|
||||
for p in mgr.profiles {
|
||||
@@ -565,6 +583,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
serversMenuItem.submenu?.addItem(showQRItem!)
|
||||
serversMenuItem.submenu?.addItem(scanQRItem!)
|
||||
serversMenuItem.submenu?.addItem(showBunch!)
|
||||
serversMenuItem.submenu?.addItem(importBuntch!)
|
||||
serversMenuItem.submenu?.addItem(exportAllServer!)
|
||||
serversMenuItem.submenu?.addItem(NSMenuItem.separator())
|
||||
serversMenuItem.submenu?.addItem(preferencesItem!)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wCp-nB-dda">
|
||||
<rect key="frame" x="18" y="308" width="439" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Don't change these preferences if you have no idea what are they." id="8wL-qv-5nD">
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Don't change these preferences if you have no idea what they are." id="8wL-qv-5nD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
"By Handle SS URL" = "By Handle SS URL";
|
||||
|
||||
" Encription Method: \(profile.method)" = " Encription Method: \(profile.method)";
|
||||
" Encryption Method: \(profile.method)" = " Encryption Method: \(profile.method)";
|
||||
|
||||
"Current server profile is not valid." = "Current server profile is not valid.";
|
||||
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="ShadowsocksX_NG" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="autoModeMenuItem" destination="r07-Gu-aEz" id="9aH-pQ-Rgi"/>
|
||||
<outlet property="exportAllServerProfileItem" destination="6k0-gn-DQv" id="W2x-96-ISj"/>
|
||||
<outlet property="globalModeMenuItem" destination="Mw3-Jm-eXA" id="ar5-Yx-3ze"/>
|
||||
<outlet property="importBunchJsonFileItem" destination="T9g-gy-gvv" id="vua-jg-YWe"/>
|
||||
<outlet property="lanchAtLoginMenuItem" destination="eUq-p7-ICK" id="q3D-7x-0db"/>
|
||||
<outlet property="hudWindow" destination="QWV-F6-ac1" id="K6D-a4-oqE"/>
|
||||
<outlet property="isNameTextField" destination="rUN-Nq-HDb" id="ayK-aM-rSa"/>
|
||||
<outlet property="lanchAtLoginMenuItem" destination="eUq-p7-ICK" id="w4p-0c-DZn"/>
|
||||
<outlet property="manualModeMenuItem" destination="8PR-gs-c5N" id="9qz-mU-5kt"/>
|
||||
<outlet property="panelView" destination="rai-SH-9tZ" id="5aU-ld-rWb"/>
|
||||
<outlet property="proxyMenuItem" destination="diI-fB-Rss" id="Qjk-9U-3Qy"/>
|
||||
@@ -27,6 +29,7 @@
|
||||
<outlet property="scanQRCodeMenuItem" destination="Qe6-bF-paT" id="XHa-pa-nCa"/>
|
||||
<outlet property="serversMenuItem" destination="u5M-hQ-VSc" id="8gp-SY-Y4U"/>
|
||||
<outlet property="serversPreferencesMenuItem" destination="M5r-E7-44f" id="voe-SX-k6a"/>
|
||||
<outlet property="showBunchJsonExampleFileItem" destination="pdy-JE-50Q" id="xcZ-ep-mON"/>
|
||||
<outlet property="showQRCodeMenuItem" destination="R6A-96-Zcb" id="XHz-pz-nCz"/>
|
||||
<outlet property="showRunningModeMenuItem" destination="CCV-hX-fVA" id="AU2-hH-w8F"/>
|
||||
<outlet property="statusMenu" destination="Hob-KD-bx9" id="clA-ZW-0pT"/>
|
||||
@@ -113,6 +116,24 @@
|
||||
<action selector="scanQRCodeFromScreen:" target="Voe-Tx-rLC" id="zQZ-IT-H4a"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show Bunch Json Example File..." id="pdy-JE-50Q">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showBunchJsonExampleFileWithSender:" target="Voe-Tx-rLC" id="lMy-Lo-OyB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Import Bunch Json File..." id="T9g-gy-gvv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="importBunchJsonFileWithSender:" target="Voe-Tx-rLC" id="bRk-cg-9yn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Export All Server To Json..." id="6k0-gn-DQv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="exportAllServerProfileWithSender:" target="Voe-Tx-rLC" id="0ip-dJ-juc"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="6sL-A4-S7N"/>
|
||||
<menuItem title="Server Preferences..." id="M5r-E7-44f">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
@@ -138,7 +159,7 @@
|
||||
<menuItem title="Launch At Login" id="eUq-p7-ICK">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleLaunghAtLogin:" target="Voe-Tx-rLC" id="erk-ce-mO3"/>
|
||||
<action selector="toggleLaunghAtLoginWithSender:" target="Voe-Tx-rLC" id="eFB-1Q-AOT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="DTJ-NF-Wxr"/>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<capability name="box content view" minToolsVersion="7.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="PreferencesWindowController" customModule="ShadowsocksX_NG" customModuleProvider="target">
|
||||
@@ -26,7 +27,7 @@
|
||||
<window title="Server Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" oneShot="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<rect key="contentRect" x="423" y="365" width="590" height="330"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1418"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="590" height="330"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -70,6 +71,7 @@
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-2" id="fz3-X6-PbB"/>
|
||||
<outlet property="delegate" destination="-2" id="6zR-v1-wZj"/>
|
||||
<outlet property="menu" destination="QmU-th-vjP" id="J0d-c9-pYs"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
@@ -339,6 +341,17 @@ DQ
|
||||
<real key="minimum" value="1"/>
|
||||
<real key="maximum" value="65535"/>
|
||||
</numberFormatter>
|
||||
<menu showsStateColumn="NO" id="QmU-th-vjP">
|
||||
<items>
|
||||
<menuItem title="Duplicate" id="bl9-lq-u9V">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="duplicate:" target="-2" id="dEb-Dt-yBW"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
<point key="canvasLocation" x="-25.5" y="171.5"/>
|
||||
</menu>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSAddTemplate" width="11" height="11"/>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
let SS_LOCAL_VERSION = "2.4.6"
|
||||
let SS_LOCAL_VERSION = "2.5.6"
|
||||
let PRIVOXY_VERSION = "3.0.26.static"
|
||||
let APP_SUPPORT_DIR = "/Library/Application Support/ShadowsocksX-NG/"
|
||||
let LAUNCH_AGENT_DIR = "/Library/LaunchAgents/"
|
||||
@@ -114,7 +114,8 @@ func InstallSSLocal() {
|
||||
let homeDir = NSHomeDirectory()
|
||||
let appSupportDir = homeDir+APP_SUPPORT_DIR
|
||||
if !fileMgr.fileExists(atPath: appSupportDir + "ss-local-\(SS_LOCAL_VERSION)/ss-local")
|
||||
|| !fileMgr.fileExists(atPath: appSupportDir + "libcrypto.1.0.0.dylib") {
|
||||
|| !fileMgr.fileExists(atPath: appSupportDir + "libcrypto.1.0.0.dylib")
|
||||
|| !fileMgr.fileExists(atPath: appSupportDir + "libpcre.1.dylib") {
|
||||
let bundle = Bundle.main
|
||||
let installerPath = bundle.path(forResource: "install_ss_local.sh", ofType: nil)
|
||||
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
|
||||
|
||||
@@ -48,9 +48,16 @@ class PreferencesWindowController: NSWindowController
|
||||
"aes-256-cfb",
|
||||
"des-cfb",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"idea-cfb",
|
||||
"cast5-cfb",
|
||||
"rc2-cfb",
|
||||
"rc4-md5",
|
||||
"seed-cfb",
|
||||
"chacha20",
|
||||
"chacha20-ietf",
|
||||
"salsa20",
|
||||
"rc4",
|
||||
"table",
|
||||
@@ -114,6 +121,20 @@ class PreferencesWindowController: NSWindowController
|
||||
window?.performClose(self)
|
||||
}
|
||||
|
||||
@IBAction func duplicate(_ sender: Any) {
|
||||
let profile = profileMgr.profiles[profilesTableView.clickedRow]
|
||||
let duplicateProfile = profile.copy() as! ServerProfile
|
||||
duplicateProfile.uuid = UUID().uuidString
|
||||
profileMgr.profiles.insert(duplicateProfile, at: profilesTableView.clickedRow+1)
|
||||
profilesTableView.beginUpdates()
|
||||
let index = IndexSet(integer: profileMgr.profiles.count-1)
|
||||
profilesTableView.insertRows(at: index, withAnimation: .effectFade)
|
||||
self.profilesTableView.scrollRowToVisible(profilesTableView.clickedRow+1)
|
||||
self.profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
|
||||
profilesTableView.endUpdates()
|
||||
updateProfileBoxVisible()
|
||||
}
|
||||
|
||||
@IBAction func copyCurrentProfileURL2Pasteboard(_ sender: NSButton) {
|
||||
let index = profilesTableView.selectedRow
|
||||
if index >= 0 {
|
||||
|
||||
@@ -9,26 +9,87 @@
|
||||
import Cocoa
|
||||
|
||||
|
||||
|
||||
class ServerProfile: NSObject {
|
||||
var uuid: String
|
||||
class ServerProfile: NSObject, NSCopying {
|
||||
|
||||
var uuid: String
|
||||
|
||||
var serverHost: String = ""
|
||||
var serverPort: uint16 = 8379
|
||||
var method:String = "aes-128-cfb"
|
||||
var password:String = ""
|
||||
var remark:String = ""
|
||||
var ota: Bool = false // onetime authentication
|
||||
|
||||
|
||||
override init() {
|
||||
uuid = UUID().uuidString
|
||||
}
|
||||
|
||||
|
||||
init(uuid: String) {
|
||||
self.uuid = uuid
|
||||
}
|
||||
|
||||
convenience init?(url: URL?) {
|
||||
self.init()
|
||||
|
||||
func padBase64(string: String) -> String {
|
||||
var length = string.characters.count
|
||||
if length % 4 == 0 {
|
||||
return string
|
||||
} else {
|
||||
length = 4 - length % 4 + length
|
||||
return string.padding(toLength: length, withPad: "=", startingAt: 0)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUrl(url: URL?) -> String? {
|
||||
guard let encodedStr = url?.host else {
|
||||
return nil
|
||||
}
|
||||
guard let data = Data(base64Encoded: padBase64(string: encodedStr)) else {
|
||||
return url?.absoluteString
|
||||
}
|
||||
guard let decoded = String(data: data, encoding: String.Encoding.utf8) else {
|
||||
return nil
|
||||
}
|
||||
return "ss://\(decoded)"
|
||||
}
|
||||
|
||||
guard let decodedUrl = decodeUrl(url: url) else {
|
||||
return nil
|
||||
}
|
||||
guard var parsedUrl = URLComponents(string: decodedUrl) else {
|
||||
return nil
|
||||
}
|
||||
guard let host = parsedUrl.host, let port = parsedUrl.port,
|
||||
let method = parsedUrl.user, let password = parsedUrl.password else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.serverHost = host
|
||||
self.serverPort = UInt16(port)
|
||||
self.method = method
|
||||
self.password = password
|
||||
|
||||
remark = parsedUrl.queryItems?
|
||||
.filter({ $0.name == "Remark" }).first?.value ?? ""
|
||||
if let otaStr = parsedUrl.queryItems?
|
||||
.filter({ $0.name == "OTA" }).first?.value {
|
||||
ota = NSString(string: otaStr).boolValue
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDictionary(_ data:[String:AnyObject]) -> ServerProfile {
|
||||
public func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = ServerProfile()
|
||||
copy.serverHost = self.serverHost
|
||||
copy.serverPort = self.serverPort
|
||||
copy.method = self.method
|
||||
copy.password = self.password
|
||||
copy.remark = self.remark
|
||||
copy.ota = self.ota
|
||||
return copy;
|
||||
}
|
||||
|
||||
static func fromDictionary(_ data:[String:Any?]) -> ServerProfile {
|
||||
let cp = {
|
||||
(profile: ServerProfile) in
|
||||
profile.serverHost = data["ServerHost"] as! String
|
||||
@@ -42,7 +103,7 @@ class ServerProfile: NSObject {
|
||||
profile.ota = ota as! Bool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let id = data["Id"] as? String {
|
||||
let profile = ServerProfile(uuid: id)
|
||||
cp(profile)
|
||||
@@ -53,7 +114,7 @@ class ServerProfile: NSObject {
|
||||
return profile
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func toDictionary() -> [String:AnyObject] {
|
||||
var d = [String:AnyObject]()
|
||||
d["Id"] = uuid as AnyObject?
|
||||
@@ -65,28 +126,28 @@ class ServerProfile: NSObject {
|
||||
d["OTA"] = ota as AnyObject?
|
||||
return d
|
||||
}
|
||||
|
||||
|
||||
func toJsonConfig() -> [String: AnyObject] {
|
||||
var conf: [String: AnyObject] = ["server": serverHost as AnyObject,
|
||||
"server_port": NSNumber(value: serverPort as UInt16),
|
||||
"password": password as AnyObject,
|
||||
"method": method as AnyObject,]
|
||||
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
conf["local_port"] = NSNumber(value: UInt16(defaults.integer(forKey: "LocalSocks5.ListenPort")) as UInt16)
|
||||
conf["local_address"] = defaults.string(forKey: "LocalSocks5.ListenAddress") as AnyObject?
|
||||
conf["timeout"] = NSNumber(value: UInt32(defaults.integer(forKey: "LocalSocks5.Timeout")) as UInt32)
|
||||
conf["auth"] = NSNumber(value: ota as Bool)
|
||||
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
|
||||
func isValid() -> Bool {
|
||||
func validateIpAddress(_ ipToValidate: String) -> Bool {
|
||||
|
||||
|
||||
var sin = sockaddr_in()
|
||||
var sin6 = sockaddr_in6()
|
||||
|
||||
|
||||
if ipToValidate.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1 {
|
||||
// IPv6 peer.
|
||||
return true
|
||||
@@ -95,35 +156,48 @@ class ServerProfile: NSObject {
|
||||
// IPv4 peer.
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
func validateDomainName(_ value: String) -> Bool {
|
||||
let validHostnameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
|
||||
|
||||
|
||||
if (value.range(of: validHostnameRegex, options: .regularExpression) != nil) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !(validateIpAddress(serverHost) || validateDomainName(serverHost)){
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if password.isEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func URL() -> Foundation.URL? {
|
||||
let parts = "\(method):\(password)@\(serverHost):\(serverPort)"
|
||||
let base64String = parts.data(using: String.Encoding.utf8)?
|
||||
.base64EncodedString(options: NSData.Base64EncodingOptions())
|
||||
var url = URLComponents()
|
||||
|
||||
url.host = serverHost
|
||||
url.user = method
|
||||
url.password = password
|
||||
url.port = Int(serverPort)
|
||||
|
||||
url.queryItems = [URLQueryItem(name: "Remark", value: remark),
|
||||
URLQueryItem(name: "OTA", value: ota.description)]
|
||||
|
||||
let parts = url.string?.replacingOccurrences(
|
||||
of: "//", with: "",
|
||||
options: String.CompareOptions.anchored, range: nil)
|
||||
|
||||
let base64String = parts?.data(using: String.Encoding.utf8)?
|
||||
.base64EncodedString(options: Data.Base64EncodingOptions())
|
||||
if var s = base64String {
|
||||
s = s.trimmingCharacters(in: CharacterSet(charactersIn: "="))
|
||||
return Foundation.URL(string: "ss://\(s)")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// ServerProfileManager.swift
|
||||
// ShadowsocksX-NG
|
||||
//
|
||||
// Created by 邱宇舟 on 16/6/6.
|
||||
// Created by 邱宇舟 on 16/6/6. Modified by 秦宇航 16/9/12
|
||||
// Copyright © 2016年 qiuyuzhou. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -21,7 +21,7 @@ class ServerProfileManager: NSObject {
|
||||
let defaults = UserDefaults.standard
|
||||
if let _profiles = defaults.array(forKey: "ServerProfiles") {
|
||||
for _profile in _profiles {
|
||||
let profile = ServerProfile.fromDictionary(_profile as! [String : AnyObject])
|
||||
let profile = ServerProfile.fromDictionary(_profile as! [String: Any])
|
||||
profiles.append(profile)
|
||||
}
|
||||
}
|
||||
@@ -70,4 +70,114 @@ class ServerProfileManager: NSObject {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func importConfigFile() {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.title = "Choose Config Json File".localized
|
||||
openPanel.allowsMultipleSelection = false
|
||||
openPanel.canChooseDirectories = false
|
||||
openPanel.canCreateDirectories = false
|
||||
openPanel.canChooseFiles = true
|
||||
openPanel.becomeKey()
|
||||
openPanel.begin { (result) -> Void in
|
||||
if (result == NSFileHandlingPanelOKButton && (openPanel.url) != nil) {
|
||||
let fileManager = FileManager.default
|
||||
let filePath:String = (openPanel.url?.path)!
|
||||
if (fileManager.fileExists(atPath: filePath) && filePath.hasSuffix("json")) {
|
||||
let data = fileManager.contents(atPath: filePath)
|
||||
let readString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)!
|
||||
let readStringData = readString.data(using: String.Encoding.utf8.rawValue)
|
||||
|
||||
let jsonArr1 = try! JSONSerialization.jsonObject(with: readStringData!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
|
||||
|
||||
for item in jsonArr1.object(forKey: "configs") as! [[String: AnyObject]]{
|
||||
let profile = ServerProfile()
|
||||
profile.serverHost = item["server"] as! String
|
||||
profile.serverPort = UInt16((item["server_port"]?.integerValue)!)
|
||||
profile.method = item["method"] as! String
|
||||
profile.password = item["password"] as! String
|
||||
profile.remark = item["remarks"] as! String
|
||||
self.profiles.append(profile)
|
||||
self.save()
|
||||
}
|
||||
NotificationCenter.default.post(name: NSNotification.Name(rawValue: NOTIFY_SERVER_PROFILES_CHANGED), object: nil)
|
||||
let configsCount = (jsonArr1.object(forKey: "configs") as! [[String: AnyObject]]).count
|
||||
let notification = NSUserNotification()
|
||||
notification.title = "Import Server Profile succeed!".localized
|
||||
notification.informativeText = "Successful import \(configsCount) items".localized
|
||||
NSUserNotificationCenter.default
|
||||
.deliver(notification)
|
||||
}else{
|
||||
let notification = NSUserNotification()
|
||||
notification.title = "Import Server Profile failed!".localized
|
||||
notification.informativeText = "Invalid config file!".localized
|
||||
NSUserNotificationCenter.default
|
||||
.deliver(notification)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func exportConfigFile() {
|
||||
//读取example文件,删掉configs里面的配置,再用NSDictionary填充到configs里面
|
||||
let fileManager = FileManager.default
|
||||
|
||||
let filePath:String = Bundle.main.path(forResource: "example-gui-config", ofType: "json")!
|
||||
let data = fileManager.contents(atPath: filePath)
|
||||
let readString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)!
|
||||
let readStringData = readString.data(using: String.Encoding.utf8.rawValue)
|
||||
let jsonArr1 = try! JSONSerialization.jsonObject(with: readStringData!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
|
||||
|
||||
let configsArray:NSMutableArray = [] //not using var?
|
||||
|
||||
for profile in profiles{
|
||||
let configProfile:NSMutableDictionary = [:] //not using var?
|
||||
//standard ss profile
|
||||
configProfile.setValue(true, forKey: "enable")
|
||||
configProfile.setValue(profile.serverHost, forKey: "server")
|
||||
configProfile.setValue(NSNumber(value:profile.serverPort), forKey: "server_port")//not work
|
||||
configProfile.setValue(profile.password, forKey: "password")
|
||||
configProfile.setValue(profile.method, forKey: "method")
|
||||
configProfile.setValue(profile.remark, forKey: "remarks")
|
||||
configProfile.setValue(profile.remark.data(using: String.Encoding.utf8)?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)), forKey: "remarks_base64")
|
||||
configsArray.add(configProfile)
|
||||
}
|
||||
jsonArr1.setValue(configsArray, forKey: "configs")
|
||||
let jsonData = try! JSONSerialization.data(withJSONObject: jsonArr1, options: JSONSerialization.WritingOptions.prettyPrinted)
|
||||
let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.title = "Export Config Json File".localized
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.allowedFileTypes = ["json"]
|
||||
savePanel.nameFieldStringValue = "export.json"
|
||||
savePanel.becomeKey()
|
||||
savePanel.begin { (result) -> Void in
|
||||
if (result == NSFileHandlingPanelOKButton && (savePanel.url) != nil) {
|
||||
//write jsonArr1 back to file
|
||||
try! jsonString.write(toFile: (savePanel.url?.path)!, atomically: true, encoding: String.Encoding.utf8)
|
||||
NSWorkspace.shared().selectFile((savePanel.url?.path)!, inFileViewerRootedAtPath: (savePanel.directoryURL?.path)!)
|
||||
let notification = NSUserNotification()
|
||||
notification.title = "Export Server Profile succeed!".localized
|
||||
notification.informativeText = "Successful Export \(self.profiles.count) items".localized
|
||||
NSUserNotificationCenter.default
|
||||
.deliver(notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class func showExampleConfigFile() {
|
||||
//copy file to ~/Downloads folder
|
||||
let filePath:String = Bundle.main.path(forResource: "example-gui-config", ofType: "json")!
|
||||
let fileMgr = FileManager.default
|
||||
let dataPath = NSHomeDirectory() + "/Downloads"
|
||||
let destPath = dataPath + "/example-gui-config.json"
|
||||
//检测文件是否已经存在,如果存在直接用sharedWorkspace显示
|
||||
if fileMgr.fileExists(atPath: destPath) {
|
||||
NSWorkspace.shared().selectFile(destPath, inFileViewerRootedAtPath: dataPath)
|
||||
}else{
|
||||
try! fileMgr.copyItem(atPath: filePath, toPath: destPath)
|
||||
NSWorkspace.shared().selectFile(destPath, inFileViewerRootedAtPath: dataPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,4 @@
|
||||
|
||||
void ScanQRCodeOnScreen();
|
||||
|
||||
NSDictionary<NSString *, id>* ParseSSURL(NSURL* url);
|
||||
|
||||
#endif /* QRCodeUtils_h */
|
||||
|
||||
@@ -71,63 +71,3 @@ void ScanQRCodeOnScreen() {
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// 解析SS URL,如果成功则返回一个与ServerProfile类兼容的dict
|
||||
NSDictionary<NSString *, id>* ParseSSURL(NSURL* url) {
|
||||
if (!url.host) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *urlString = [url absoluteString];
|
||||
int i = 0;
|
||||
NSString *errorReason = nil;
|
||||
while(i < 2) {
|
||||
if (i == 1) {
|
||||
NSString* host = url.host;
|
||||
if ([host length]%4!=0) {
|
||||
int n = 4 - [host length]%4;
|
||||
if (1==n) {
|
||||
host = [host stringByAppendingString:@"="];
|
||||
} else if (2==n) {
|
||||
host = [host stringByAppendingString:@"=="];
|
||||
}
|
||||
}
|
||||
NSData *data = [[NSData alloc] initWithBase64EncodedString:host options:0];
|
||||
NSString *decodedString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
urlString = decodedString;
|
||||
}
|
||||
i++;
|
||||
urlString = [urlString stringByReplacingOccurrencesOfString:@"ss://" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, urlString.length)];
|
||||
NSRange firstColonRange = [urlString rangeOfString:@":"];
|
||||
NSRange lastColonRange = [urlString rangeOfString:@":" options:NSBackwardsSearch];
|
||||
NSRange lastAtRange = [urlString rangeOfString:@"@" options:NSBackwardsSearch];
|
||||
if (firstColonRange.length == 0) {
|
||||
errorReason = @"colon not found";
|
||||
continue;
|
||||
}
|
||||
if (firstColonRange.location == lastColonRange.location) {
|
||||
errorReason = @"only one colon";
|
||||
continue;
|
||||
}
|
||||
if (lastAtRange.length == 0) {
|
||||
errorReason = @"at not found";
|
||||
continue;
|
||||
}
|
||||
if (!((firstColonRange.location < lastAtRange.location) && (lastAtRange.location < lastColonRange.location))) {
|
||||
errorReason = @"wrong position";
|
||||
continue;
|
||||
}
|
||||
NSString *method = [urlString substringWithRange:NSMakeRange(0, firstColonRange.location)];
|
||||
NSString *password = [urlString substringWithRange:NSMakeRange(firstColonRange.location + 1, lastAtRange.location - firstColonRange.location - 1)];
|
||||
NSString *IP = [urlString substringWithRange:NSMakeRange(lastAtRange.location + 1, lastColonRange.location - lastAtRange.location - 1)];
|
||||
NSString *port = [urlString substringWithRange:NSMakeRange(lastColonRange.location + 1, urlString.length - lastColonRange.location - 1)];
|
||||
|
||||
|
||||
return @{@"ServerHost": IP,
|
||||
@"ServerPort": @([port integerValue]),
|
||||
@"Method": method,
|
||||
@"Password": password,
|
||||
};
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
/* Class = "NSButtonCell"; title = "Enable OTA"; ObjectID = "hRZ-Rp-I5e"; */
|
||||
"hRZ-Rp-I5e.title" = "Enable OTA";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Cacnel"; ObjectID = "j03-9E-uHW"; */
|
||||
"j03-9E-uHW.title" = "Cacnel";
|
||||
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "j03-9E-uHW"; */
|
||||
"j03-9E-uHW.title" = "Cancel";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Password:"; ObjectID = "kil-xC-HPD"; */
|
||||
"kil-xC-HPD.title" = "Password:";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"index": 0,
|
||||
"random": false,
|
||||
"global": false,
|
||||
"enabled": true,
|
||||
"shareOverLan": false,
|
||||
"isDefault": false,
|
||||
"localPort": 1080,
|
||||
"pacUrl": null,
|
||||
"useOnlinePac": false,
|
||||
"reconnectTimes": 3,
|
||||
"randomAlgorithm": 0,
|
||||
"TTL": 0,
|
||||
"proxyEnable": false,
|
||||
"proxyType": 0,
|
||||
"proxyHost": null,
|
||||
"proxyPort": 0,
|
||||
"proxyAuthUser": null,
|
||||
"proxyAuthPass": null,
|
||||
"authUser": null,
|
||||
"authPass": null,
|
||||
"autoban": false,
|
||||
"configs": [{
|
||||
"remarks": "example",
|
||||
"server": "abc.xyz",
|
||||
"server_port": 1234,
|
||||
"method": "rc4-md5",
|
||||
"remarks_base64": "ZXhhbXBsZQ==",
|
||||
"password": "passwd",
|
||||
"tcp_over_udp": false,
|
||||
"udp_over_tcp": false,
|
||||
"enable": true
|
||||
},{
|
||||
"remarks": "example2",
|
||||
"server": "xyz.xyz",
|
||||
"server_port": 1234,
|
||||
"method": "rc4-md5",
|
||||
"remarks_base64": "ZXhhbXBsZTI=",
|
||||
"password": "passwd",
|
||||
"tcp_over_udp": false,
|
||||
"udp_over_tcp": false,
|
||||
"enable": true
|
||||
}]
|
||||
}
|
||||
@@ -8,11 +8,14 @@
|
||||
|
||||
|
||||
cd `dirname "${BASH_SOURCE[0]}"`
|
||||
mkdir -p "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-2.4.6"
|
||||
cp -f ss-local "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-2.4.6/"
|
||||
mkdir -p "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-2.5.6"
|
||||
cp -f ss-local "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-2.5.6/"
|
||||
rm -f "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local"
|
||||
ln -s "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-2.4.6/ss-local" "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local"
|
||||
ln -s "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-2.5.6/ss-local" "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local"
|
||||
|
||||
cp -f libcrypto.1.0.0.dylib "$HOME/Library/Application Support/ShadowsocksX-NG/"
|
||||
cp -f libpcre.1.dylib "$HOME/Library/Application Support/ShadowsocksX-NG/"
|
||||
rm -f "$HOME/Library/Application Support/ShadowsocksX-NG/libpcre.dylib"
|
||||
ln -s "$HOME/Library/Application Support/ShadowsocksX-NG/libpcre.1.dylib" "$HOME/Library/Application Support/ShadowsocksX-NG/libpcre.dylib"
|
||||
|
||||
echo done
|
||||
echo done
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -80,3 +80,11 @@
|
||||
/* Class = "NSMenuItem"; title = "HTTP Proxy Preference ..."; ObjectID = "uEp-Gz-cu0"; */
|
||||
"uEp-Gz-cu0.title" = "HTTP代理设置...";
|
||||
|
||||
/* Class = "NSMenuItem"; title = "Show Bunch Json Example File..."; ObjectID = "pdy-JE-50Q"; */
|
||||
"pdy-JE-50Q.title" = "显示示例服务器配置文件...";
|
||||
|
||||
/* Class = "NSMenuItem"; title = "Import Bunch Json File..."; ObjectID = "T9g-gy-gvv"; */
|
||||
"T9g-gy-gvv.title" = "导入服务器配置文件...";
|
||||
|
||||
/* Class = "NSMenuItem"; title = "Export All Server To Json..."; ObjectID = "6k0-gn-DQv"; */
|
||||
"6k0-gn-DQv.title" = "导出全部服务器配置...";
|
||||
|
||||
@@ -34,3 +34,6 @@
|
||||
|
||||
/* Class = "NSButtonCell"; title = "OK"; ObjectID = "zPE-oD-PwK"; */
|
||||
"zPE-oD-PwK.title" = "确定";
|
||||
|
||||
/* Class = "NSMenuItem"; title = "Duplicate"; ObjectID = "bl9-lq-u9V"; */
|
||||
"bl9-lq-u9V.title" = "复制";
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// ServerProfileTests.swift
|
||||
// ShadowsocksX-NG
|
||||
//
|
||||
// Created by Rainux Luo on 07/01/2017.
|
||||
// Copyright © 2017 qiuyuzhou. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import ShadowsocksX_NG
|
||||
|
||||
class ServerProfileTests: XCTestCase {
|
||||
|
||||
var profile: ServerProfile!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
profile = ServerProfile.fromDictionary(["ServerHost": "example.com",
|
||||
"ServerPort": 8388,
|
||||
"Method": "aes-256-cfb",
|
||||
"Password": "password",
|
||||
"Remark": "Protoss Prism",
|
||||
"OTA": true])
|
||||
XCTAssertNotNil(profile)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testInitWithSelfGeneratedURL() {
|
||||
let newProfile = ServerProfile.init(url: profile.URL())
|
||||
|
||||
XCTAssertEqual(newProfile?.serverHost, profile.serverHost)
|
||||
XCTAssertEqual(newProfile?.serverPort, profile.serverPort)
|
||||
XCTAssertEqual(newProfile?.method, profile.method)
|
||||
XCTAssertEqual(newProfile?.password, profile.password)
|
||||
XCTAssertEqual(newProfile?.remark, profile.remark)
|
||||
XCTAssertEqual(newProfile?.ota, profile.ota)
|
||||
}
|
||||
|
||||
func testInitWithPlainURL() {
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
|
||||
XCTAssertEqual(profile?.serverHost, "example.com")
|
||||
XCTAssertEqual(profile?.serverPort, 8388)
|
||||
XCTAssertEqual(profile?.method, "aes-256-cfb")
|
||||
XCTAssertEqual(profile?.password, "password")
|
||||
XCTAssertEqual(profile?.remark, "")
|
||||
XCTAssertEqual(profile?.ota, false)
|
||||
}
|
||||
|
||||
func testInitWithPlainURLandQuery() {
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
|
||||
XCTAssertEqual(profile?.serverHost, "example.com")
|
||||
XCTAssertEqual(profile?.serverPort, 8388)
|
||||
XCTAssertEqual(profile?.method, "aes-256-cfb")
|
||||
XCTAssertEqual(profile?.password, "password")
|
||||
XCTAssertEqual(profile?.remark, "Prism")
|
||||
XCTAssertEqual(profile?.ota, true)
|
||||
}
|
||||
|
||||
func testInitWithPlainURLandAnotherQuery() {
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=0")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
|
||||
XCTAssertEqual(profile?.serverHost, "example.com")
|
||||
XCTAssertEqual(profile?.serverPort, 8388)
|
||||
XCTAssertEqual(profile?.method, "aes-256-cfb")
|
||||
XCTAssertEqual(profile?.password, "password")
|
||||
XCTAssertEqual(profile?.remark, "Prism")
|
||||
XCTAssertEqual(profile?.ota, false)
|
||||
}
|
||||
|
||||
func testInitWithBase64EncodedURL() {
|
||||
// "ss://aes-256-cfb:password@example.com:8388"
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OA")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
|
||||
XCTAssertEqual(profile?.serverHost, "example.com")
|
||||
XCTAssertEqual(profile?.serverPort, 8388)
|
||||
XCTAssertEqual(profile?.method, "aes-256-cfb")
|
||||
XCTAssertEqual(profile?.password, "password")
|
||||
XCTAssertEqual(profile?.remark, "")
|
||||
XCTAssertEqual(profile?.ota, false)
|
||||
}
|
||||
|
||||
func testInitWithBase64EncodedURLandQuery() {
|
||||
// "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true"
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OD9SZW1hcms9UHJpc20mT1RBPXRydWU")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
|
||||
XCTAssertEqual(profile?.serverHost, "example.com")
|
||||
XCTAssertEqual(profile?.serverPort, 8388)
|
||||
XCTAssertEqual(profile?.method, "aes-256-cfb")
|
||||
XCTAssertEqual(profile?.password, "password")
|
||||
XCTAssertEqual(profile?.remark, "Prism")
|
||||
XCTAssertEqual(profile?.ota, true)
|
||||
}
|
||||
|
||||
func testInitWithEmptyURL() {
|
||||
let url = URL(string: "ss://")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNil(profile)
|
||||
}
|
||||
|
||||
func testInitWithInvalidURL() {
|
||||
let url = URL(string: "ss://invalid url")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNil(profile)
|
||||
}
|
||||
|
||||
func testInitWithBase64EncodedInvalidURL() {
|
||||
// "ss://invalid url"
|
||||
let url = URL(string: "ss://aW52YWxpZCB1cmw")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNil(profile)
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,5 +32,5 @@ class ShadowsocksX_NGTests: XCTestCase {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user