Merge branch 'release/v1.6.0'
This commit is contained in:
52
README.md
52
README.md
@ -1,6 +1,6 @@
|
||||
# ShadowsocksX-NG
|
||||
|
||||
Current version is 1.5
|
||||
Current version is 1.5.2
|
||||
|
||||
[](https://travis-ci.org/shadowsocks/ShadowsocksX-NG)
|
||||
|
||||
@ -9,23 +9,23 @@ Next Generation of [ShadowsocksX](https://github.com/shadowsocks/shadowsocks-iOS
|
||||
## Why?
|
||||
|
||||
It's hard to maintain the original implementation as there is too much unused code in it.
|
||||
It also embeds the ss-local source. It's crazy to maintain dependencies of ss-local.
|
||||
So it's hard to update the ss-local version.
|
||||
It also embeds the `ss-local` source. It's crazy to maintain dependencies of `ss-local`.
|
||||
So it's hard to update the `ss-local` version.
|
||||
|
||||
Now I just copied the ss-local from homebrew. Run ss-local executable as a Launch Agent in the background.
|
||||
Serve PAC js file as a file URL. So there is only some source code related to GUI left.
|
||||
Now I just copied the `ss-local` from Homebrew. Run `ss-local` executable as a Launch Agent in the background.
|
||||
Serve PAC JS file as a file URL. So there is only some source code related to GUI left.
|
||||
Then I will rewrite the GUI code in Swift.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Running
|
||||
|
||||
- Mac OS X 10.11 +
|
||||
- macOS 10.11+
|
||||
|
||||
### Building
|
||||
|
||||
- XCode 8.3+
|
||||
- cocoapod 1.2+
|
||||
- Xcode 8.3+
|
||||
- CocoaPods 1.2+
|
||||
|
||||
## Download
|
||||
|
||||
@ -33,39 +33,39 @@ From [here](https://github.com/shadowsocks/ShadowsocksX-NG/releases/)
|
||||
|
||||
## Features
|
||||
|
||||
- Use ss-local from shadowsocks-libev 3.0.5
|
||||
- Could Update PAC by download GFW List from GitHub.
|
||||
- Show QRCode for current server profile.
|
||||
- Scan QRCode from screen.
|
||||
- Uses `ss-local` from shadowsocks-libev 3.0.5
|
||||
- Could update PAC by download GFW List from GitHub.
|
||||
- Shows QRCode for current server profile.
|
||||
- Scans QRCode from screen.
|
||||
- Auto launch at login.
|
||||
- User rules for PAC.
|
||||
- Support OTA
|
||||
- Support for [AEAD Ciphers](https://shadowsocks.org/en/spec/AEAD-Ciphers.html)
|
||||
- HTTP Proxy by [privoxy](http://www.privoxy.org/)
|
||||
- Over [kcptun](https://github.com/xtaci/kcptun). Version 20170322
|
||||
- Export/Import configure file.
|
||||
- An advanced preferences panel to configure:
|
||||
- Local socks5 listen address.
|
||||
- Local socks5 listen port.
|
||||
- Local socks5 timeout.
|
||||
- An advanced preferences panel for configuring:
|
||||
- Local SOCKS5 listen address.
|
||||
- Local SOCKS5 listen port.
|
||||
- Local SOCKS5 timeout.
|
||||
- If enable UDP relay.
|
||||
- GFW List URL.
|
||||
- Manual specify network service profiles which would be configure the proxy.
|
||||
- Could reorder shadowsocks profiles by drag & drop in servers preferences panel.
|
||||
- Manually specify network service profiles which would be used to configure the proxy.
|
||||
- Could reorder shadowsocks profiles by drag-&-dropping in servers preferences panel.
|
||||
- Configurable global shortcuts for toggle running and switch proxy mode.
|
||||
|
||||
## Different from orignal ShadowsocksX
|
||||
## Difference from original ShadowsocksX
|
||||
|
||||
Run ss-local as a background service through launchd, not as an in-app process.
|
||||
So after you quit the app, the ss-local maybe be still running.
|
||||
`ss-local` is run as a background service through launchd, not as an in-app process.
|
||||
So after you quit the app, the `ss-local` might be still running.
|
||||
|
||||
Added a manual mode which won't configure the system proxy settings.
|
||||
Then you could configure your apps to use socks5 proxy manual.
|
||||
Added a manual mode which won't configure the system proxy settings,
|
||||
so that you could configure your apps to use the SOCKS5 proxy manually.
|
||||
|
||||
## Contributing
|
||||
## Contributing
|
||||
|
||||
[](https://gitter.im/ShadowsocksX-NG/Lobby)
|
||||
|
||||
Contributions must be available on a separately named branch based on the latest version of the main branch develop.
|
||||
Contributions must be available on a separately named branch based on the latest version of the main branch `develop`.
|
||||
|
||||
ref: [GitFlow](http://nvie.com/posts/a-successful-git-branching-model/)
|
||||
|
||||
|
@ -64,6 +64,7 @@
|
||||
9BA04B231D23D5A5005AAD7F /* ProxyConfTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA04B221D23D5A5005AAD7F /* ProxyConfTool.m */; };
|
||||
9BAFE2E21E83ED7F00F71CCE /* PreferencesWinController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9BAFE2E41E83ED7F00F71CCE /* PreferencesWinController.xib */; };
|
||||
9BB706A71D1B982300551F0E /* SWBApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BB706A51D1B982300551F0E /* SWBApplication.m */; };
|
||||
9BBE7B751F508A0E00E8FFE5 /* fix_dir_owner.sh in Resources */ = {isa = PBXBuildFile; fileRef = 9BBE7B711F50790500E8FFE5 /* fix_dir_owner.sh */; };
|
||||
9BC70EDC1D2E3E3100EDA4CA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9B172A6C1D0ADDDD00B87B9A /* Localizable.strings */; };
|
||||
9BEEF0691D04D4D500FC52B3 /* install_ss_local.sh in Resources */ = {isa = PBXBuildFile; fileRef = 9BEEF0651D04CB8500FC52B3 /* install_ss_local.sh */; };
|
||||
9BEEF06A1D04D4D500FC52B3 /* start_ss_local.sh in Resources */ = {isa = PBXBuildFile; fileRef = 9BEEF0661D04CE8D00FC52B3 /* start_ss_local.sh */; };
|
||||
@ -216,6 +217,7 @@
|
||||
9BAFE2EB1E83F91D00F71CCE /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
||||
9BB706A51D1B982300551F0E /* SWBApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWBApplication.m; sourceTree = "<group>"; };
|
||||
9BB706A61D1B982300551F0E /* SWBApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWBApplication.h; sourceTree = "<group>"; };
|
||||
9BBE7B711F50790500E8FFE5 /* fix_dir_owner.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = fix_dir_owner.sh; sourceTree = "<group>"; };
|
||||
9BE8FBC11D0B71CF00CAFD01 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/PreferencesWindowController.strings"; sourceTree = "<group>"; };
|
||||
9BEEF0651D04CB8500FC52B3 /* install_ss_local.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = install_ss_local.sh; sourceTree = "<group>"; };
|
||||
9BEEF0661D04CE8D00FC52B3 /* start_ss_local.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = start_ss_local.sh; sourceTree = "<group>"; };
|
||||
@ -324,6 +326,7 @@
|
||||
C6D429981DA76FBC002A5711 /* privoxy.config.example */,
|
||||
9B9CBCB01E2644DC00FC61AA /* start_kcptun.sh */,
|
||||
9B9CBCB11E26450D00FC61AA /* stop_kcptun.sh */,
|
||||
9BBE7B711F50790500E8FFE5 /* fix_dir_owner.sh */,
|
||||
);
|
||||
name = "Support Files";
|
||||
sourceTree = "<group>";
|
||||
@ -577,6 +580,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9BBE7B751F508A0E00E8FFE5 /* fix_dir_owner.sh in Resources */,
|
||||
9B3F7BFF1E82BF5B00C68B75 /* libev.4.dylib in Resources */,
|
||||
9B3F7C001E82BF5B00C68B75 /* libmbedcrypto.2.4.2.dylib in Resources */,
|
||||
9B3F7C011E82BF5B00C68B75 /* libsodium.18.dylib in Resources */,
|
||||
|
@ -32,10 +32,8 @@ 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 var serverProfilesBeginSeparatorMenuItem: NSMenuItem!
|
||||
@IBOutlet var serverProfilesEndSeparatorMenuItem: NSMenuItem!
|
||||
|
||||
@IBOutlet weak var copyHttpProxyExportCmdLineMenuItem: NSMenuItem!
|
||||
|
||||
@ -48,7 +46,29 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
let kProfileMenuItemIndexBase = 100
|
||||
|
||||
var statusItem: NSStatusItem!
|
||||
static let StatusItemIconWidth:CGFloat = 20
|
||||
static let StatusItemIconWidth: CGFloat = NSVariableStatusItemLength
|
||||
|
||||
func ensureLaunchAgentsDirOwner () {
|
||||
let dirPath = NSHomeDirectory() + "/Library/LaunchAgents"
|
||||
let fileMgr = FileManager.default
|
||||
if fileMgr.fileExists(atPath: dirPath) {
|
||||
do {
|
||||
let attrs = try fileMgr.attributesOfItem(atPath: dirPath)
|
||||
if attrs[FileAttributeKey.ownerAccountName] as! String != NSUserName() {
|
||||
//try fileMgr.setAttributes([FileAttributeKey.ownerAccountName: NSUserName()], ofItemAtPath: dirPath)
|
||||
let bashFilePath = Bundle.main.path(forResource: "fix_dir_owner.sh", ofType: nil)!
|
||||
let script = "do shell script \"bash \(bashFilePath) \(NSUserName()) \" with administrator privileges"
|
||||
if let appleScript = NSAppleScript(source: script) {
|
||||
var err: NSDictionary? = nil
|
||||
appleScript.executeAndReturnError(&err)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
NSLog("Error when ensure the owner of $HOME/Library/LaunchAgents, \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
|
||||
@ -56,6 +76,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
|
||||
NSUserNotificationCenter.default.delegate = self
|
||||
|
||||
self.ensureLaunchAgentsDirOwner()
|
||||
|
||||
// Prepare ss-local
|
||||
InstallSSLocal()
|
||||
InstallKcptunClient()
|
||||
@ -241,6 +263,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
qrcodeWinCtrl = SWBQRCodeWindowController(windowNibName: "SWBQRCodeWindowController")
|
||||
qrcodeWinCtrl.qrCode = profile.URL()!.absoluteString
|
||||
qrcodeWinCtrl.legacyQRCode = profile.URL(legacy: true)!.absoluteString
|
||||
qrcodeWinCtrl.title = profile.title()
|
||||
qrcodeWinCtrl.showWindow(self)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
@ -453,39 +476,33 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
|
||||
func updateServersMenu() {
|
||||
guard let menu = serversMenuItem.submenu else { return }
|
||||
|
||||
let mgr = ServerProfileManager.instance
|
||||
serversMenuItem.submenu?.removeAllItems()
|
||||
let preferencesItem = serversPreferencesMenuItem
|
||||
let showBunch = showBunchJsonExampleFileItem
|
||||
let importBuntch = importBunchJsonFileItem
|
||||
let exportAllServer = exportAllServerProfileItem
|
||||
|
||||
serversMenuItem.submenu?.addItem(preferencesItem!)
|
||||
serversMenuItem.submenu?.addItem(NSMenuItem.separator())
|
||||
|
||||
var i = 0
|
||||
for p in mgr.profiles {
|
||||
let profiles = mgr.profiles
|
||||
|
||||
// Remove all profile menu items
|
||||
let beginIndex = menu.index(of: serverProfilesBeginSeparatorMenuItem) + 1
|
||||
let endIndex = menu.index(of: serverProfilesEndSeparatorMenuItem)
|
||||
// Remove from end to begin, so the index won't change :)
|
||||
for index in (beginIndex..<endIndex).reversed() {
|
||||
menu.removeItem(at: index)
|
||||
}
|
||||
|
||||
// Insert all profile menu items
|
||||
for (i, profile) in profiles.enumerated().reversed() {
|
||||
let item = NSMenuItem()
|
||||
item.tag = i + kProfileMenuItemIndexBase
|
||||
item.title = p.title()
|
||||
if mgr.activeProfileId == p.uuid {
|
||||
item.state = 1
|
||||
}
|
||||
if !p.isValid() {
|
||||
item.isEnabled = false
|
||||
}
|
||||
item.title = profile.title()
|
||||
item.state = (mgr.activeProfileId == profile.uuid) ? 1 : 0
|
||||
item.isEnabled = profile.isValid()
|
||||
item.action = #selector(AppDelegate.selectServer)
|
||||
|
||||
serversMenuItem.submenu?.addItem(item)
|
||||
i += 1
|
||||
menu.insertItem(item, at: beginIndex)
|
||||
}
|
||||
if !mgr.profiles.isEmpty {
|
||||
serversMenuItem.submenu?.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
serversMenuItem.submenu?.addItem(showBunch!)
|
||||
serversMenuItem.submenu?.addItem(importBuntch!)
|
||||
serversMenuItem.submenu?.addItem(exportAllServer!)
|
||||
|
||||
// End separator is redundant if profile section is empty
|
||||
serverProfilesEndSeparatorMenuItem.isHidden = profiles.isEmpty
|
||||
}
|
||||
|
||||
func handleURLEvent(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@ -16,15 +16,13 @@
|
||||
<connections>
|
||||
<outlet property="autoModeMenuItem" destination="r07-Gu-aEz" id="9aH-pQ-Rgi"/>
|
||||
<outlet property="copyHttpProxyExportCmdLineMenuItem" destination="lg6-To-GZA" id="VTb-he-dg4"/>
|
||||
<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="manualModeMenuItem" destination="8PR-gs-c5N" id="9qz-mU-5kt"/>
|
||||
<outlet property="runningStatusMenuItem" destination="fzk-mE-CEV" id="Vwm-Rg-Ykn"/>
|
||||
<outlet property="scanQRCodeMenuItem" destination="Qe6-bF-paT" id="XHa-pa-nCa"/>
|
||||
<outlet property="serverProfilesBeginSeparatorMenuItem" destination="4iN-w2-but" id="Jyu-48-AzD"/>
|
||||
<outlet property="serverProfilesEndSeparatorMenuItem" destination="3cf-dF-7dx" id="eyc-6W-nWV"/>
|
||||
<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="statusMenu" destination="Hob-KD-bx9" id="clA-ZW-0pT"/>
|
||||
<outlet property="toggleRunningMenuItem" destination="GSu-Tf-StS" id="XHw-pU-nCa"/>
|
||||
@ -73,6 +71,7 @@
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4iN-w2-but"/>
|
||||
<menuItem isSeparatorItem="YES" id="3cf-dF-7dx"/>
|
||||
<menuItem title="Show Bunch Json Example File..." id="pdy-JE-50Q">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<string>1.6.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@ -44,7 +44,7 @@
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2016年 qiuyuzhou. All rights reserved. License GPLv3.</string>
|
||||
<string>Copyright © 2016-2017 qiuyuzhou. All rights reserved. License GPLv3.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
@ -60,7 +60,6 @@ func generateSSLocalLauchAgentPlist() -> Bool {
|
||||
let dict: NSMutableDictionary = [
|
||||
"Label": "com.qiuyuzhou.shadowsocksX-NG.local",
|
||||
"WorkingDirectory": NSHomeDirectory() + APP_SUPPORT_DIR,
|
||||
"KeepAlive": true,
|
||||
"StandardOutPath": logFilePath,
|
||||
"StandardErrorPath": logFilePath,
|
||||
"ProgramArguments": arguments,
|
||||
@ -203,7 +202,6 @@ func generatePrivoxyLauchAgentPlist() -> Bool {
|
||||
let dict: NSMutableDictionary = [
|
||||
"Label": "com.qiuyuzhou.shadowsocksX-NG.http",
|
||||
"WorkingDirectory": NSHomeDirectory() + APP_SUPPORT_DIR,
|
||||
"KeepAlive": true,
|
||||
"StandardOutPath": logFilePath,
|
||||
"StandardErrorPath": logFilePath,
|
||||
"ProgramArguments": arguments
|
||||
@ -358,7 +356,6 @@ func generateKcptunLauchAgentPlist() -> Bool {
|
||||
let dict: NSMutableDictionary = [
|
||||
"Label": "com.qiuyuzhou.shadowsocksX-NG.kcptun",
|
||||
"WorkingDirectory": NSHomeDirectory() + APP_SUPPORT_DIR,
|
||||
"KeepAlive": true,
|
||||
"StandardOutPath": logFilePath,
|
||||
"StandardErrorPath": logFilePath,
|
||||
"ProgramArguments": arguments,
|
||||
|
@ -107,6 +107,7 @@ class PreferencesWindowController: NSWindowController
|
||||
|
||||
override func awakeFromNib() {
|
||||
profilesTableView.register(forDraggedTypes: [tableViewDragType])
|
||||
profilesTableView.allowsMultipleSelection = true
|
||||
}
|
||||
|
||||
@IBAction func addProfile(_ sender: NSButton) {
|
||||
@ -129,13 +130,20 @@ class PreferencesWindowController: NSWindowController
|
||||
}
|
||||
|
||||
@IBAction func removeProfile(_ sender: NSButton) {
|
||||
let index = profilesTableView.selectedRow
|
||||
let index = Int(profilesTableView.selectedRowIndexes.first!)
|
||||
var deleteCount = 0
|
||||
if index >= 0 {
|
||||
profilesTableView.beginUpdates()
|
||||
profileMgr.profiles.remove(at: index)
|
||||
profilesTableView.removeRows(at: IndexSet(integer: index), withAnimation: .effectFade)
|
||||
for (_, toDeleteIndex) in profilesTableView.selectedRowIndexes.enumerated() {
|
||||
print(profileMgr.profiles.count)
|
||||
profileMgr.profiles.remove(at: toDeleteIndex - deleteCount)
|
||||
profilesTableView.removeRows(at: IndexSet(integer: toDeleteIndex - deleteCount), withAnimation: .effectFade)
|
||||
deleteCount += 1
|
||||
}
|
||||
profilesTableView.endUpdates()
|
||||
}
|
||||
self.profilesTableView.scrollRowToVisible(index-1)
|
||||
self.profilesTableView.selectRowIndexes(IndexSet(integer: index-1), byExtendingSelection: false)
|
||||
updateProfileBoxVisible()
|
||||
}
|
||||
|
||||
@ -160,16 +168,23 @@ class PreferencesWindowController: NSWindowController
|
||||
}
|
||||
|
||||
@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()
|
||||
var copyCount = 0
|
||||
for (_, toDuplicateIndex) in profilesTableView.selectedRowIndexes.enumerated() {
|
||||
print(profileMgr.profiles.count)
|
||||
let profile = profileMgr.profiles[toDuplicateIndex + copyCount]
|
||||
let duplicateProfile = profile.copy() as! ServerProfile
|
||||
duplicateProfile.uuid = UUID().uuidString
|
||||
profileMgr.profiles.insert(duplicateProfile, at:toDuplicateIndex + copyCount)
|
||||
|
||||
profilesTableView.beginUpdates()
|
||||
let index = IndexSet(integer: toDuplicateIndex + copyCount)
|
||||
profilesTableView.insertRows(at: index, withAnimation: .effectFade)
|
||||
self.profilesTableView.scrollRowToVisible(toDuplicateIndex + copyCount)
|
||||
self.profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
|
||||
profilesTableView.endUpdates()
|
||||
|
||||
copyCount += 1
|
||||
}
|
||||
updateProfileBoxVisible()
|
||||
}
|
||||
|
||||
|
@ -68,5 +68,8 @@ class ProxyInterfacesViewCtrl: NSViewController, NSTableViewDataSource, NSTableV
|
||||
} else {
|
||||
selectedNetworkServices.remove(key)
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(selectedNetworkServices.allObjects,
|
||||
forKey: "Proxy4NetworkServices")
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
@interface SWBQRCodeWindowController : NSWindowController
|
||||
|
||||
@property (nonatomic, copy) NSString *legacyQRCode;
|
||||
@property (nonatomic, copy) NSString *qrCode;
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
|
||||
|
@ -19,13 +19,32 @@
|
||||
[super windowDidLoad];
|
||||
|
||||
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
|
||||
[self setQRCode:self.qrCode];
|
||||
[self setQRCode:self.qrCode withOverlayText:@"Shadowsocks-NG SIP002"];
|
||||
}
|
||||
|
||||
- (void)setQRCode:(NSString*) qrCode {
|
||||
- (void)setQRCode:(NSString*) qrCode withOverlayText: (NSString*) text {
|
||||
CGImageRef cgImgRef = [self createQRImageForString:qrCode size:CGSizeMake(250, 250)];
|
||||
|
||||
NSImage *image = [[NSImage alloc]initWithCGImage:cgImgRef size:CGSizeMake(250, 250)];
|
||||
if (text) {
|
||||
// Draw overlay text
|
||||
NSDictionary* attrs = @{
|
||||
NSForegroundColorAttributeName: [NSColor colorWithRed:28/255.0 green:155/255.0 blue:71/255.0 alpha:1],
|
||||
NSBackgroundColorAttributeName: [NSColor whiteColor],
|
||||
NSFontAttributeName: [NSFont fontWithName:@"Helvetica" size:(CGFloat)16],
|
||||
};
|
||||
NSMutableAttributedString* attrsText = [[NSMutableAttributedString alloc] initWithString: text
|
||||
attributes: attrs];
|
||||
[attrsText setAttributes:@{
|
||||
NSForegroundColorAttributeName: [NSColor darkGrayColor],
|
||||
NSBackgroundColorAttributeName: [NSColor whiteColor],
|
||||
NSFontAttributeName: [NSFont fontWithName:@"Helvetica" size:(CGFloat)16],
|
||||
} range: NSMakeRange(0, 14)];
|
||||
|
||||
[image lockFocus];
|
||||
[attrsText drawAtPoint: NSMakePoint(45, 8)];
|
||||
[image unlockFocus];
|
||||
}
|
||||
self.imageView.image = image;
|
||||
}
|
||||
|
||||
@ -36,6 +55,14 @@
|
||||
|
||||
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[filter setValue:data forKey:@"inputMessage"];
|
||||
/*
|
||||
L: 7%
|
||||
M: 15%
|
||||
Q: 25%
|
||||
H: 30%
|
||||
*/
|
||||
[filter setValue:@"Q" forKey:@"inputCorrectionLevel"];
|
||||
|
||||
CIImage *image = [filter valueForKey:@"outputImage"];
|
||||
|
||||
// Calculate the size of the generated image and the scale for the desired image size
|
||||
@ -78,4 +105,13 @@
|
||||
[pasteboard writeObjects:copiedObjects];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
NSUInteger modifiers = event.modifierFlags & NSDeviceIndependentModifierFlagsMask;
|
||||
if (modifiers & NSAlternateKeyMask) {
|
||||
[self setQRCode:self.legacyQRCode withOverlayText:@"Shadowsocks-NG Legacy"];
|
||||
} else {
|
||||
[self setQRCode:self.qrCode withOverlayText:@"Shadowsocks-NG SIP002"];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<capability name="system font weights other than Regular or Bold" minToolsVersion="7.0"/>
|
||||
</dependencies>
|
||||
@ -17,14 +17,14 @@
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="QR Code" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" oneShot="NO" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<rect key="contentRect" x="566" y="456" width="290" height="359"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<rect key="contentRect" x="566" y="456" width="290" height="379"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="290" height="359"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="290" height="379"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yRy-QH-TEe">
|
||||
<rect key="frame" x="20" y="61" width="250" height="250"/>
|
||||
<rect key="frame" x="20" y="81" width="250" height="250"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="iAJ-tw-LNU"/>
|
||||
<connections>
|
||||
@ -32,7 +32,7 @@
|
||||
</connections>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9f5-sB-jlD">
|
||||
<rect key="frame" x="18" y="322" width="254" height="17"/>
|
||||
<rect key="frame" x="18" y="342" width="254" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Title For Server" id="fHU-xF-zyc">
|
||||
<font key="font" metaFont="systemSemibold" size="13"/>
|
||||
@ -44,7 +44,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fAd-u3-u8m">
|
||||
<rect key="frame" x="14" y="13" width="262" height="32"/>
|
||||
<rect key="frame" x="14" y="20" width="262" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Copy To Pasteboard" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="3q0-EO-Vug">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -54,9 +54,18 @@
|
||||
<action selector="copyQRCode:" target="-2" id="4fF-ID-p1E"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dqP-hN-9CY">
|
||||
<rect key="frame" x="34" y="56" width="222" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Press option/alt key to show legacy " id="x9U-Ti-73h">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="152" y="-70.5"/>
|
||||
<point key="canvasLocation" x="152" y="-60.5"/>
|
||||
</window>
|
||||
<menu id="5h4-gO-Rh7">
|
||||
<items>
|
||||
|
@ -31,7 +31,7 @@ class ServerProfile: NSObject, NSCopying {
|
||||
self.uuid = uuid
|
||||
}
|
||||
|
||||
convenience init?(url: URL?) {
|
||||
convenience init?(url: URL) {
|
||||
self.init()
|
||||
|
||||
func padBase64(string: String) -> String {
|
||||
@ -44,14 +44,12 @@ class ServerProfile: NSObject, NSCopying {
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUrl(url: URL?) -> String? {
|
||||
guard let urlStr = url?.absoluteString else {
|
||||
return nil
|
||||
}
|
||||
func decodeUrl(url: URL) -> String? {
|
||||
let urlStr = url.absoluteString
|
||||
let index = urlStr.index(urlStr.startIndex, offsetBy: 5)
|
||||
let encodedStr = urlStr.substring(from: index)
|
||||
guard let data = Data(base64Encoded: padBase64(string: encodedStr)) else {
|
||||
return url?.absoluteString
|
||||
return url.absoluteString
|
||||
}
|
||||
guard let decoded = String(data: data, encoding: String.Encoding.utf8) else {
|
||||
return nil
|
||||
@ -67,17 +65,40 @@ class ServerProfile: NSObject, NSCopying {
|
||||
return nil
|
||||
}
|
||||
guard let host = parsedUrl.host, let port = parsedUrl.port,
|
||||
let method = parsedUrl.user, let password = parsedUrl.password else {
|
||||
let user = parsedUrl.user else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.serverHost = host
|
||||
self.serverPort = UInt16(port)
|
||||
self.method = method.lowercased()
|
||||
self.password = password
|
||||
|
||||
// This can be overriden by the fragment part of SIP002 URL
|
||||
remark = parsedUrl.queryItems?
|
||||
.filter({ $0.name == "Remark" }).first?.value ?? ""
|
||||
|
||||
if let password = parsedUrl.password {
|
||||
self.method = user.lowercased()
|
||||
self.password = password
|
||||
} else {
|
||||
// SIP002 URL have no password section
|
||||
guard let data = Data(base64Encoded: padBase64(string: user)),
|
||||
let userInfo = String(data: data, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let parts = userInfo.characters.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
|
||||
if parts.count != 2 {
|
||||
return nil
|
||||
}
|
||||
self.method = String(parts[0]).lowercased()
|
||||
self.password = String(parts[1])
|
||||
|
||||
// SIP002 defines where to put the profile name
|
||||
if let profileName = parsedUrl.fragment {
|
||||
self.remark = profileName
|
||||
}
|
||||
}
|
||||
|
||||
if let otaStr = parsedUrl.queryItems?
|
||||
.filter({ $0.name == "OTA" }).first?.value {
|
||||
ota = NSString(string: otaStr).boolValue
|
||||
@ -225,7 +246,7 @@ class ServerProfile: NSObject, NSCopying {
|
||||
return true
|
||||
}
|
||||
|
||||
func URL() -> Foundation.URL? {
|
||||
private func makeLegacyURL() -> URL? {
|
||||
var url = URLComponents()
|
||||
|
||||
url.host = serverHost
|
||||
@ -254,6 +275,39 @@ class ServerProfile: NSObject, NSCopying {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func URL(legacy: Bool = false) -> URL? {
|
||||
// If you want the URL from <= 1.5.1
|
||||
if (legacy) {
|
||||
return self.makeLegacyURL()
|
||||
}
|
||||
|
||||
guard let rawUserInfo = "\(method):\(password)".data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
let paddings = CharacterSet(charactersIn: "=")
|
||||
let userInfo = rawUserInfo.base64EncodedString().trimmingCharacters(in: paddings)
|
||||
|
||||
var items = [URLQueryItem(name: "OTA", value: ota.description)]
|
||||
if enabledKcptun {
|
||||
items.append(URLQueryItem(name: "Kcptun", value: enabledKcptun.description))
|
||||
items.append(contentsOf: kcptunProfile.urlQueryItems())
|
||||
}
|
||||
|
||||
var comps = URLComponents()
|
||||
|
||||
comps.scheme = "ss"
|
||||
comps.host = serverHost
|
||||
comps.port = Int(serverPort)
|
||||
comps.user = userInfo
|
||||
comps.path = "/" // This is required by SIP0002 for URLs with fragment or query
|
||||
comps.fragment = remark
|
||||
comps.queryItems = items
|
||||
|
||||
let url = try? comps.asURL()
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func title() -> String {
|
||||
if remark.isEmpty {
|
||||
|
@ -30,6 +30,8 @@ class ToastWindowController: NSWindowController {
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
|
||||
self.shouldCascadeWindows = false
|
||||
|
||||
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
|
||||
if let win = self.window {
|
||||
win.isOpaque = false
|
||||
|
@ -56,7 +56,10 @@ void ScanQRCodeOnScreen() {
|
||||
NSLog(@"%@", feature.messageString);
|
||||
if ( [feature.messageString hasPrefix:@"ss://"] )
|
||||
{
|
||||
[foundSSUrls addObject:[NSURL URLWithString:feature.messageString]];
|
||||
NSURL *url = [NSURL URLWithString:feature.messageString];
|
||||
if (url) {
|
||||
[foundSSUrls addObject:url];
|
||||
}
|
||||
}
|
||||
}
|
||||
CGImageRelease(image);
|
||||
|
12
ShadowsocksX-NG/fix_dir_owner.sh
Executable file
12
ShadowsocksX-NG/fix_dir_owner.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
# fix_dir_owner.sh
|
||||
# ShadowsocksX-NG
|
||||
#
|
||||
# Created by 邱宇舟 on 2017/8/25.
|
||||
# Copyright © 2017年 qiuyuzhou. All rights reserved.
|
||||
|
||||
|
||||
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
|
||||
sudo chown $@ "$HOME/Library/LaunchAgents"
|
||||
|
@ -9,6 +9,6 @@
|
||||
#ifndef proxy_conf_helper_version_h
|
||||
#define proxy_conf_helper_version_h
|
||||
|
||||
#define kProxyConfHelperVersion @"1.5.0"
|
||||
#define kProxyConfHelperVersion @"1.6.0"
|
||||
|
||||
#endif /* proxy_conf_helper_version_h */
|
||||
|
@ -6,4 +6,6 @@
|
||||
# Created by 邱宇舟 on 2017/1/11.
|
||||
# Copyright © 2017年 qiuyuzhou. All rights reserved.
|
||||
|
||||
chmod 644 "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.kcptun.plist"
|
||||
launchctl load "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.kcptun.plist"
|
||||
launchctl start com.qiuyuzhou.shadowsocksX-NG.kcptun
|
||||
|
@ -6,4 +6,6 @@
|
||||
# Created by 王晨 on 16/10/7.
|
||||
# Copyright © 2016年 zhfish. All rights reserved.
|
||||
|
||||
chmod 644 "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.http.plist"
|
||||
launchctl load "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.http.plist"
|
||||
launchctl start com.qiuyuzhou.shadowsocksX-NG.http
|
||||
|
@ -6,4 +6,6 @@
|
||||
# Created by 邱宇舟 on 16/6/6.
|
||||
# Copyright © 2016年 qiuyuzhou. All rights reserved.
|
||||
|
||||
launchctl load "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist"
|
||||
chmod 644 "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist"
|
||||
launchctl load "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist"
|
||||
launchctl start com.qiuyuzhou.shadowsocksX-NG.local
|
||||
|
@ -6,4 +6,5 @@
|
||||
# Created by 邱宇舟 on 2017/1/11.
|
||||
# Copyright © 2017年 qiuyuzhou. All rights reserved.
|
||||
|
||||
launchctl stop com.qiuyuzhou.shadowsocksX-NG.kcptun
|
||||
launchctl unload "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.kcptun.plist"
|
||||
|
@ -6,6 +6,5 @@
|
||||
# Created by 王晨 on 16/10/7.
|
||||
# Copyright © 2016年 zhfish. All rights reserved.
|
||||
|
||||
|
||||
|
||||
launchctl stop com.qiuyuzhou.shadowsocksX-NG.http
|
||||
launchctl unload "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.http.plist"
|
||||
|
@ -6,6 +6,5 @@
|
||||
# Created by 邱宇舟 on 16/6/6.
|
||||
# Copyright © 2016年 qiuyuzhou. All rights reserved.
|
||||
|
||||
|
||||
|
||||
launchctl unload "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist"
|
||||
launchctl stop com.qiuyuzhou.shadowsocksX-NG.local
|
||||
launchctl unload "$HOME/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist"
|
||||
|
@ -26,7 +26,7 @@
|
||||
* ./AppDelegate.swift
|
||||
*/
|
||||
|
||||
"Add Shadowsocks Server Profile" = "已添加新Shaodwsocks服务器配置";
|
||||
"Add Shadowsocks Server Profile" = "已添加新Shadowsocks服务器配置";
|
||||
|
||||
"By scan QR Code" = "通过扫描二维码";
|
||||
|
||||
|
@ -31,7 +31,7 @@ class ServerProfileTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInitWithSelfGeneratedURL() {
|
||||
let newProfile = ServerProfile.init(url: profile.URL())
|
||||
let newProfile = ServerProfile.init(url: profile.URL()!)
|
||||
|
||||
XCTAssertEqual(newProfile?.serverHost, profile.serverHost)
|
||||
XCTAssertEqual(newProfile?.serverPort, profile.serverPort)
|
||||
@ -42,7 +42,7 @@ class ServerProfileTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInitWithPlainURL() {
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388")
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
@ -57,7 +57,7 @@ class ServerProfileTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInitWithPlainURLandQuery() {
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true")
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
@ -72,7 +72,7 @@ class ServerProfileTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInitWithPlainURLandAnotherQuery() {
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=0")
|
||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=0")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
@ -88,7 +88,7 @@ class ServerProfileTests: XCTestCase {
|
||||
|
||||
func testInitWithBase64EncodedURL() {
|
||||
// "ss://aes-256-cfb:password@example.com:8388"
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OA")
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OA")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
@ -104,7 +104,7 @@ class ServerProfileTests: XCTestCase {
|
||||
|
||||
func testInitWithBase64EncodedURLandQuery() {
|
||||
// "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true"
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OD9SZW1hcms9UHJpc20mT1RBPXRydWU")
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OD9SZW1hcms9UHJpc20mT1RBPXRydWU")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
@ -119,15 +119,7 @@ class ServerProfileTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInitWithEmptyURL() {
|
||||
let url = URL(string: "ss://")
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNil(profile)
|
||||
}
|
||||
|
||||
func testInitWithInvalidURL() {
|
||||
let url = URL(string: "ss://invalid url")
|
||||
let url = URL(string: "ss://")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
@ -136,13 +128,47 @@ class ServerProfileTests: XCTestCase {
|
||||
|
||||
func testInitWithBase64EncodedInvalidURL() {
|
||||
// "ss://invalid url"
|
||||
let url = URL(string: "ss://aW52YWxpZCB1cmw")
|
||||
let url = URL(string: "ss://aW52YWxpZCB1cmw")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNil(profile)
|
||||
}
|
||||
|
||||
func testInitWithSIP002URL() {
|
||||
// "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true"
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmQ=@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 testInitWithSIP002URLProfileName() {
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmQ=@example.com:8388/#Name")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
XCTAssertEqual(profile?.remark, "Name")
|
||||
}
|
||||
|
||||
func testInitWithSIP002URLProfileNameOverride() {
|
||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmQ=@example.com:8388/?Remark=Name#Overriden")!
|
||||
|
||||
let profile = ServerProfile(url: url)
|
||||
|
||||
XCTAssertNotNil(profile)
|
||||
XCTAssertEqual(profile?.remark, "Overriden")
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
|
@ -158,7 +158,7 @@ int main(int argc, const char * argv[])
|
||||
kCFNetworkProxiesSOCKSPort];
|
||||
[proxies setObject:[NSNumber numberWithInt:1] forKey:(NSString*)
|
||||
kCFNetworkProxiesSOCKSEnable];
|
||||
[proxies setObject:@[@"127.0.0.1", @"localhost"] forKey:(NSString *)kCFNetworkProxiesExceptionsList];
|
||||
[proxies setObject:@[@"127.0.0.1", @"localhost", @"192.168.0.0/16", @"10.0.0.0/8"] forKey:(NSString *)kCFNetworkProxiesExceptionsList];
|
||||
|
||||
if (privoxyPort != 0) {
|
||||
[proxies setObject:@"127.0.0.1" forKey:(NSString *)
|
||||
|
Reference in New Issue
Block a user