Merge pull request #1 from qinyuhang/white-list

add White list feature
This commit is contained in:
秦宇航
2016-08-14 07:58:44 +08:00
committed by GitHub
13 changed files with 10069 additions and 14 deletions
+4 -4
View File
@@ -8,13 +8,13 @@ Next Generation of [ShadowsocksX](https://github.com/shadowsocks/shadowsocks-iOS
## Why?
It's hard to maintaine the original implement. There are too many unused code in it.
It also embed ss-local source. It's crazy to maitaine depandences of ss-local.
It's hard to maintain the original implement. There are too many unused code in it.
It also embed ss-local source. It's crazy to maintain depandences of ss-local.
So it's hard to update ss-local version.
Now I just copy the ss-local from home brew. Run ss-local executable as a Launch Agent in background.
Serve pac js file as a file url. So there are only some souce codes related to GUI left.
Then I rewrite the gui code by swift.
Serve pac js file as a file url. So there are only some souce code related to GUI left.
Then I rewrite the GUI code by swift.
## Requirements
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
088EC3971D5F5B8600E40791 /* whitelist.pac in Resources */ = {isa = PBXBuildFile; fileRef = 088EC3961D5F5B8600E40791 /* whitelist.pac */; };
088EC3991D5F5BA300E40791 /* whiteiplist.pac in Resources */ = {isa = PBXBuildFile; fileRef = 088EC3981D5F5BA300E40791 /* whiteiplist.pac */; };
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 */; };
@@ -88,6 +90,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
088EC3961D5F5B8600E40791 /* whitelist.pac */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = whitelist.pac; sourceTree = "<group>"; };
088EC3981D5F5BA300E40791 /* whiteiplist.pac */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = whiteiplist.pac; 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; };
@@ -222,6 +226,8 @@
9B3FFF361D08CF110019A709 /* qrcode.min.js */,
9B3FFF371D08CF110019A709 /* qrcode.htm */,
9B3FFF281D08A1DF0019A709 /* user-rule.txt */,
088EC3961D5F5B8600E40791 /* whitelist.pac */,
088EC3981D5F5BA300E40791 /* whiteiplist.pac */,
9B3FFF261D0898EB0019A709 /* gfwlist.txt */,
9B3FFF221D088E8D0019A709 /* abp.js */,
9B07EFA61D048BBB0052D9DF /* ss-local */,
@@ -471,12 +477,14 @@
9B3FFF381D08CF110019A709 /* jquery.min.js in Resources */,
9B3FFF271D0898EB0019A709 /* gfwlist.txt in Resources */,
9BC70EDC1D2E3E3100EDA4CA /* Localizable.strings in Resources */,
088EC3971D5F5B8600E40791 /* whitelist.pac in Resources */,
9B0BFFEE1D0460A70040E62B /* MainMenu.xib in Resources */,
9B3FFF4C1D09D8F70019A709 /* install_helper.sh in Resources */,
9B07EFAC1D048E880052D9DF /* menu_icon@2x.png in Resources */,
9B07EFA71D048BBB0052D9DF /* ss-local in Resources */,
9B07EFAF1D048E880052D9DF /* menu_icon_disabled@2x.png in Resources */,
9B07EFAE1D048E880052D9DF /* menu_icon_disabled.png in Resources */,
088EC3991D5F5BA300E40791 /* whiteiplist.pac in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+59
View File
@@ -29,6 +29,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
@IBOutlet weak var autoModeMenuItem: NSMenuItem!
@IBOutlet weak var globalModeMenuItem: NSMenuItem!
@IBOutlet weak var manualModeMenuItem: NSMenuItem!
@IBOutlet weak var whiteListModeMenuItem: NSMenuItem!
@IBOutlet weak var whiteListDomainMenuItem: NSMenuItem!
@IBOutlet weak var whiteListIPMenuItem: NSMenuItem!
@IBOutlet weak var serversMenuItem: NSMenuItem!
@IBOutlet var showQRCodeMenuItem: NSMenuItem!
@@ -58,6 +61,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
"LocalSocks5.EnableUDPRelay": NSNumber(bool: false),
"LocalSocks5.EnableVerboseMode": NSNumber(bool: false),
"GFWListURL": "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt",
"WhiteListURL": "https://raw.githubusercontent.com/breakwa11/gfw_whitelist/master/whitelist.pac",
"WhiteListIPURL": "https://raw.githubusercontent.com/breakwa11/gfw_whitelist/master/whiteiplist.pac",
"AutoConfigureNetworkServices": NSNumber(bool: true)
])
@@ -144,6 +149,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
StopSSLocal()
ProxyConfHelper.disableProxy()
}
func applyConfig() {
@@ -154,11 +161,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
if isOn {
StartSSLocal()
if mode == "auto" {
ProxyConfHelper.disableProxy()
ProxyConfHelper.enablePACProxy()
} else if mode == "global" {
ProxyConfHelper.disableProxy()
ProxyConfHelper.enableGlobalProxy()
} else if mode == "manual" {
ProxyConfHelper.disableProxy()
ProxyConfHelper.disableProxy()
} else if mode == "whiteListDomain" {
ProxyConfHelper.disableProxy()
ProxyConfHelper.enableWhiteDomainListProxy()
} else if mode == "whiteListIP" {
ProxyConfHelper.disableProxy()
ProxyConfHelper.enableWhiteIPListProxy()
}
} else {
StopSSLocal()
@@ -181,6 +197,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
UpdatePACFromGFWList()
}
@IBAction func updateWhiteList(sender: NSMenuItem) {
UpdatePACFromWhiteList()
}
@IBAction func editUserRulesForPAC(sender: NSMenuItem) {
if editUserRulesWinCtrl != nil {
editUserRulesWinCtrl.close()
@@ -252,6 +272,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
applyConfig()
}
@IBAction func selectWhiteDomainListMode(sender: NSMenuItem) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("whiteListDomain", forKey: "ShadowsocksRunningMode")
updateRunningModeMenu()
applyConfig()
}
@IBAction func selectWhiteIPListMode(sender: NSMenuItem) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("whiteListIP", forKey: "ShadowsocksRunningMode")
updateRunningModeMenu()
applyConfig()
}
@IBAction func editServerPreferences(sender: NSMenuItem) {
if preferencesWinCtrl != nil {
preferencesWinCtrl.close()
@@ -331,16 +365,41 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
autoModeMenuItem.state = 1
globalModeMenuItem.state = 0
manualModeMenuItem.state = 0
whiteListModeMenuItem.state = 0
whiteListDomainMenuItem.state = 0
whiteListIPMenuItem.state = 0
} else if mode == "global" {
proxyMenuItem.title = "Proxy - Global".localized
autoModeMenuItem.state = 0
globalModeMenuItem.state = 1
manualModeMenuItem.state = 0
whiteListModeMenuItem.state = 0
whiteListDomainMenuItem.state = 0
whiteListIPMenuItem.state = 0
} else if mode == "manual" {
proxyMenuItem.title = "Proxy - Manual".localized
autoModeMenuItem.state = 0
globalModeMenuItem.state = 0
manualModeMenuItem.state = 1
whiteListModeMenuItem.state = 0
whiteListDomainMenuItem.state = 0
whiteListIPMenuItem.state = 0
} else if mode == "whiteListDomain" {
proxyMenuItem.title = "Proxy - WhiteList Domain".localized
autoModeMenuItem.state = 0
globalModeMenuItem.state = 0
manualModeMenuItem.state = 0
whiteListModeMenuItem.state = 1
whiteListDomainMenuItem.state = 1
whiteListIPMenuItem.state = 0
} else if mode == "whiteListIP" {
proxyMenuItem.title = "Proxy - WhiteList IP".localized
autoModeMenuItem.state = 0
globalModeMenuItem.state = 0
manualModeMenuItem.state = 0
whiteListModeMenuItem.state = 1
whiteListDomainMenuItem.state = 0
whiteListIPMenuItem.state = 1
}
}
@@ -45,3 +45,15 @@
"Proxy - Global" = "Proxy - Global";
"Proxy - Manual" = "Proxy - Manual";
"Proxy - WhiteList" = "Proxy - WhiteList";
"Proxy - WhiteList Domain" = "Proxy - WhiteList Domain";
"Proxy - WhiteList IP" = "Proxy - WhiteList IP";
"White List Mode" = "White List Mode";
"White List Domain" = "White List Domain";
"White List IP" = "White List IP";
+29 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
@@ -26,6 +26,9 @@
<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"/>
<outlet property="whiteListDomainMenuItem" destination="L27-aH-Wwl" id="h4b-aF-lyt"/>
<outlet property="whiteListIPMenuItem" destination="Xbu-Kq-BSR" id="D9i-Xg-6Gc"/>
<outlet property="whiteListModeMenuItem" destination="RvZ-Zn-29U" id="2v5-Bd-EDj"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
@@ -63,6 +66,25 @@
<action selector="selectManualMode:" target="Voe-Tx-rLC" id="Xxb-28-6fi"/>
</connections>
</menuItem>
<menuItem title="White List Mode" id="RvZ-Zn-29U">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="White List Mode" id="nAe-c2-Q79">
<items>
<menuItem title="White List Domain" id="L27-aH-Wwl">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="selectWhiteDomainListMode:" target="Voe-Tx-rLC" id="Oge-mM-TtL"/>
</connections>
</menuItem>
<menuItem title="White List IP" id="Xbu-Kq-BSR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="selectWhiteIPListMode:" target="Voe-Tx-rLC" id="Ahq-h9-jJP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="V92-VH-Agn"/>
<menuItem title="Update PAC from GFW List" id="TFc-Ec-duM">
<modifierMask key="keyEquivalentModifierMask"/>
@@ -70,6 +92,12 @@
<action selector="updateGFWList:" target="Voe-Tx-rLC" id="Ztt-PS-F3T"/>
</connections>
</menuItem>
<menuItem title="Update All Wihte List" id="gde-ZE-086">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="updateWhiteList:" target="Voe-Tx-rLC" id="2Iv-M4-k9C"/>
</connections>
</menuItem>
<menuItem title="Edit User Rules For PAC..." id="rms-p0-CvB">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.2.R</string>
<string>1.3.2-R</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
+104
View File
@@ -15,6 +15,8 @@ let PACRulesDirPath = NSHomeDirectory() + "/.ShadowsocksX-NG/"
let PACUserRuleFilePath = PACRulesDirPath + "user-rule.txt"
let PACFilePath = PACRulesDirPath + "gfwlist.js"
let GFWListFilePath = PACRulesDirPath + "gfwlist.txt"
let WhiteListDomainPACFilePath = PACRulesDirPath + "whitelist.pac"
let WhiteListIPPACFilePath = PACRulesDirPath + "whiteiplist.pac"
// Because of LocalSocks5.ListenPort may be changed
@@ -33,6 +35,10 @@ func SyncPac() {
needGenerate = true
}
if !fileMgr.fileExistsAtPath(WhiteListDomainPACFilePath) && !fileMgr.fileExistsAtPath(WhiteListIPPACFilePath) {
needGenerate = true
}
if needGenerate {
GeneratePACFile()
}
@@ -63,6 +69,18 @@ func GeneratePACFile() -> Bool {
try! fileMgr.copyItemAtPath(src!, toPath: PACUserRuleFilePath)
}
// If whitelist.pac is not exsited, copy from bundle
if !fileMgr.fileExistsAtPath(WhiteListDomainPACFilePath) {
let src = NSBundle.mainBundle().pathForResource("whitelist", ofType: "pac")
try! fileMgr.copyItemAtPath(src!, toPath: WhiteListDomainPACFilePath)
}
// If whitelistip.pac is not exsited, copy from bundle
if !fileMgr.fileExistsAtPath(WhiteListIPPACFilePath) {
let src = NSBundle.mainBundle().pathForResource("whiteiplist", ofType: "pac")
try! fileMgr.copyItemAtPath(src!, toPath: WhiteListIPPACFilePath)
}
let socks5Port = NSUserDefaults.standardUserDefaults().integerForKey("LocalSocks5.ListenPort")
do {
@@ -114,6 +132,26 @@ func GeneratePACFile() -> Bool {
try result.dataUsingEncoding(NSUTF8StringEncoding)?
.writeToFile(PACFilePath, options: .DataWritingAtomic)
// Setup Pac for White List
let DomainSrc = NSBundle.mainBundle().pathForResource("whitelist", ofType: "pac")
let IPSrc = NSBundle.mainBundle().pathForResource("whiteiplist", ofType: "pac")
let DomainPacFile = NSData(contentsOfFile: DomainSrc!)
let IPPACFile = NSData(contentsOfFile: IPSrc!)
var DomainPACStr = String(data: DomainPacFile!,encoding: NSUTF8StringEncoding)!
var IPPACStr = String(data: IPPACFile!,encoding: NSUTF8StringEncoding)!
if(DomainPACStr.rangeOfString("SOCKS ") == nil) {
DomainPACStr = DomainPACStr.stringByReplacingOccurrencesOfString("SOCKS5 127.0.0.1:1080;", withString: "SOCKS5 127.0.0.1:\(socks5Port);SOCKS 127.0.0.1:\(socks5Port);")
}
DomainPACStr = DomainPACStr.stringByReplacingOccurrencesOfString("SOCKS 127.0.0.1:1080;", withString: "SOCKS 127.0.0.1:\(socks5Port);")
if(IPPACStr.rangeOfString("SOCKS ") == nil) {
IPPACStr = IPPACStr.stringByReplacingOccurrencesOfString("SOCKS5 127.0.0.1:1080;", withString: "SOCKS5 127.0.0.1:\(socks5Port);SOCKS 127.0.0.1:\(socks5Port);")
}
IPPACStr = IPPACStr.stringByReplacingOccurrencesOfString("SOCKS 127.0.0.1:1080;", withString: "SOCKS 127.0.0.1:\(socks5Port);")
try
DomainPACStr.dataUsingEncoding(NSUTF8StringEncoding)?.writeToFile(WhiteListDomainPACFilePath, options: .DataWritingAtomic)
try
IPPACStr.dataUsingEncoding(NSUTF8StringEncoding)?.writeToFile(WhiteListIPPACFilePath, options: .DataWritingAtomic)
return true
} catch {
@@ -164,3 +202,69 @@ func UpdatePACFromGFWList() {
}
}
}
func UpdatePACFromWhiteList(){
if !NSFileManager.defaultManager().fileExistsAtPath(PACRulesDirPath) {
do {
try NSFileManager.defaultManager().createDirectoryAtPath(PACRulesDirPath
, withIntermediateDirectories: true, attributes: nil)
} catch {
}
}
let url = NSUserDefaults.standardUserDefaults().stringForKey("WhiteListURL")
Alamofire.request(.GET, url!)
.responseString {
response in
if response.result.isSuccess {
if let v = response.result.value {
do {
try v.writeToFile(WhiteListDomainPACFilePath, atomically: true, encoding: NSUTF8StringEncoding)
if GeneratePACFile() {
// Popup a user notification
let notification = NSUserNotification()
notification.title = "Wihte List update succeed.".localized
NSUserNotificationCenter.defaultUserNotificationCenter()
.deliverNotification(notification)
}
} catch {
}
}
} else {
// Popup a user notification
let notification = NSUserNotification()
notification.title = "Failed to download latest Wihte List update succeed.".localized
NSUserNotificationCenter.defaultUserNotificationCenter()
.deliverNotification(notification)
}
}
let IPURL = NSUserDefaults.standardUserDefaults().stringForKey("WhiteListIPURL")
Alamofire.request(.GET, IPURL!)
.responseString {
response in
if response.result.isSuccess {
if let v = response.result.value {
do {
try v.writeToFile(WhiteListIPPACFilePath, atomically: true, encoding: NSUTF8StringEncoding)
if GeneratePACFile() {
// Popup a user notification
let notification = NSUserNotification()
notification.title = "Wihte List update succeed.".localized
NSUserNotificationCenter.defaultUserNotificationCenter()
.deliverNotification(notification)
}
} catch {
}
}
} else {
// Popup a user notification
let notification = NSUserNotification()
notification.title = "Failed to download latest Wihte List update succeed.".localized
NSUserNotificationCenter.defaultUserNotificationCenter()
.deliverNotification(notification)
}
}
}
+4
View File
@@ -18,4 +18,8 @@
+ (void)disableProxy;
+ (void)enableWhiteDomainListProxy;
+ (void)enableWhiteIPListProxy;
@end
+29 -8
View File
@@ -135,16 +135,37 @@
[self callHelper:args];
}
+ (void)enableWhiteDomainListProxy {
NSString* urlString = [NSString stringWithFormat:@"%@/.ShadowsocksX-NG/whitelist.pac", NSHomeDirectory()];
NSURL* url = [NSURL fileURLWithPath:urlString];
NSMutableArray* args = [@[@"--mode", @"auto", @"--pac-url", [url absoluteString]]mutableCopy];
[self addArguments4ManualSpecifyNetworkServices:args];
[self callHelper:args];
}
+ (void)enableWhiteIPListProxy {
NSString* urlString = [NSString stringWithFormat:@"%@/.ShadowsocksX-NG/whiteiplist.pac", NSHomeDirectory()];
NSURL* url = [NSURL fileURLWithPath:urlString];
NSMutableArray* args = [@[@"--mode", @"auto", @"--pac-url", [url absoluteString]]mutableCopy];
[self addArguments4ManualSpecifyNetworkServices:args];
[self callHelper:args];
}
+ (void)disableProxy {
// 带上所有参数是为了判断是否原有代理设置是否由ssx-ng设置的。如果是用户手工设置的其他配置,则不进行清空。
NSString* urlString = [NSString stringWithFormat:@"%@/.ShadowsocksX-NG/gfwlist.js", NSHomeDirectory()];
NSURL* url = [NSURL fileURLWithPath:urlString];
NSUInteger port = [[NSUserDefaults standardUserDefaults]integerForKey:@"LocalSocks5.ListenPort"];
NSMutableArray* args = [@[@"--mode", @"off"
, @"--port", [NSString stringWithFormat:@"%lu", (unsigned long)port]
, @"--pac-url", [url absoluteString]
]mutableCopy];
// NSString* urlString = [NSString stringWithFormat:@"%@/.ShadowsocksX-NG/gfwlist.js", NSHomeDirectory()];
// NSURL* url = [NSURL fileURLWithPath:urlString];
// NSUInteger port = [[NSUserDefaults standardUserDefaults]integerForKey:@"LocalSocks5.ListenPort"];
//
// NSMutableArray* args = [@[@"--mode", @"off"
// , @"--port", [NSString stringWithFormat:@"%lu", (unsigned long)port]
// , @"--pac-url", [url absoluteString]
// ]mutableCopy];
NSMutableArray* args = [@[@"--mode", @"off"]mutableCopy];
[self addArguments4ManualSpecifyNetworkServices:args];
[self callHelper:args];
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -50,3 +50,14 @@
"Proxy - Manual" = "代理 - 手动";
"Proxy - WhiteList" = "代理 - 白名单";
"Proxy - WhiteList Domain" = "代理 - 域名白名单";
"Proxy - WhiteList IP" = "代理 - IP白名单";
"White List Mode" = "白名单模式";
"White List Domain" = "域名白名单";
"White List IP" = "IP白名单";
@@ -67,3 +67,18 @@
/* Class = "NSMenuItem"; title = "退出"; ObjectID = "vJS-JW-byz"; */
"vJS-JW-byz.title" = "退出";
/* Class = "NSMenuItem"; title = "域名白名单"; ObjectID = "L27-aH-Wwl"; */
"L27-aH-Wwl.title" = "域名白名单";
/* Class = "NSMenuItem"; title = "IP白名单"; ObjectID = "Xbu-Kq-BSR"; */
"Xbu-Kq-BSR.title" = "IP白名单";
/* Class = "NSMenuItem"; title = "白名单模式"; ObjectID = "nAe-c2-Q79"; */
"nAe-c2-Q79.title" = "白名单模式";
/* Class = "NSMenuItem"; title = "白名单模式"; ObjectID = "RvZ-Zn-29U"; */
"RvZ-Zn-29U.title" = "白名单模式";
/* Class = "NSMenuItem"; title = "更新全部白名单"; ObjectID = "gde-ZE-086"; */
"gde-ZE-086.title" = "更新全部白名单";