Commit be6cf63d authored by Mygod's avatar Mygod

Merge branch 'master' into preference-1.1

parents f4171711 dca0da09
...@@ -9,14 +9,11 @@ buildscript { ...@@ -9,14 +9,11 @@ buildscript {
sdkVersion = 28 sdkVersion = 28
compileSdkVersion = 28 compileSdkVersion = 28
buildToolsVersion = '28.0.3' buildToolsVersion = '28.0.3'
lifecycleVersion = '2.0.0'
roomVersion = '2.0.0'
workVersion = '1.0.0-beta05'
junitVersion = '4.12' junitVersion = '4.12'
androidTestVersion = '1.1.1' androidTestVersion = '1.1.1'
androidEspressoVersion = '3.1.1' androidEspressoVersion = '3.1.1'
versionCode = 4070050 versionCode = 4070150
versionName = '4.7.0-nightly' versionName = '4.7.1-nightly'
} }
repositories { repositories {
...@@ -28,6 +25,7 @@ buildscript { ...@@ -28,6 +25,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
classpath 'com.android.tools.build:gradle:3.3.1' classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
classpath 'com.google.gms:google-services:4.2.0' classpath 'com.google.gms:google-services:4.2.0'
......
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
...@@ -38,6 +39,13 @@ android { ...@@ -38,6 +39,13 @@ android {
} }
} }
androidExtensions {
experimental = true
}
def lifecycleVersion = '2.0.0'
def roomVersion = '2.0.0'
def workVersion = '1.0.0-rc02'
dependencies { dependencies {
api project(':plugin') api project(':plugin')
api "android.arch.work:work-runtime-ktx:$workVersion" api "android.arch.work:work-runtime-ktx:$workVersion"
...@@ -46,7 +54,7 @@ dependencies { ...@@ -46,7 +54,7 @@ dependencies {
api 'androidx.preference:preference:1.1.0-alpha03' api 'androidx.preference:preference:1.1.0-alpha03'
api "androidx.room:room-runtime:$roomVersion" api "androidx.room:room-runtime:$roomVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.9.9' api 'com.crashlytics.sdk.android:crashlytics:2.9.9'
api 'com.google.firebase:firebase-config:16.3.0' api 'com.google.firebase:firebase-config:16.1.3'
api 'com.google.firebase:firebase-core:16.0.7' api 'com.google.firebase:firebase-core:16.0.7'
api 'dnsjava:dnsjava:2.1.8' api 'dnsjava:dnsjava:2.1.8'
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
......
# #
# GFW list from https://github.com/gfwlist/gfwlist/blob/master/gfwlist.txt # GFW list from https://github.com/gfwlist/gfwlist/blob/master/gfwlist.txt
# updated on 2018-03-30 # updated on 2019-02-21
# #
[bypass_all] [bypass_all]
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
91.108.56.0/22 91.108.56.0/22
109.239.140.0/24 109.239.140.0/24
149.154.160.0/20 149.154.160.0/20
1.1.1.1
14.102.250.18 14.102.250.18
14.102.250.19 14.102.250.19
174.142.105.153 174.142.105.153
...@@ -36,6 +38,7 @@ ...@@ -36,6 +38,7 @@
(^|\.)12bet\.com$ (^|\.)12bet\.com$
(^|\.)12vpn\.com$ (^|\.)12vpn\.com$
(^|\.)12vpn\.net$ (^|\.)12vpn\.net$
(^|\.)138\.com$
(^|\.)141hongkong\.com$ (^|\.)141hongkong\.com$
(^|\.)141jj\.com$ (^|\.)141jj\.com$
(^|\.)141tube\.com$ (^|\.)141tube\.com$
...@@ -72,7 +75,6 @@ ...@@ -72,7 +75,6 @@
(^|\.)24hrs\.ca$ (^|\.)24hrs\.ca$
(^|\.)24smile\.org$ (^|\.)24smile\.org$
(^|\.)25u\.com$ (^|\.)25u\.com$
(^|\.)2dbook\.com$
(^|\.)2lipstube\.com$ (^|\.)2lipstube\.com$
(^|\.)2shared\.com$ (^|\.)2shared\.com$
(^|\.)2waky\.com$ (^|\.)2waky\.com$
...@@ -88,6 +90,7 @@ ...@@ -88,6 +90,7 @@
(^|\.)3proxy\.ru$ (^|\.)3proxy\.ru$
(^|\.)3ren\.ca$ (^|\.)3ren\.ca$
(^|\.)3tui\.net$ (^|\.)3tui\.net$
(^|\.)43110\.cf$
(^|\.)466453\.com$ (^|\.)466453\.com$
(^|\.)4bluestones\.biz$ (^|\.)4bluestones\.biz$
(^|\.)4chan\.com$ (^|\.)4chan\.com$
...@@ -98,11 +101,11 @@ ...@@ -98,11 +101,11 @@
(^|\.)4pu\.com$ (^|\.)4pu\.com$
(^|\.)4rbtv\.com$ (^|\.)4rbtv\.com$
(^|\.)4shared\.com$ (^|\.)4shared\.com$
(^|\.)4tern\.com$
(^|\.)51\.ca$ (^|\.)51\.ca$
(^|\.)51jav\.org$ (^|\.)51jav\.org$
(^|\.)51luoben\.com$ (^|\.)51luoben\.com$
(^|\.)5278\.cc$ (^|\.)5278\.cc$
(^|\.)5299\.tv$
(^|\.)56cun04\.jigsy\.com$ (^|\.)56cun04\.jigsy\.com$
(^|\.)5aimiku\.com$ (^|\.)5aimiku\.com$
(^|\.)5i01\.com$ (^|\.)5i01\.com$
...@@ -145,10 +148,13 @@ ...@@ -145,10 +148,13 @@
(^|\.)a5\.com\.ru$ (^|\.)a5\.com\.ru$
(^|\.)aamacau\.com$ (^|\.)aamacau\.com$
(^|\.)abc\.com$ (^|\.)abc\.com$
(^|\.)abc\.net\.au$
(^|\.)abc\.pp\.ru$ (^|\.)abc\.pp\.ru$
(^|\.)abc\.xyz$ (^|\.)abc\.xyz$
(^|\.)abchinese\.com$ (^|\.)abchinese\.com$
(^|\.)abclite\.net$ (^|\.)abclite\.net$
(^|\.)abebooks\.com$
(^|\.)abematv\.akamaized\.net$
(^|\.)abitno\.linpie\.com$ (^|\.)abitno\.linpie\.com$
(^|\.)ablwang\.com$ (^|\.)ablwang\.com$
(^|\.)aboluowang\.com$ (^|\.)aboluowang\.com$
...@@ -167,10 +173,11 @@ ...@@ -167,10 +173,11 @@
(^|\.)actimes\.com\.au$ (^|\.)actimes\.com\.au$
(^|\.)activpn\.com$ (^|\.)activpn\.com$
(^|\.)aculo\.us$ (^|\.)aculo\.us$
(^|\.)adcex\.com$
(^|\.)addictedtocoffee\.de$ (^|\.)addictedtocoffee\.de$
(^|\.)adelaidebbs\.com$ (^|\.)adelaidebbs\.com$
(^|\.)admin\.recaptcha\.net$
(^|\.)admob\.com$ (^|\.)admob\.com$
(^|\.)adorama\.com$
(^|\.)adpl\.org\.hk$ (^|\.)adpl\.org\.hk$
(^|\.)ads-twitter\.com$ (^|\.)ads-twitter\.com$
(^|\.)adsense\.com$ (^|\.)adsense\.com$
...@@ -183,6 +190,7 @@ ...@@ -183,6 +190,7 @@
(^|\.)ae\.hao123\.com$ (^|\.)ae\.hao123\.com$
(^|\.)ae\.org$ (^|\.)ae\.org$
(^|\.)aenhancers\.com$ (^|\.)aenhancers\.com$
(^|\.)aex\.com$
(^|\.)af\.mil$ (^|\.)af\.mil$
(^|\.)afantibbs\.com$ (^|\.)afantibbs\.com$
(^|\.)agnesb\.fr$ (^|\.)agnesb\.fr$
...@@ -192,6 +200,7 @@ ...@@ -192,6 +200,7 @@
(^|\.)ai-kan\.net$ (^|\.)ai-kan\.net$
(^|\.)ai-wen\.net$ (^|\.)ai-wen\.net$
(^|\.)ai\.binwang\.me$ (^|\.)ai\.binwang\.me$
(^|\.)ai\.google$
(^|\.)aiph\.net$ (^|\.)aiph\.net$
(^|\.)airasia\.com$ (^|\.)airasia\.com$
(^|\.)airconsole\.com$ (^|\.)airconsole\.com$
...@@ -203,6 +212,7 @@ ...@@ -203,6 +212,7 @@
(^|\.)akademiye\.org$ (^|\.)akademiye\.org$
(^|\.)akiba-online\.com$ (^|\.)akiba-online\.com$
(^|\.)akiba-web\.com$ (^|\.)akiba-web\.com$
(^|\.)akow\.org$
(^|\.)al-islam\.com$ (^|\.)al-islam\.com$
(^|\.)al-qimmah\.net$ (^|\.)al-qimmah\.net$
(^|\.)alabout\.com$ (^|\.)alabout\.com$
...@@ -213,9 +223,9 @@ ...@@ -213,9 +223,9 @@
(^|\.)alforattv\.net$ (^|\.)alforattv\.net$
(^|\.)alhayat\.com$ (^|\.)alhayat\.com$
(^|\.)alicejapan\.co\.jp$ (^|\.)alicejapan\.co\.jp$
(^|\.)alien-ufos\.com$
(^|\.)aliengu\.com$ (^|\.)aliengu\.com$
(^|\.)alkasir\.com$ (^|\.)alkasir\.com$
(^|\.)allcoin\.com$
(^|\.)allconnected\.co$ (^|\.)allconnected\.co$
(^|\.)alldrawnsex\.com$ (^|\.)alldrawnsex\.com$
(^|\.)allervpn\.com$ (^|\.)allervpn\.com$
...@@ -239,6 +249,7 @@ ...@@ -239,6 +249,7 @@
(^|\.)alwaysdata\.net$ (^|\.)alwaysdata\.net$
(^|\.)alwaysvpn\.com$ (^|\.)alwaysvpn\.com$
(^|\.)am730\.com\.hk$ (^|\.)am730\.com\.hk$
(^|\.)amazon\.co\.jp$
(^|\.)amazon\.com$ (^|\.)amazon\.com$
(^|\.)ameblo\.jp$ (^|\.)ameblo\.jp$
(^|\.)americangreencard\.com$ (^|\.)americangreencard\.com$
...@@ -261,8 +272,11 @@ ...@@ -261,8 +272,11 @@
(^|\.)android\.com$ (^|\.)android\.com$
(^|\.)androidify\.com$ (^|\.)androidify\.com$
(^|\.)androidplus\.co$ (^|\.)androidplus\.co$
(^|\.)androidtv\.com$
(^|\.)andygod\.com$ (^|\.)andygod\.com$
(^|\.)angela-merkel\.de$
(^|\.)angelfire\.com$ (^|\.)angelfire\.com$
(^|\.)angola\.org$
(^|\.)angularjs\.org$ (^|\.)angularjs\.org$
(^|\.)animecrazy\.net$ (^|\.)animecrazy\.net$
(^|\.)animeshippuuden\.com$ (^|\.)animeshippuuden\.com$
...@@ -287,6 +301,8 @@ ...@@ -287,6 +301,8 @@
(^|\.)aojiao\.org$ (^|\.)aojiao\.org$
(^|\.)aolchannels\.aol\.com$ (^|\.)aolchannels\.aol\.com$
(^|\.)aomiwang\.com$ (^|\.)aomiwang\.com$
(^|\.)apartmentratings\.com$
(^|\.)apartments\.com$
(^|\.)apetube\.com$ (^|\.)apetube\.com$
(^|\.)api-secure\.recaptcha\.net$ (^|\.)api-secure\.recaptcha\.net$
(^|\.)api-verify\.recaptcha\.net$ (^|\.)api-verify\.recaptcha\.net$
...@@ -294,6 +310,7 @@ ...@@ -294,6 +310,7 @@
(^|\.)api\.dropboxapi\.com$ (^|\.)api\.dropboxapi\.com$
(^|\.)api\.linksalpha\.com$ (^|\.)api\.linksalpha\.com$
(^|\.)api\.proxlet\.com$ (^|\.)api\.proxlet\.com$
(^|\.)api\.pureapk\.com$
(^|\.)api\.recaptcha\.net$ (^|\.)api\.recaptcha\.net$
(^|\.)apiary\.io$ (^|\.)apiary\.io$
(^|\.)apidocs\.linksalpha\.com$ (^|\.)apidocs\.linksalpha\.com$
...@@ -307,17 +324,23 @@ ...@@ -307,17 +324,23 @@
(^|\.)aplusvpn\.com$ (^|\.)aplusvpn\.com$
(^|\.)app\.box\.com$ (^|\.)app\.box\.com$
(^|\.)app\.heywire\.com$ (^|\.)app\.heywire\.com$
(^|\.)app\.smartmailcloud\.com$
(^|\.)app\.tutanota\.com$ (^|\.)app\.tutanota\.com$
(^|\.)appdownloader\.net$ (^|\.)appdownloader\.net$
(^|\.)appledaily\.com$ (^|\.)appledaily\.com$
(^|\.)appledaily\.com\.hk$
(^|\.)appledaily\.com\.tw$
(^|\.)appshopper\.com$ (^|\.)appshopper\.com$
(^|\.)appsocks\.net$ (^|\.)appsocks\.net$
(^|\.)appspot\.com$ (^|\.)appspot\.com$
(^|\.)appsto\.re$ (^|\.)appsto\.re$
(^|\.)aptoide\.com$
(^|\.)ar\.hao123\.com$ (^|\.)ar\.hao123\.com$
(^|\.)archive\.fo$ (^|\.)archive\.fo$
(^|\.)archive\.is$ (^|\.)archive\.is$
(^|\.)archive\.li$
(^|\.)archive\.org$ (^|\.)archive\.org$
(^|\.)archive\.today$
(^|\.)archives\.gov$ (^|\.)archives\.gov$
(^|\.)archives\.gov\.tw$ (^|\.)archives\.gov\.tw$
(^|\.)arctosia\.com$ (^|\.)arctosia\.com$
...@@ -327,10 +350,10 @@ ...@@ -327,10 +350,10 @@
(^|\.)arlingtoncemetery\.mil$ (^|\.)arlingtoncemetery\.mil$
(^|\.)army\.mil$ (^|\.)army\.mil$
(^|\.)art4tibet1998\.org$ (^|\.)art4tibet1998\.org$
(^|\.)arte\.tv$
(^|\.)artofpeacefoundation\.org$ (^|\.)artofpeacefoundation\.org$
(^|\.)artsy\.net$ (^|\.)artsy\.net$
(^|\.)asacp\.org$ (^|\.)asacp\.org$
(^|\.)asahichinese\.com$
(^|\.)asdfg\.jp$ (^|\.)asdfg\.jp$
(^|\.)asg\.to$ (^|\.)asg\.to$
(^|\.)asia-gaming\.com$ (^|\.)asia-gaming\.com$
...@@ -344,6 +367,7 @@ ...@@ -344,6 +367,7 @@
(^|\.)askstudent\.com$ (^|\.)askstudent\.com$
(^|\.)askynz\.net$ (^|\.)askynz\.net$
(^|\.)assembla\.com$ (^|\.)assembla\.com$
(^|\.)assets\.bwbx\.io$
(^|\.)assimp\.org$ (^|\.)assimp\.org$
(^|\.)astrill\.com$ (^|\.)astrill\.com$
(^|\.)atc\.org\.au$ (^|\.)atc\.org\.au$
...@@ -358,6 +382,7 @@ ...@@ -358,6 +382,7 @@
(^|\.)authorizeddns\.org$ (^|\.)authorizeddns\.org$
(^|\.)authorizeddns\.us$ (^|\.)authorizeddns\.us$
(^|\.)autodraw\.com$ (^|\.)autodraw\.com$
(^|\.)av-e-body\.com$
(^|\.)av\.com$ (^|\.)av\.com$
(^|\.)av\.movie$ (^|\.)av\.movie$
(^|\.)av\.nightlife141\.com$ (^|\.)av\.nightlife141\.com$
...@@ -368,6 +393,7 @@ ...@@ -368,6 +393,7 @@
(^|\.)avdb\.in$ (^|\.)avdb\.in$
(^|\.)avdb\.tv$ (^|\.)avdb\.tv$
(^|\.)avfantasy\.com$ (^|\.)avfantasy\.com$
(^|\.)avgle\.com$
(^|\.)avidemux\.org$ (^|\.)avidemux\.org$
(^|\.)avmo\.pw$ (^|\.)avmo\.pw$
(^|\.)avmoo\.com$ (^|\.)avmoo\.com$
...@@ -384,17 +410,22 @@ ...@@ -384,17 +410,22 @@
(^|\.)backchina\.com$ (^|\.)backchina\.com$
(^|\.)backpackers\.com\.tw$ (^|\.)backpackers\.com\.tw$
(^|\.)backtotiananmen\.com$ (^|\.)backtotiananmen\.com$
(^|\.)badiucao\.com$
(^|\.)badjojo\.com$ (^|\.)badjojo\.com$
(^|\.)badoo\.com$ (^|\.)badoo\.com$
(^|\.)baidu\.jp$ (^|\.)baidu\.jp$
(^|\.)baijie\.org$
(^|\.)bailandaily\.com$ (^|\.)bailandaily\.com$
(^|\.)baixing\.me$ (^|\.)baixing\.me$
(^|\.)bakgeekhome\.tk$ (^|\.)bakgeekhome\.tk$
(^|\.)banana-vpn\.com$ (^|\.)banana-vpn\.com$
(^|\.)band\.us$
(^|\.)bandwagonhost\.com$ (^|\.)bandwagonhost\.com$
(^|\.)bangbrosnetwork\.com$ (^|\.)bangbrosnetwork\.com$
(^|\.)bangchen\.net$ (^|\.)bangchen\.net$
(^|\.)bangdream\.space$
(^|\.)bangyoulater\.com$ (^|\.)bangyoulater\.com$
(^|\.)bankmobilevibe\.com$
(^|\.)bannedbook\.org$ (^|\.)bannedbook\.org$
(^|\.)bannednews\.org$ (^|\.)bannednews\.org$
(^|\.)banorte\.com$ (^|\.)banorte\.com$
...@@ -409,10 +440,10 @@ ...@@ -409,10 +440,10 @@
(^|\.)bb\.ttv\.com\.tw$ (^|\.)bb\.ttv\.com\.tw$
(^|\.)bbc\.co\.uk$ (^|\.)bbc\.co\.uk$
(^|\.)bbc\.com$ (^|\.)bbc\.com$
(^|\.)bbc\.com%2fzhongwen$
(^|\.)bbc\.in$ (^|\.)bbc\.in$
(^|\.)bbcchinese\.com$ (^|\.)bbcchinese\.com$
(^|\.)bbchat\.tv$ (^|\.)bbchat\.tv$
(^|\.)bbci\.co\.uk$
(^|\.)bbg\.gov$ (^|\.)bbg\.gov$
(^|\.)bbkz\.com$ (^|\.)bbkz\.com$
(^|\.)bbnradio\.org$ (^|\.)bbnradio\.org$
...@@ -425,6 +456,7 @@ ...@@ -425,6 +456,7 @@
(^|\.)bbs\.huasing\.org$ (^|\.)bbs\.huasing\.org$
(^|\.)bbs\.junglobal\.net$ (^|\.)bbs\.junglobal\.net$
(^|\.)bbs\.kimy\.com\.tw$ (^|\.)bbs\.kimy\.com\.tw$
(^|\.)bbs\.mikocon\.com$
(^|\.)bbs\.morbell\.com$ (^|\.)bbs\.morbell\.com$
(^|\.)bbs\.mychat\.to$ (^|\.)bbs\.mychat\.to$
(^|\.)bbs\.netbig\.com$ (^|\.)bbs\.netbig\.com$
...@@ -444,12 +476,14 @@ ...@@ -444,12 +476,14 @@
(^|\.)bcast\.co\.nz$ (^|\.)bcast\.co\.nz$
(^|\.)bcc\.com\.tw$ (^|\.)bcc\.com\.tw$
(^|\.)bcchinese\.net$ (^|\.)bcchinese\.net$
(^|\.)bcex\.ca$
(^|\.)bcmorning\.com$ (^|\.)bcmorning\.com$
(^|\.)bdsmvideos\.net$ (^|\.)bdsmvideos\.net$
(^|\.)beaconevents\.com$ (^|\.)beaconevents\.com$
(^|\.)bebo\.com$ (^|\.)bebo\.com$
(^|\.)beeg\.com$ (^|\.)beeg\.com$
(^|\.)beevpn\.com$ (^|\.)beevpn\.com$
(^|\.)behance\.net$
(^|\.)behindkink\.com$ (^|\.)behindkink\.com$
(^|\.)beijing1989\.com$ (^|\.)beijing1989\.com$
(^|\.)beijingspring\.com$ (^|\.)beijingspring\.com$
...@@ -469,7 +503,6 @@ ...@@ -469,7 +503,6 @@
(^|\.)bestvpnservice\.com$ (^|\.)bestvpnservice\.com$
(^|\.)bestvpnusa\.com$ (^|\.)bestvpnusa\.com$
(^|\.)bet365\.com$ (^|\.)bet365\.com$
(^|\.)beta\.usejump\.com$
(^|\.)betfair\.com$ (^|\.)betfair\.com$
(^|\.)betternet\.co$ (^|\.)betternet\.co$
(^|\.)bettervpn\.com$ (^|\.)bettervpn\.com$
...@@ -484,7 +517,9 @@ ...@@ -484,7 +517,9 @@
(^|\.)biantailajiao\.com$ (^|\.)biantailajiao\.com$
(^|\.)biantailajiao\.in$ (^|\.)biantailajiao\.in$
(^|\.)biblesforamerica\.org$ (^|\.)biblesforamerica\.org$
(^|\.)bibox\.com$
(^|\.)bic2011\.org$ (^|\.)bic2011\.org$
(^|\.)big\.one$
(^|\.)bigfools\.com$ (^|\.)bigfools\.com$
(^|\.)bigjapanesesex\.com$ (^|\.)bigjapanesesex\.com$
(^|\.)bigmoney\.biz$ (^|\.)bigmoney\.biz$
...@@ -492,12 +527,19 @@ ...@@ -492,12 +527,19 @@
(^|\.)bigsound\.org$ (^|\.)bigsound\.org$
(^|\.)biliworld\.com$ (^|\.)biliworld\.com$
(^|\.)billypan\.com$ (^|\.)billypan\.com$
(^|\.)binance\.com$
(^|\.)binux\.me$ (^|\.)binux\.me$
(^|\.)bipic\.net$ (^|\.)bipic\.net$
(^|\.)bird\.so$
(^|\.)bit-z\.com$
(^|\.)bit\.do$ (^|\.)bit\.do$
(^|\.)bit\.ly$ (^|\.)bit\.ly$
(^|\.)bitc\.bme\.emory\.edu$ (^|\.)bitc\.bme\.emory\.edu$
(^|\.)bitcointalk\.org$ (^|\.)bitcointalk\.org$
(^|\.)bitcoinworld\.com$
(^|\.)bitfinex\.com$
(^|\.)bithumb\.com$
(^|\.)bitinka\.com\.ar$
(^|\.)bitmex\.com$ (^|\.)bitmex\.com$
(^|\.)bitshare\.com$ (^|\.)bitshare\.com$
(^|\.)bitsnoop\.com$ (^|\.)bitsnoop\.com$
...@@ -560,7 +602,10 @@ ...@@ -560,7 +602,10 @@
(^|\.)blogs\.libraryinformationtechnology\.com$ (^|\.)blogs\.libraryinformationtechnology\.com$
(^|\.)blogs\.tampabay\.com$ (^|\.)blogs\.tampabay\.com$
(^|\.)blogs\.yahoo\.co\.jp$ (^|\.)blogs\.yahoo\.co\.jp$
(^|\.)blogspot(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}$ (^|\.)blogspot\.com$
(^|\.)blogspot\.hk$
(^|\.)blogspot\.jp$
(^|\.)blogspot\.tw$
(^|\.)blogtd\.net$ (^|\.)blogtd\.net$
(^|\.)blogtd\.org$ (^|\.)blogtd\.org$
(^|\.)bloodshed\.net$ (^|\.)bloodshed\.net$
...@@ -572,6 +617,7 @@ ...@@ -572,6 +617,7 @@
(^|\.)blueangellive\.com$ (^|\.)blueangellive\.com$
(^|\.)bmfinn\.com$ (^|\.)bmfinn\.com$
(^|\.)bnews\.co$ (^|\.)bnews\.co$
(^|\.)bnn\.co$
(^|\.)bnrmetal\.com$ (^|\.)bnrmetal\.com$
(^|\.)boardreader\.com$ (^|\.)boardreader\.com$
(^|\.)bod\.asia$ (^|\.)bod\.asia$
...@@ -587,6 +633,7 @@ ...@@ -587,6 +633,7 @@
(^|\.)book\.zi5\.me$ (^|\.)book\.zi5\.me$
(^|\.)bookepub\.com$ (^|\.)bookepub\.com$
(^|\.)books\.com\.tw$ (^|\.)books\.com\.tw$
(^|\.)booktopia\.com\.au$
(^|\.)boomssr\.com$ (^|\.)boomssr\.com$
(^|\.)bot\.nu$ (^|\.)bot\.nu$
(^|\.)botanwang\.com$ (^|\.)botanwang\.com$
...@@ -625,10 +672,14 @@ ...@@ -625,10 +672,14 @@
(^|\.)bt95\.com$ (^|\.)bt95\.com$
(^|\.)btaia\.com$ (^|\.)btaia\.com$
(^|\.)btbtav\.com$ (^|\.)btbtav\.com$
(^|\.)btc98\.com$
(^|\.)btcbank\.bank$
(^|\.)btctrade\.im$
(^|\.)btdigg\.org$ (^|\.)btdigg\.org$
(^|\.)btku\.me$ (^|\.)btku\.me$
(^|\.)btku\.org$ (^|\.)btku\.org$
(^|\.)btspread\.com$ (^|\.)btspread\.com$
(^|\.)btsynckeys\.com$
(^|\.)budaedu\.org$ (^|\.)budaedu\.org$
(^|\.)buddhanet\.com\.tw$ (^|\.)buddhanet\.com\.tw$
(^|\.)buddhistchannel\.tv$ (^|\.)buddhistchannel\.tv$
...@@ -647,13 +698,17 @@ ...@@ -647,13 +698,17 @@
(^|\.)buzzhand\.net$ (^|\.)buzzhand\.net$
(^|\.)buzzorange\.com$ (^|\.)buzzorange\.com$
(^|\.)bvpn\.com$ (^|\.)bvpn\.com$
(^|\.)bwgyhw\.com$
(^|\.)bwh1\.net$
(^|\.)bwsj\.hk$ (^|\.)bwsj\.hk$
(^|\.)bx\.in\.th$
(^|\.)bx\.tl$ (^|\.)bx\.tl$
(^|\.)bynet\.co\.il$ (^|\.)bynet\.co\.il$
(^|\.)c-est-simple\.com$ (^|\.)c-est-simple\.com$
(^|\.)c-spanvideo\.org$ (^|\.)c-spanvideo\.org$
(^|\.)c100tibet\.org$ (^|\.)c100tibet\.org$
(^|\.)c1522\.mooo\.com$ (^|\.)c1522\.mooo\.com$
(^|\.)c2cx\.com$
(^|\.)cablegatesearch\.net$ (^|\.)cablegatesearch\.net$
(^|\.)cachinese\.com$ (^|\.)cachinese\.com$
(^|\.)cacnw\.com$ (^|\.)cacnw\.com$
...@@ -693,6 +748,7 @@ ...@@ -693,6 +748,7 @@
(^|\.)casinoriva\.com$ (^|\.)casinoriva\.com$
(^|\.)castbox\.fm$ (^|\.)castbox\.fm$
(^|\.)catch22\.net$ (^|\.)catch22\.net$
(^|\.)catchgod\.com$
(^|\.)catfightpayperview\.xxx$ (^|\.)catfightpayperview\.xxx$
(^|\.)catholic\.org\.hk$ (^|\.)catholic\.org\.hk$
(^|\.)catholic\.org\.tw$ (^|\.)catholic\.org\.tw$
...@@ -711,6 +767,7 @@ ...@@ -711,6 +767,7 @@
(^|\.)cclife\.org$ (^|\.)cclife\.org$
(^|\.)cclifefl\.org$ (^|\.)cclifefl\.org$
(^|\.)ccthere\.com$ (^|\.)ccthere\.com$
(^|\.)ccthere\.net$
(^|\.)cctmweb\.net$ (^|\.)cctmweb\.net$
(^|\.)cctongbao\.com$ (^|\.)cctongbao\.com$
(^|\.)ccue\.ca$ (^|\.)ccue\.ca$
...@@ -728,6 +785,7 @@ ...@@ -728,6 +785,7 @@
(^|\.)cdn\.assets\.lfpcontent\.com$ (^|\.)cdn\.assets\.lfpcontent\.com$
(^|\.)cdn\.helixstudios\.net$ (^|\.)cdn\.helixstudios\.net$
(^|\.)cdn\.printfriendly\.com$ (^|\.)cdn\.printfriendly\.com$
(^|\.)cdn\.seatguru\.com$
(^|\.)cdn\.softlayer\.net$ (^|\.)cdn\.softlayer\.net$
(^|\.)cdn1\.lp\.saboom\.com$ (^|\.)cdn1\.lp\.saboom\.com$
(^|\.)cdnews\.com\.tw$ (^|\.)cdnews\.com\.tw$
...@@ -751,6 +809,7 @@ ...@@ -751,6 +809,7 @@
(^|\.)certificate-transparency\.org$ (^|\.)certificate-transparency\.org$
(^|\.)certificate\.revocationcheck\.com$ (^|\.)certificate\.revocationcheck\.com$
(^|\.)cfhks\.org\.hk$ (^|\.)cfhks\.org\.hk$
(^|\.)cfos\.de$
(^|\.)cftfc\.com$ (^|\.)cftfc\.com$
(^|\.)cgdepot\.org$ (^|\.)cgdepot\.org$
(^|\.)cgst\.edu$ (^|\.)cgst\.edu$
...@@ -762,6 +821,7 @@ ...@@ -762,6 +821,7 @@
(^|\.)changp\.com$ (^|\.)changp\.com$
(^|\.)changsa\.net$ (^|\.)changsa\.net$
(^|\.)channel8news\.sg$ (^|\.)channel8news\.sg$
(^|\.)chaoex\.com$
(^|\.)chapm25\.com$ (^|\.)chapm25\.com$
(^|\.)chatnook\.com$ (^|\.)chatnook\.com$
(^|\.)chaturbate\.com$ (^|\.)chaturbate\.com$
...@@ -824,6 +884,7 @@ ...@@ -824,6 +884,7 @@
(^|\.)chinatopsex\.com$ (^|\.)chinatopsex\.com$
(^|\.)chinatown\.com\.au$ (^|\.)chinatown\.com\.au$
(^|\.)chinatweeps\.com$ (^|\.)chinatweeps\.com$
(^|\.)chinaview\.wordpress\.com$
(^|\.)chinaway\.org$ (^|\.)chinaway\.org$
(^|\.)chinaworker\.info$ (^|\.)chinaworker\.info$
(^|\.)chinaxchina\.com$ (^|\.)chinaxchina\.com$
...@@ -841,6 +902,7 @@ ...@@ -841,6 +902,7 @@
(^|\.)chinesedemocracy\.com$ (^|\.)chinesedemocracy\.com$
(^|\.)chinesegay\.org$ (^|\.)chinesegay\.org$
(^|\.)chinesen\.de$ (^|\.)chinesen\.de$
(^|\.)chinesenews\.net\.au$
(^|\.)chinesepen\.org$ (^|\.)chinesepen\.org$
(^|\.)chinesetalks\.net$ (^|\.)chinesetalks\.net$
(^|\.)chineseupress\.com$ (^|\.)chineseupress\.com$
...@@ -848,6 +910,7 @@ ...@@ -848,6 +910,7 @@
(^|\.)chinman\.net$ (^|\.)chinman\.net$
(^|\.)chithu\.org$ (^|\.)chithu\.org$
(^|\.)chn\.chosun\.com$ (^|\.)chn\.chosun\.com$
(^|\.)chobit\.cc$
(^|\.)chrdnet\.com$ (^|\.)chrdnet\.com$
(^|\.)christianfreedom\.org$ (^|\.)christianfreedom\.org$
(^|\.)christianstudy\.com$ (^|\.)christianstudy\.com$
...@@ -870,6 +933,7 @@ ...@@ -870,6 +933,7 @@
(^|\.)cineastentreff\.de$ (^|\.)cineastentreff\.de$
(^|\.)cipfg\.org$ (^|\.)cipfg\.org$
(^|\.)circlethebayfortibet\.org$ (^|\.)circlethebayfortibet\.org$
(^|\.)cirosantilli\.com$
(^|\.)citizencn\.com$ (^|\.)citizencn\.com$
(^|\.)citizenlab\.org$ (^|\.)citizenlab\.org$
(^|\.)citizenscommission\.hk$ (^|\.)citizenscommission\.hk$
...@@ -893,19 +957,24 @@ ...@@ -893,19 +957,24 @@
(^|\.)cleansite\.info$ (^|\.)cleansite\.info$
(^|\.)cleansite\.us$ (^|\.)cleansite\.us$
(^|\.)clearharmony\.net$ (^|\.)clearharmony\.net$
(^|\.)clearsurance\.com$
(^|\.)clearwisdom\.net$ (^|\.)clearwisdom\.net$
(^|\.)clementine-player\.org$ (^|\.)clementine-player\.org$
(^|\.)cling\.omy\.sg$ (^|\.)cling\.omy\.sg$
(^|\.)clinica-tibet\.ru$ (^|\.)clinica-tibet\.ru$
(^|\.)clipfish\.de$ (^|\.)clipfish\.de$
(^|\.)cloakpoint\.com$ (^|\.)cloakpoint\.com$
(^|\.)cloud\.feedly\.com$
(^|\.)cloud\.mail\.ru$ (^|\.)cloud\.mail\.ru$
(^|\.)club1069\.com$ (^|\.)club1069\.com$
(^|\.)clyp\.it$
(^|\.)cmcn\.org$
(^|\.)cmi\.org\.tw$ (^|\.)cmi\.org\.tw$
(^|\.)cmp\.hku\.hk$ (^|\.)cmp\.hku\.hk$
(^|\.)cms\.gov$ (^|\.)cms\.gov$
(^|\.)cmule\.com$ (^|\.)cmule\.com$
(^|\.)cmule\.org$ (^|\.)cmule\.org$
(^|\.)cmx\.im$
(^|\.)cn-proxy\.com$ (^|\.)cn-proxy\.com$
(^|\.)cn\.calameo\.com$ (^|\.)cn\.calameo\.com$
(^|\.)cn\.dayabook\.com$ (^|\.)cn\.dayabook\.com$
...@@ -917,6 +986,7 @@ ...@@ -917,6 +986,7 @@
(^|\.)cn\.sandscotaicentral\.com$ (^|\.)cn\.sandscotaicentral\.com$
(^|\.)cn\.shafaqna\.com$ (^|\.)cn\.shafaqna\.com$
(^|\.)cn\.streetvoice\.com$ (^|\.)cn\.streetvoice\.com$
(^|\.)cn\.thegay\.com$
(^|\.)cn\.uncyclopedia\.wikia\.com$ (^|\.)cn\.uncyclopedia\.wikia\.com$
(^|\.)cn\.uptodown\.com$ (^|\.)cn\.uptodown\.com$
(^|\.)cn\.voa\.mobi$ (^|\.)cn\.voa\.mobi$
...@@ -934,11 +1004,21 @@ ...@@ -934,11 +1004,21 @@
(^|\.)cnproxy\.com$ (^|\.)cnproxy\.com$
(^|\.)co\.ng\.mil$ (^|\.)co\.ng\.mil$
(^|\.)coat\.co\.jp$ (^|\.)coat\.co\.jp$
(^|\.)cobinhood\.com$
(^|\.)cochina\.co$ (^|\.)cochina\.co$
(^|\.)cochina\.org$ (^|\.)cochina\.org$
(^|\.)code1984\.com$ (^|\.)code1984\.com$
(^|\.)codeshare\.io$ (^|\.)codeshare\.io$
(^|\.)codeskulptor\.org$ (^|\.)codeskulptor\.org$
(^|\.)coin2co\.in$
(^|\.)coinbene\.com$
(^|\.)coinegg\.com$
(^|\.)coinex\.com$
(^|\.)coingi\.com$
(^|\.)coinrail\.co\.kr$
(^|\.)cointiger\.com$
(^|\.)cointobe\.com$
(^|\.)coinut\.com$
(^|\.)collateralmurder\.com$ (^|\.)collateralmurder\.com$
(^|\.)collateralmurder\.org$ (^|\.)collateralmurder\.org$
(^|\.)com\.google$ (^|\.)com\.google$
...@@ -961,21 +1041,27 @@ ...@@ -961,21 +1041,27 @@
(^|\.)coolder\.com$ (^|\.)coolder\.com$
(^|\.)coolloud\.org\.tw$ (^|\.)coolloud\.org\.tw$
(^|\.)coolncute\.com$ (^|\.)coolncute\.com$
(^|\.)coolstuffinc\.com$
(^|\.)corumcollege\.com$ (^|\.)corumcollege\.com$
(^|\.)cos-moe\.com$ (^|\.)cos-moe\.com$
(^|\.)cosmic\.monar\.ch$ (^|\.)cosmic\.monar\.ch$
(^|\.)cosplayjav\.pl$ (^|\.)cosplayjav\.pl$
(^|\.)costco\.com$
(^|\.)cotweet\.com$ (^|\.)cotweet\.com$
(^|\.)counter\.social$
(^|\.)coursehero\.com$ (^|\.)coursehero\.com$
(^|\.)cpj\.org$ (^|\.)cpj\.org$
(^|\.)cq99\.us$ (^|\.)cq99\.us$
(^|\.)crackle\.com$ (^|\.)crackle\.com$
(^|\.)crazys\.cc$ (^|\.)crazys\.cc$
(^|\.)crazyshit\.com$
(^|\.)crbug\.com$
(^|\.)crchina\.org$ (^|\.)crchina\.org$
(^|\.)crd-net\.org$ (^|\.)crd-net\.org$
(^|\.)creaders\.net$ (^|\.)creaders\.net$
(^|\.)creadersnet\.com$ (^|\.)creadersnet\.com$
(^|\.)creativelab5\.com$ (^|\.)creativelab5\.com$
(^|\.)crisisresponse\.google$
(^|\.)cristyli\.com$ (^|\.)cristyli\.com$
(^|\.)crocotube\.com$ (^|\.)crocotube\.com$
(^|\.)crossfire\.co\.kr$ (^|\.)crossfire\.co\.kr$
...@@ -1000,6 +1086,7 @@ ...@@ -1000,6 +1086,7 @@
(^|\.)cumlouder\.com$ (^|\.)cumlouder\.com$
(^|\.)curvefish\.com$ (^|\.)curvefish\.com$
(^|\.)cusu\.hk$ (^|\.)cusu\.hk$
(^|\.)cutscenes\.net$
(^|\.)cw\.com\.tw$ (^|\.)cw\.com\.tw$
(^|\.)cyberghost\.natado\.com$ (^|\.)cyberghost\.natado\.com$
(^|\.)cyberghostvpn\.com$ (^|\.)cyberghostvpn\.com$
...@@ -1019,15 +1106,18 @@ ...@@ -1019,15 +1106,18 @@
(^|\.)dabr\.mobi$ (^|\.)dabr\.mobi$
(^|\.)dadazim\.com$ (^|\.)dadazim\.com$
(^|\.)dadi360\.com$ (^|\.)dadi360\.com$
(^|\.)dafabet\.com$
(^|\.)dafagood\.com$ (^|\.)dafagood\.com$
(^|\.)dafahao\.com$ (^|\.)dafahao\.com$
(^|\.)dafoh\.org$ (^|\.)dafoh\.org$
(^|\.)daftporn\.com$
(^|\.)dagelijksestandaard\.nl$ (^|\.)dagelijksestandaard\.nl$
(^|\.)daidostup\.ru$ (^|\.)daidostup\.ru$
(^|\.)dailidaili\.com$ (^|\.)dailidaili\.com$
(^|\.)dailymotion\.com$ (^|\.)dailymotion\.com$
(^|\.)dailynews\.sina\.com$ (^|\.)dailynews\.sina\.com$
(^|\.)dailynews\.sina\.com%2f$ (^|\.)dailynews\.sina\.com%2f$
(^|\.)dailyview\.tw$
(^|\.)daiphapinfo\.net$ (^|\.)daiphapinfo\.net$
(^|\.)dajiyuan\.com$ (^|\.)dajiyuan\.com$
(^|\.)dajiyuan\.de$ (^|\.)dajiyuan\.de$
...@@ -1115,6 +1205,7 @@ ...@@ -1115,6 +1205,7 @@
(^|\.)dhcp\.biz$ (^|\.)dhcp\.biz$
(^|\.)diaoyuislands\.org$ (^|\.)diaoyuislands\.org$
(^|\.)dictionary\.goo\.ne\.jp$ (^|\.)dictionary\.goo\.ne\.jp$
(^|\.)difangwenge\.org$
(^|\.)digiland\.tw$ (^|\.)digiland\.tw$
(^|\.)digisfera\.com$ (^|\.)digisfera\.com$
(^|\.)digitalnomadsproject\.org$ (^|\.)digitalnomadsproject\.org$
...@@ -1123,6 +1214,9 @@ ...@@ -1123,6 +1214,9 @@
(^|\.)dingchin\.com\.tw$ (^|\.)dingchin\.com\.tw$
(^|\.)dipity\.com$ (^|\.)dipity\.com$
(^|\.)directcreative\.com$ (^|\.)directcreative\.com$
(^|\.)discoins\.com$
(^|\.)discordapp\.com$
(^|\.)discordapp\.net$
(^|\.)discuss\.com\.hk$ (^|\.)discuss\.com\.hk$
(^|\.)discuss4u\.com$ (^|\.)discuss4u\.com$
(^|\.)dish\.com$ (^|\.)dish\.com$
...@@ -1173,6 +1267,8 @@ ...@@ -1173,6 +1267,8 @@
(^|\.)dotsub\.com$ (^|\.)dotsub\.com$
(^|\.)dotvpn\.com$ (^|\.)dotvpn\.com$
(^|\.)doub\.io$ (^|\.)doub\.io$
(^|\.)doubibackup\.com$
(^|\.)doubmirror\.cf$
(^|\.)dougscripts\.com$ (^|\.)dougscripts\.com$
(^|\.)douhokanko\.net$ (^|\.)douhokanko\.net$
(^|\.)doujincafe\.com$ (^|\.)doujincafe\.com$
...@@ -1183,6 +1279,7 @@ ...@@ -1183,6 +1279,7 @@
(^|\.)dphk\.org$ (^|\.)dphk\.org$
(^|\.)dpp\.org\.tw$ (^|\.)dpp\.org\.tw$
(^|\.)dpr\.info$ (^|\.)dpr\.info$
(^|\.)dragonex\.io$
(^|\.)dragonsprings\.org$ (^|\.)dragonsprings\.org$
(^|\.)dreamamateurs\.com$ (^|\.)dreamamateurs\.com$
(^|\.)drepung\.org$ (^|\.)drepung\.org$
...@@ -1218,6 +1315,7 @@ ...@@ -1218,6 +1315,7 @@
(^|\.)dupola\.com$ (^|\.)dupola\.com$
(^|\.)dupola\.net$ (^|\.)dupola\.net$
(^|\.)dushi\.ca$ (^|\.)dushi\.ca$
(^|\.)dvdpac\.com$
(^|\.)dvorak\.org$ (^|\.)dvorak\.org$
(^|\.)dw-world\.com$ (^|\.)dw-world\.com$
(^|\.)dw-world\.de$ (^|\.)dw-world\.de$
...@@ -1239,6 +1337,7 @@ ...@@ -1239,6 +1337,7 @@
(^|\.)dynu\.com$ (^|\.)dynu\.com$
(^|\.)dynu\.net$ (^|\.)dynu\.net$
(^|\.)dynupdate\.no-ip\.com$ (^|\.)dynupdate\.no-ip\.com$
(^|\.)dysfz\.cc$
(^|\.)dzze\.com$ (^|\.)dzze\.com$
(^|\.)e-classical\.com\.tw$ (^|\.)e-classical\.com\.tw$
(^|\.)e-gold\.com$ (^|\.)e-gold\.com$
...@@ -1263,6 +1362,7 @@ ...@@ -1263,6 +1362,7 @@
(^|\.)ebook\.hyread\.com\.tw$ (^|\.)ebook\.hyread\.com\.tw$
(^|\.)ebookbrowse\.com$ (^|\.)ebookbrowse\.com$
(^|\.)ebookee\.com$ (^|\.)ebookee\.com$
(^|\.)ebtcbank\.com$
(^|\.)ecfa\.org\.tw$ (^|\.)ecfa\.org\.tw$
(^|\.)echofon\.com$ (^|\.)echofon\.com$
(^|\.)ecimg\.tw$ (^|\.)ecimg\.tw$
...@@ -1290,6 +1390,7 @@ ...@@ -1290,6 +1390,7 @@
(^|\.)eksisozluk\.com$ (^|\.)eksisozluk\.com$
(^|\.)electionsmeter\.com$ (^|\.)electionsmeter\.com$
(^|\.)elgoog\.im$ (^|\.)elgoog\.im$
(^|\.)ellawine\.org$
(^|\.)elpais\.com$ (^|\.)elpais\.com$
(^|\.)eltondisney\.com$ (^|\.)eltondisney\.com$
(^|\.)emaga\.com$ (^|\.)emaga\.com$
...@@ -1353,9 +1454,11 @@ ...@@ -1353,9 +1454,11 @@
(^|\.)eroticsaloon\.net$ (^|\.)eroticsaloon\.net$
(^|\.)eslite\.com$ (^|\.)eslite\.com$
(^|\.)esmtp\.biz$ (^|\.)esmtp\.biz$
(^|\.)esurance\.com$
(^|\.)etaa\.org\.au$ (^|\.)etaa\.org\.au$
(^|\.)etadult\.com$ (^|\.)etadult\.com$
(^|\.)etaiwannews\.com$ (^|\.)etaiwannews\.com$
(^|\.)etherdelta\.com$
(^|\.)etizer\.org$ (^|\.)etizer\.org$
(^|\.)etokki\.com$ (^|\.)etokki\.com$
(^|\.)etools\.ncol\.com$ (^|\.)etools\.ncol\.com$
...@@ -1367,10 +1470,12 @@ ...@@ -1367,10 +1470,12 @@
(^|\.)eucasino\.com$ (^|\.)eucasino\.com$
(^|\.)eulam\.com$ (^|\.)eulam\.com$
(^|\.)eurekavpt\.com$ (^|\.)eurekavpt\.com$
(^|\.)euronews\.com$
(^|\.)evchk\.wikia\.com$ (^|\.)evchk\.wikia\.com$
(^|\.)evschool\.net$ (^|\.)evschool\.net$
(^|\.)exblog\.jp$ (^|\.)exblog\.jp$
(^|\.)exchristian\.hk$ (^|\.)exchristian\.hk$
(^|\.)exmo\.com$
(^|\.)exmormon\.org$ (^|\.)exmormon\.org$
(^|\.)expatshield\.com$ (^|\.)expatshield\.com$
(^|\.)expecthim\.com$ (^|\.)expecthim\.com$
...@@ -1378,8 +1483,10 @@ ...@@ -1378,8 +1483,10 @@
(^|\.)experts-univers\.com$ (^|\.)experts-univers\.com$
(^|\.)exploader\.net$ (^|\.)exploader\.net$
(^|\.)expressvpn\.com$ (^|\.)expressvpn\.com$
(^|\.)exrates\.me$
(^|\.)extmatrix\.com$ (^|\.)extmatrix\.com$
(^|\.)extremetube\.com$ (^|\.)extremetube\.com$
(^|\.)exx\.com$
(^|\.)eyevio\.jp$ (^|\.)eyevio\.jp$
(^|\.)eyny\.com$ (^|\.)eyny\.com$
(^|\.)ezpc\.tk$ (^|\.)ezpc\.tk$
...@@ -1393,6 +1500,7 @@ ...@@ -1393,6 +1500,7 @@
(^|\.)facebook\.in$ (^|\.)facebook\.in$
(^|\.)facebook\.nl$ (^|\.)facebook\.nl$
(^|\.)facebook\.se$ (^|\.)facebook\.se$
(^|\.)facebookmail\.com$
(^|\.)facebookquotes4u\.com$ (^|\.)facebookquotes4u\.com$
(^|\.)faceless\.me$ (^|\.)faceless\.me$
(^|\.)facesofnyfw\.com$ (^|\.)facesofnyfw\.com$
...@@ -1453,13 +1561,16 @@ ...@@ -1453,13 +1561,16 @@
(^|\.)fastpic\.ru$ (^|\.)fastpic\.ru$
(^|\.)fastssh\.com$ (^|\.)fastssh\.com$
(^|\.)faststone\.org$ (^|\.)faststone\.org$
(^|\.)fatbtc\.com$
(^|\.)favstar\.fm$ (^|\.)favstar\.fm$
(^|\.)fawanghuihui\.org$ (^|\.)fawanghuihui\.org$
(^|\.)faydao\.com$ (^|\.)faydao\.com$
(^|\.)fb\.com$ (^|\.)fb\.com$
(^|\.)fb\.me$ (^|\.)fb\.me$
(^|\.)fbaddins\.com$
(^|\.)fbcdn\.net$ (^|\.)fbcdn\.net$
(^|\.)fbsbx\.com$ (^|\.)fbsbx\.com$
(^|\.)fbworkmail\.com$
(^|\.)fc2\.com$ (^|\.)fc2\.com$
(^|\.)fc2blog\.net$ (^|\.)fc2blog\.net$
(^|\.)fc2china\.com$ (^|\.)fc2china\.com$
...@@ -1499,6 +1610,7 @@ ...@@ -1499,6 +1610,7 @@
(^|\.)finchvpn\.com$ (^|\.)finchvpn\.com$
(^|\.)findmespot\.com$ (^|\.)findmespot\.com$
(^|\.)findyoutube\.com$ (^|\.)findyoutube\.com$
(^|\.)findyoutube\.net$
(^|\.)fingerdaily\.com$ (^|\.)fingerdaily\.com$
(^|\.)finler\.net$ (^|\.)finler\.net$
(^|\.)firearmsworld\.net$ (^|\.)firearmsworld\.net$
...@@ -1521,6 +1633,10 @@ ...@@ -1521,6 +1633,10 @@
(^|\.)flitto\.com$ (^|\.)flitto\.com$
(^|\.)flnet\.org$ (^|\.)flnet\.org$
(^|\.)flog\.tw$ (^|\.)flog\.tw$
(^|\.)flyvpn\.com$
(^|\.)flyzy2005\.com$
(^|\.)fnac\.be$
(^|\.)fnac\.com$
(^|\.)fochk\.org$ (^|\.)fochk\.org$
(^|\.)focustaiwan\.tw$ (^|\.)focustaiwan\.tw$
(^|\.)focusvpn\.com$ (^|\.)focusvpn\.com$
...@@ -1529,6 +1645,7 @@ ...@@ -1529,6 +1645,7 @@
(^|\.)fofldfradio\.org$ (^|\.)fofldfradio\.org$
(^|\.)fooooo\.com$ (^|\.)fooooo\.com$
(^|\.)footwiball\.com$ (^|\.)footwiball\.com$
(^|\.)foreignpolicy\.com$
(^|\.)forum\.baby-kingdom\.com$ (^|\.)forum\.baby-kingdom\.com$
(^|\.)forum\.cyberctm\.com$ (^|\.)forum\.cyberctm\.com$
(^|\.)forum\.idsam\.com$ (^|\.)forum\.idsam\.com$
...@@ -1561,6 +1678,7 @@ ...@@ -1561,6 +1678,7 @@
(^|\.)free-gate\.org$ (^|\.)free-gate\.org$
(^|\.)free-hada-now\.org$ (^|\.)free-hada-now\.org$
(^|\.)free-proxy\.cz$ (^|\.)free-proxy\.cz$
(^|\.)free-ss\.site$
(^|\.)free-ssh\.com$ (^|\.)free-ssh\.com$
(^|\.)free\.fr$ (^|\.)free\.fr$
(^|\.)free4u\.com\.ar$ (^|\.)free4u\.com\.ar$
...@@ -1570,6 +1688,7 @@ ...@@ -1570,6 +1688,7 @@
(^|\.)freechina\.net$ (^|\.)freechina\.net$
(^|\.)freechina\.news$ (^|\.)freechina\.news$
(^|\.)freechinaforum\.org$ (^|\.)freechinaforum\.org$
(^|\.)freechinaweibo\.com$
(^|\.)freeddns\.com$ (^|\.)freeddns\.com$
(^|\.)freeddns\.org$ (^|\.)freeddns\.org$
(^|\.)freedomchina\.info$ (^|\.)freedomchina\.info$
...@@ -1657,6 +1776,7 @@ ...@@ -1657,6 +1776,7 @@
(^|\.)g-area\.org$ (^|\.)g-area\.org$
(^|\.)g-queen\.com$ (^|\.)g-queen\.com$
(^|\.)g\.co$ (^|\.)g\.co$
(^|\.)g0v\.social$
(^|\.)g6hentai\.com$ (^|\.)g6hentai\.com$
(^|\.)gabocorp\.com$ (^|\.)gabocorp\.com$
(^|\.)gaeproxy\.com$ (^|\.)gaeproxy\.com$
...@@ -1667,7 +1787,9 @@ ...@@ -1667,7 +1787,9 @@
(^|\.)game735\.com$ (^|\.)game735\.com$
(^|\.)gamebase\.com\.tw$ (^|\.)gamebase\.com\.tw$
(^|\.)gamejolt\.com$ (^|\.)gamejolt\.com$
(^|\.)gamer-cds\.cdn\.hinet\.net$
(^|\.)gamer\.com\.tw$ (^|\.)gamer\.com\.tw$
(^|\.)gamer2-cds\.cdn\.hinet\.net$
(^|\.)gamez\.com\.tw$ (^|\.)gamez\.com\.tw$
(^|\.)gamousa\.com$ (^|\.)gamousa\.com$
(^|\.)ganges\.com$ (^|\.)ganges\.com$
...@@ -1679,6 +1801,8 @@ ...@@ -1679,6 +1801,8 @@
(^|\.)gardennetworks\.org$ (^|\.)gardennetworks\.org$
(^|\.)gartlive\.com$ (^|\.)gartlive\.com$
(^|\.)gate-project\.com$ (^|\.)gate-project\.com$
(^|\.)gate\.io$
(^|\.)gatecoin\.com$
(^|\.)gather\.com$ (^|\.)gather\.com$
(^|\.)gatherproxy\.com$ (^|\.)gatherproxy\.com$
(^|\.)gati\.org\.tw$ (^|\.)gati\.org\.tw$
...@@ -1686,11 +1810,13 @@ ...@@ -1686,11 +1810,13 @@
(^|\.)gaycn\.net$ (^|\.)gaycn\.net$
(^|\.)gayhub\.com$ (^|\.)gayhub\.com$
(^|\.)gaymap\.cc$ (^|\.)gaymap\.cc$
(^|\.)gaymenring\.com$
(^|\.)gaytube\.com$ (^|\.)gaytube\.com$
(^|\.)gaywatch\.com$ (^|\.)gaywatch\.com$
(^|\.)gazotube\.com$ (^|\.)gazotube\.com$
(^|\.)gcc\.org\.hk$ (^|\.)gcc\.org\.hk$
(^|\.)gclooney\.com$ (^|\.)gclooney\.com$
(^|\.)gcmasia\.com$
(^|\.)gcpnews\.com$ (^|\.)gcpnews\.com$
(^|\.)gcr\.io$ (^|\.)gcr\.io$
(^|\.)gdbt\.net$ (^|\.)gdbt\.net$
...@@ -1704,7 +1830,9 @@ ...@@ -1704,7 +1830,9 @@
(^|\.)geocities\.com$ (^|\.)geocities\.com$
(^|\.)geocities\.jp$ (^|\.)geocities\.jp$
(^|\.)gerefoundation\.org$ (^|\.)gerefoundation\.org$
(^|\.)get\.app$
(^|\.)get\.how$ (^|\.)get\.how$
(^|\.)get\.page$
(^|\.)getastrill\.com$ (^|\.)getastrill\.com$
(^|\.)getchu\.com$ (^|\.)getchu\.com$
(^|\.)getcloak\.com$ (^|\.)getcloak\.com$
...@@ -1716,6 +1844,7 @@ ...@@ -1716,6 +1844,7 @@
(^|\.)getjetso\.com$ (^|\.)getjetso\.com$
(^|\.)getlantern\.org$ (^|\.)getlantern\.org$
(^|\.)getmdl\.io$ (^|\.)getmdl\.io$
(^|\.)getoutline\.org$
(^|\.)getsocialscope\.com$ (^|\.)getsocialscope\.com$
(^|\.)getsync\.com$ (^|\.)getsync\.com$
(^|\.)gettrials\.com$ (^|\.)gettrials\.com$
...@@ -1728,7 +1857,6 @@ ...@@ -1728,7 +1857,6 @@
(^|\.)gfw\.press$ (^|\.)gfw\.press$
(^|\.)ggpht\.com$ (^|\.)ggpht\.com$
(^|\.)ggssl\.com$ (^|\.)ggssl\.com$
(^|\.)ghost\.org$
(^|\.)ghostpath\.com$ (^|\.)ghostpath\.com$
(^|\.)ghut\.org$ (^|\.)ghut\.org$
(^|\.)giantessnight\.com$ (^|\.)giantessnight\.com$
...@@ -1737,6 +1865,7 @@ ...@@ -1737,6 +1865,7 @@
(^|\.)gigporno\.ru$ (^|\.)gigporno\.ru$
(^|\.)girlbanker\.com$ (^|\.)girlbanker\.com$
(^|\.)git\.io$ (^|\.)git\.io$
(^|\.)github\.com$
(^|\.)gizlen\.net$ (^|\.)gizlen\.net$
(^|\.)gjczz\.com$ (^|\.)gjczz\.com$
(^|\.)glass8\.eu$ (^|\.)glass8\.eu$
...@@ -1799,8 +1928,61 @@ ...@@ -1799,8 +1928,61 @@
(^|\.)goodtv\.com\.tw$ (^|\.)goodtv\.com\.tw$
(^|\.)goodtv\.tv$ (^|\.)goodtv\.tv$
(^|\.)goofind\.com$ (^|\.)goofind\.com$
(^|\.)google(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}$ (^|\.)google\.ae$
(^|\.)googleapis(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}$ (^|\.)google\.am$
(^|\.)google\.as$
(^|\.)google\.at$
(^|\.)google\.az$
(^|\.)google\.ba$
(^|\.)google\.be$
(^|\.)google\.bg$
(^|\.)google\.ca$
(^|\.)google\.calstate\.edu$
(^|\.)google\.cd$
(^|\.)google\.ci$
(^|\.)google\.co\.id$
(^|\.)google\.co\.jp$
(^|\.)google\.co\.kr$
(^|\.)google\.co\.ma$
(^|\.)google\.co\.uk$
(^|\.)google\.com$
(^|\.)google\.de$
(^|\.)google\.dj$
(^|\.)google\.dk$
(^|\.)google\.es$
(^|\.)google\.fi$
(^|\.)google\.fm$
(^|\.)google\.fr$
(^|\.)google\.gg$
(^|\.)google\.gl$
(^|\.)google\.gr$
(^|\.)google\.ie$
(^|\.)google\.is$
(^|\.)google\.it$
(^|\.)google\.jo$
(^|\.)google\.kz$
(^|\.)google\.lv$
(^|\.)google\.mn$
(^|\.)google\.ms$
(^|\.)google\.nl$
(^|\.)google\.no$
(^|\.)google\.nu$
(^|\.)google\.ro$
(^|\.)google\.ru$
(^|\.)google\.rw$
(^|\.)google\.sc$
(^|\.)google\.sh$
(^|\.)google\.sk$
(^|\.)google\.sm$
(^|\.)google\.sn$
(^|\.)google\.tk$
(^|\.)google\.tm$
(^|\.)google\.to$
(^|\.)google\.tt$
(^|\.)google\.vu$
(^|\.)google\.ws$
(^|\.)googleapis\.cn$
(^|\.)googleapis\.com$
(^|\.)googleapps\.com$ (^|\.)googleapps\.com$
(^|\.)googlearth\.com$ (^|\.)googlearth\.com$
(^|\.)googleartproject\.com$ (^|\.)googleartproject\.com$
...@@ -1831,6 +2013,7 @@ ...@@ -1831,6 +2013,7 @@
(^|\.)googlezip\.net$ (^|\.)googlezip\.net$
(^|\.)gopetition\.com$ (^|\.)gopetition\.com$
(^|\.)goproxing\.net$ (^|\.)goproxing\.net$
(^|\.)goregrish\.com$
(^|\.)gospelherald\.com$ (^|\.)gospelherald\.com$
(^|\.)got-game\.org$ (^|\.)got-game\.org$
(^|\.)gotdns\.ch$ (^|\.)gotdns\.ch$
...@@ -1864,8 +2047,11 @@ ...@@ -1864,8 +2047,11 @@
(^|\.)greenreadings\.com$ (^|\.)greenreadings\.com$
(^|\.)greenvpn\.net$ (^|\.)greenvpn\.net$
(^|\.)greenvpn\.org$ (^|\.)greenvpn\.org$
(^|\.)grotty-monday\.com$
(^|\.)groups\.google\.cn$ (^|\.)groups\.google\.cn$
(^|\.)grow\.google$
(^|\.)gs-discuss\.com$ (^|\.)gs-discuss\.com$
(^|\.)gsp\.target\.com$
(^|\.)gstatic\.com$ (^|\.)gstatic\.com$
(^|\.)gtricks\.com$ (^|\.)gtricks\.com$
(^|\.)gts-vpn\.com$ (^|\.)gts-vpn\.com$
...@@ -1873,6 +2059,7 @@ ...@@ -1873,6 +2059,7 @@
(^|\.)guaguass\.com$ (^|\.)guaguass\.com$
(^|\.)guaguass\.org$ (^|\.)guaguass\.org$
(^|\.)guancha\.org$ (^|\.)guancha\.org$
(^|\.)guaneryu\.com$
(^|\.)guangming\.com\.my$ (^|\.)guangming\.com\.my$
(^|\.)guardster\.com$ (^|\.)guardster\.com$
(^|\.)guishan\.org$ (^|\.)guishan\.org$
...@@ -1907,6 +2094,7 @@ ...@@ -1907,6 +2094,7 @@
(^|\.)hacken\.cc$ (^|\.)hacken\.cc$
(^|\.)hacker\.org$ (^|\.)hacker\.org$
(^|\.)hackthatphone\.net$ (^|\.)hackthatphone\.net$
(^|\.)hahaxixi\.github\.io$
(^|\.)hahlo\.com$ (^|\.)hahlo\.com$
(^|\.)hakkatv\.org\.tw$ (^|\.)hakkatv\.org\.tw$
(^|\.)handcraftedsoftware\.org$ (^|\.)handcraftedsoftware\.org$
...@@ -1920,12 +2108,15 @@ ...@@ -1920,12 +2108,15 @@
(^|\.)hautelook\.com$ (^|\.)hautelook\.com$
(^|\.)hautelookcdn\.com$ (^|\.)hautelookcdn\.com$
(^|\.)have8\.com$ (^|\.)have8\.com$
(^|\.)hbg\.com$
(^|\.)hbo\.com$
(^|\.)hclips\.com$ (^|\.)hclips\.com$
(^|\.)hd\.stheadline\.com$ (^|\.)hd\.stheadline\.com$
(^|\.)hdlt\.me$ (^|\.)hdlt\.me$
(^|\.)hdtvb\.net$ (^|\.)hdtvb\.net$
(^|\.)hdzog\.com$ (^|\.)hdzog\.com$
(^|\.)heartyit\.com$ (^|\.)heartyit\.com$
(^|\.)heavy-r\.com$
(^|\.)hec\.su$ (^|\.)hec\.su$
(^|\.)hecaitou\.net$ (^|\.)hecaitou\.net$
(^|\.)hechaji\.com$ (^|\.)hechaji\.com$
...@@ -1977,6 +2168,7 @@ ...@@ -1977,6 +2168,7 @@
(^|\.)himalayanglacier\.com$ (^|\.)himalayanglacier\.com$
(^|\.)himemix\.com$ (^|\.)himemix\.com$
(^|\.)himemix\.net$ (^|\.)himemix\.net$
(^|\.)hitbtc\.com$
(^|\.)hitomi\.la$ (^|\.)hitomi\.la$
(^|\.)hiwifi\.com$ (^|\.)hiwifi\.com$
(^|\.)hizb-ut-tahrir\.info$ (^|\.)hizb-ut-tahrir\.info$
...@@ -2020,6 +2212,7 @@ ...@@ -2020,6 +2212,7 @@
(^|\.)hkfaa\.com$ (^|\.)hkfaa\.com$
(^|\.)hkfreezone\.com$ (^|\.)hkfreezone\.com$
(^|\.)hkfront\.org$ (^|\.)hkfront\.org$
(^|\.)hkgalden\.com$
(^|\.)hkgolden\.com$ (^|\.)hkgolden\.com$
(^|\.)hkgreenradio\.org$ (^|\.)hkgreenradio\.org$
(^|\.)hkheadline\.com$ (^|\.)hkheadline\.com$
...@@ -2040,6 +2233,7 @@ ...@@ -2040,6 +2233,7 @@
(^|\.)hkwcc\.org\.hk$ (^|\.)hkwcc\.org\.hk$
(^|\.)hkzone\.org$ (^|\.)hkzone\.org$
(^|\.)hmonghot\.com$ (^|\.)hmonghot\.com$
(^|\.)hmv\.co\.jp$
(^|\.)hmvdigital\.ca$ (^|\.)hmvdigital\.ca$
(^|\.)hmvdigital\.com$ (^|\.)hmvdigital\.com$
(^|\.)hnjhj\.com$ (^|\.)hnjhj\.com$
...@@ -2073,6 +2267,7 @@ ...@@ -2073,6 +2267,7 @@
(^|\.)hotvpn\.com$ (^|\.)hotvpn\.com$
(^|\.)hougaige\.com$ (^|\.)hougaige\.com$
(^|\.)howtoforge\.com$ (^|\.)howtoforge\.com$
(^|\.)hoxx\.com$
(^|\.)hpa\.gov\.tw$ (^|\.)hpa\.gov\.tw$
(^|\.)hqcdp\.org$ (^|\.)hqcdp\.org$
(^|\.)hqjapanesesex\.com$ (^|\.)hqjapanesesex\.com$
...@@ -2083,6 +2278,7 @@ ...@@ -2083,6 +2278,7 @@
(^|\.)hrcir\.com$ (^|\.)hrcir\.com$
(^|\.)hrea\.org$ (^|\.)hrea\.org$
(^|\.)hrichina\.org$ (^|\.)hrichina\.org$
(^|\.)hrtsea\.com$
(^|\.)hrw\.org$ (^|\.)hrw\.org$
(^|\.)hrweb\.org$ (^|\.)hrweb\.org$
(^|\.)hsjp\.net$ (^|\.)hsjp\.net$
...@@ -2109,6 +2305,7 @@ ...@@ -2109,6 +2305,7 @@
(^|\.)huaxin\.ph$ (^|\.)huaxin\.ph$
(^|\.)huayuworld\.org$ (^|\.)huayuworld\.org$
(^|\.)hudatoriq\.web\.id$ (^|\.)hudatoriq\.web\.id$
(^|\.)hudson\.org$
(^|\.)huffingtonpost\.com$ (^|\.)huffingtonpost\.com$
(^|\.)hugoroy\.eu$ (^|\.)hugoroy\.eu$
(^|\.)huhaitai\.com$ (^|\.)huhaitai\.com$
...@@ -2193,6 +2390,7 @@ ...@@ -2193,6 +2390,7 @@
(^|\.)ihakka\.net$ (^|\.)ihakka\.net$
(^|\.)ihao\.org$ (^|\.)ihao\.org$
(^|\.)iicns\.com$ (^|\.)iicns\.com$
(^|\.)iipdigital\.usembassy\.gov$
(^|\.)ikstar\.com$ (^|\.)ikstar\.com$
(^|\.)ikwb\.com$ (^|\.)ikwb\.com$
(^|\.)illusionfactory\.com$ (^|\.)illusionfactory\.com$
...@@ -2240,16 +2438,19 @@ ...@@ -2240,16 +2438,19 @@
(^|\.)instagram\.com$ (^|\.)instagram\.com$
(^|\.)instanthq\.com$ (^|\.)instanthq\.com$
(^|\.)institut-tibetain\.org$ (^|\.)institut-tibetain\.org$
(^|\.)international-news\.newsmagazine\.asia$
(^|\.)internet\.org$ (^|\.)internet\.org$
(^|\.)internetdefenseleague\.org$ (^|\.)internetdefenseleague\.org$
(^|\.)internetfreedom\.org$ (^|\.)internetfreedom\.org$
(^|\.)internetpopculture\.com$ (^|\.)internetpopculture\.com$
(^|\.)inthenameofconfuciusmovie\.com$
(^|\.)investigating\.wordpress\.com$ (^|\.)investigating\.wordpress\.com$
(^|\.)inxian\.com$ (^|\.)inxian\.com$
(^|\.)iownyour\.biz$ (^|\.)iownyour\.biz$
(^|\.)iownyour\.org$ (^|\.)iownyour\.org$
(^|\.)ipalter\.com$ (^|\.)ipalter\.com$
(^|\.)ipfire\.org$ (^|\.)ipfire\.org$
(^|\.)ipfs\.io$
(^|\.)iphone4hongkong\.com$ (^|\.)iphone4hongkong\.com$
(^|\.)iphonehacks\.com$ (^|\.)iphonehacks\.com$
(^|\.)iphonetaiwan\.org$ (^|\.)iphonetaiwan\.org$
...@@ -2272,6 +2473,7 @@ ...@@ -2272,6 +2473,7 @@
(^|\.)is\.gd$ (^|\.)is\.gd$
(^|\.)isaacmao\.com$ (^|\.)isaacmao\.com$
(^|\.)isasecret\.com$ (^|\.)isasecret\.com$
(^|\.)isc\.sans\.edu$
(^|\.)isgreat\.org$ (^|\.)isgreat\.org$
(^|\.)islahhaber\.net$ (^|\.)islahhaber\.net$
(^|\.)islam\.org\.hk$ (^|\.)islam\.org\.hk$
...@@ -2322,6 +2524,7 @@ ...@@ -2322,6 +2524,7 @@
(^|\.)janwongphoto\.com$ (^|\.)janwongphoto\.com$
(^|\.)japan-whores\.com$ (^|\.)japan-whores\.com$
(^|\.)japanfirst\.asianfreeforum\.com$ (^|\.)japanfirst\.asianfreeforum\.com$
(^|\.)japantimes\.co\.jp$
(^|\.)jav\.com$ (^|\.)jav\.com$
(^|\.)jav101\.com$ (^|\.)jav101\.com$
(^|\.)jav2be\.com$ (^|\.)jav2be\.com$
...@@ -2347,6 +2550,7 @@ ...@@ -2347,6 +2550,7 @@
(^|\.)jdwsy\.com$ (^|\.)jdwsy\.com$
(^|\.)jeanyim\.com$ (^|\.)jeanyim\.com$
(^|\.)jetos\.com$ (^|\.)jetos\.com$
(^|\.)jex\.com$
(^|\.)jfqu36\.club$ (^|\.)jfqu36\.club$
(^|\.)jfqu37\.xyz$ (^|\.)jfqu37\.xyz$
(^|\.)jgoodies\.com$ (^|\.)jgoodies\.com$
...@@ -2365,6 +2569,8 @@ ...@@ -2365,6 +2569,8 @@
(^|\.)jingsim\.org$ (^|\.)jingsim\.org$
(^|\.)jinpianwang\.com$ (^|\.)jinpianwang\.com$
(^|\.)jinroukong\.com$ (^|\.)jinroukong\.com$
(^|\.)jintian\.net$
(^|\.)jinx\.com$
(^|\.)jitouch\.com$ (^|\.)jitouch\.com$
(^|\.)jizzthis\.com$ (^|\.)jizzthis\.com$
(^|\.)jjgirls\.com$ (^|\.)jjgirls\.com$
...@@ -2376,6 +2582,7 @@ ...@@ -2376,6 +2582,7 @@
(^|\.)joachims\.org$ (^|\.)joachims\.org$
(^|\.)jobnewera\.wordpress\.com$ (^|\.)jobnewera\.wordpress\.com$
(^|\.)jobso\.tv$ (^|\.)jobso\.tv$
(^|\.)joinmastodon\.org$
(^|\.)journalchretien\.net$ (^|\.)journalchretien\.net$
(^|\.)journalofdemocracy\.org$ (^|\.)journalofdemocracy\.org$
(^|\.)joymiihub\.com$ (^|\.)joymiihub\.com$
...@@ -2383,6 +2590,7 @@ ...@@ -2383,6 +2590,7 @@
(^|\.)jp\.hao123\.com$ (^|\.)jp\.hao123\.com$
(^|\.)jpl\.nasa\.gov$ (^|\.)jpl\.nasa\.gov$
(^|\.)jpopforum\.net$ (^|\.)jpopforum\.net$
(^|\.)jtvnw\.net$
(^|\.)jubushoushen\.com$ (^|\.)jubushoushen\.com$
(^|\.)juhuaren\.com$ (^|\.)juhuaren\.com$
(^|\.)jukujo-club\.com$ (^|\.)jukujo-club\.com$
...@@ -2425,6 +2633,7 @@ ...@@ -2425,6 +2633,7 @@
(^|\.)karmapa-teachings\.org$ (^|\.)karmapa-teachings\.org$
(^|\.)karmapa\.org$ (^|\.)karmapa\.org$
(^|\.)kawaiikawaii\.jp$ (^|\.)kawaiikawaii\.jp$
(^|\.)kawase\.com$
(^|\.)kb\.monitorware\.com$ (^|\.)kb\.monitorware\.com$
(^|\.)kba-tx\.org$ (^|\.)kba-tx\.org$
(^|\.)kcoolonline\.com$ (^|\.)kcoolonline\.com$
...@@ -2437,6 +2646,7 @@ ...@@ -2437,6 +2646,7 @@
(^|\.)kenengba\.com$ (^|\.)kenengba\.com$
(^|\.)keontech\.net$ (^|\.)keontech\.net$
(^|\.)kepard\.com$ (^|\.)kepard\.com$
(^|\.)kex\.com$
(^|\.)keycdn\.com$ (^|\.)keycdn\.com$
(^|\.)khabdha\.org$ (^|\.)khabdha\.org$
(^|\.)khatrimaza\.org$ (^|\.)khatrimaza\.org$
...@@ -2472,7 +2682,9 @@ ...@@ -2472,7 +2682,9 @@
(^|\.)korenan2\.com$ (^|\.)korenan2\.com$
(^|\.)ksdl\.org$ (^|\.)ksdl\.org$
(^|\.)ksnews\.com\.tw$ (^|\.)ksnews\.com\.tw$
(^|\.)kspcoin\.com$
(^|\.)ktzhk\.com$ (^|\.)ktzhk\.com$
(^|\.)kucoin\.com$
(^|\.)kui\.name$ (^|\.)kui\.name$
(^|\.)kun\.im$ (^|\.)kun\.im$
(^|\.)kurashsultan\.com$ (^|\.)kurashsultan\.com$
...@@ -2508,6 +2720,7 @@ ...@@ -2508,6 +2720,7 @@
(^|\.)lastfm\.es$ (^|\.)lastfm\.es$
(^|\.)latelinenews\.com$ (^|\.)latelinenews\.com$
(^|\.)latibet\.org$ (^|\.)latibet\.org$
(^|\.)lbank\.info$
(^|\.)ld\.hao123img\.com$ (^|\.)ld\.hao123img\.com$
(^|\.)le-vpn\.com$ (^|\.)le-vpn\.com$
(^|\.)leafyvpn\.net$ (^|\.)leafyvpn\.net$
...@@ -2536,6 +2749,7 @@ ...@@ -2536,6 +2749,7 @@
(^|\.)lhakar\.org$ (^|\.)lhakar\.org$
(^|\.)lhasocialwork\.org$ (^|\.)lhasocialwork\.org$
(^|\.)liangyou\.net$ (^|\.)liangyou\.net$
(^|\.)liangzhichuanmei\.com$
(^|\.)lianyue\.net$ (^|\.)lianyue\.net$
(^|\.)liaowangxizang\.net$ (^|\.)liaowangxizang\.net$
(^|\.)liberal\.org\.hk$ (^|\.)liberal\.org\.hk$
...@@ -2545,12 +2759,14 @@ ...@@ -2545,12 +2759,14 @@
(^|\.)lifemiles\.com$ (^|\.)lifemiles\.com$
(^|\.)lighten\.org\.tw$ (^|\.)lighten\.org\.tw$
(^|\.)lightnovel\.cn$ (^|\.)lightnovel\.cn$
(^|\.)lihkg\.com$
(^|\.)like\.com$ (^|\.)like\.com$
(^|\.)limiao\.net$ (^|\.)limiao\.net$
(^|\.)line-apps\.com$ (^|\.)line-apps\.com$
(^|\.)line-scdn\.net$ (^|\.)line-scdn\.net$
(^|\.)line\.me$ (^|\.)line\.me$
(^|\.)line\.naver\.jp$ (^|\.)line\.naver\.jp$
(^|\.)linear-abematv\.akamaized\.net$
(^|\.)linglingfa\.com$ (^|\.)linglingfa\.com$
(^|\.)lingvodics\.com$ (^|\.)lingvodics\.com$
(^|\.)link-o-rama\.com$ (^|\.)link-o-rama\.com$
...@@ -2564,11 +2780,13 @@ ...@@ -2564,11 +2780,13 @@
(^|\.)listentoyoutube\.com$ (^|\.)listentoyoutube\.com$
(^|\.)listorious\.com$ (^|\.)listorious\.com$
(^|\.)lists\.w3\.org$ (^|\.)lists\.w3\.org$
(^|\.)liu-xiaobo\.org$
(^|\.)liudejun\.com$ (^|\.)liudejun\.com$
(^|\.)liuhanyu\.com$ (^|\.)liuhanyu\.com$
(^|\.)liujianshu\.com$ (^|\.)liujianshu\.com$
(^|\.)liuxiaobo\.net$ (^|\.)liuxiaobo\.net$
(^|\.)liuxiaotong\.com$ (^|\.)liuxiaotong\.com$
(^|\.)livecoin\.net$
(^|\.)livedoor\.jp$ (^|\.)livedoor\.jp$
(^|\.)liveleak\.com$ (^|\.)liveleak\.com$
(^|\.)livestation\.com$ (^|\.)livestation\.com$
...@@ -2582,10 +2800,12 @@ ...@@ -2582,10 +2800,12 @@
(^|\.)llss\.me$ (^|\.)llss\.me$
(^|\.)load\.to$ (^|\.)load\.to$
(^|\.)lobsangwangyal\.com$ (^|\.)lobsangwangyal\.com$
(^|\.)localbitcoins\.com$
(^|\.)localdomain\.ws$ (^|\.)localdomain\.ws$
(^|\.)localpresshk\.com$ (^|\.)localpresshk\.com$
(^|\.)lockestek\.com$ (^|\.)lockestek\.com$
(^|\.)logbot\.net$ (^|\.)logbot\.net$
(^|\.)login\.target\.com$
(^|\.)logiqx\.com$ (^|\.)logiqx\.com$
(^|\.)londonchinese\.ca$ (^|\.)londonchinese\.ca$
(^|\.)longhair\.hk$ (^|\.)longhair\.hk$
...@@ -2630,6 +2850,7 @@ ...@@ -2630,6 +2850,7 @@
(^|\.)mad-ar\.ch$ (^|\.)mad-ar\.ch$
(^|\.)madewithcode\.com$ (^|\.)madewithcode\.com$
(^|\.)madonna-av\.com$ (^|\.)madonna-av\.com$
(^|\.)madrau\.com$
(^|\.)madthumbs\.com$ (^|\.)madthumbs\.com$
(^|\.)magazines\.sina\.com\.tw$ (^|\.)magazines\.sina\.com\.tw$
(^|\.)magic-net\.info$ (^|\.)magic-net\.info$
...@@ -2664,6 +2885,9 @@ ...@@ -2664,6 +2885,9 @@
(^|\.)marxists\.org$ (^|\.)marxists\.org$
(^|\.)mash\.to$ (^|\.)mash\.to$
(^|\.)maskedip\.com$ (^|\.)maskedip\.com$
(^|\.)mastodon\.cloud$
(^|\.)mastodon\.host$
(^|\.)mastodon\.social$
(^|\.)matainja\.com$ (^|\.)matainja\.com$
(^|\.)material\.io$ (^|\.)material\.io$
(^|\.)mathable\.io$ (^|\.)mathable\.io$
...@@ -2671,6 +2895,7 @@ ...@@ -2671,6 +2895,7 @@
(^|\.)matome-plus\.com$ (^|\.)matome-plus\.com$
(^|\.)matome-plus\.net$ (^|\.)matome-plus\.net$
(^|\.)matsushimakaede\.com$ (^|\.)matsushimakaede\.com$
(^|\.)matters\.news$
(^|\.)mattwilcox\.net$ (^|\.)mattwilcox\.net$
(^|\.)maturejp\.com$ (^|\.)maturejp\.com$
(^|\.)maxing\.jp$ (^|\.)maxing\.jp$
...@@ -2680,6 +2905,7 @@ ...@@ -2680,6 +2905,7 @@
(^|\.)mcfog\.com$ (^|\.)mcfog\.com$
(^|\.)mcreasite\.com$ (^|\.)mcreasite\.com$
(^|\.)md-t\.org$ (^|\.)md-t\.org$
(^|\.)me\.me$
(^|\.)me\.youthwant\.com\.tw$ (^|\.)me\.youthwant\.com\.tw$
(^|\.)meansys\.com$ (^|\.)meansys\.com$
(^|\.)media\.nu\.nl$ (^|\.)media\.nu\.nl$
...@@ -2698,14 +2924,15 @@ ...@@ -2698,14 +2924,15 @@
(^|\.)megavideo\.com$ (^|\.)megavideo\.com$
(^|\.)megurineluka\.com$ (^|\.)megurineluka\.com$
(^|\.)meirixiaochao\.com$ (^|\.)meirixiaochao\.com$
(^|\.)melon-peach\.com$
(^|\.)meltoday\.com$ (^|\.)meltoday\.com$
(^|\.)meme\.yahoo\.com$ (^|\.)meme\.yahoo\.com$
(^|\.)memehk\.com$ (^|\.)memehk\.com$
(^|\.)memorybbs\.com$ (^|\.)memorybbs\.com$
(^|\.)memri\.org$ (^|\.)memri\.org$
(^|\.)memrijttm\.org$ (^|\.)memrijttm\.org$
(^|\.)mercatox\.com$
(^|\.)mercyprophet\.org$ (^|\.)mercyprophet\.org$
(^|\.)mergersandinquisitions\.org$
(^|\.)meridian-trust\.org$ (^|\.)meridian-trust\.org$
(^|\.)meripet\.biz$ (^|\.)meripet\.biz$
(^|\.)meripet\.com$ (^|\.)meripet\.com$
...@@ -2714,6 +2941,7 @@ ...@@ -2714,6 +2941,7 @@
(^|\.)mesotw\.com$ (^|\.)mesotw\.com$
(^|\.)messenger\.com$ (^|\.)messenger\.com$
(^|\.)metacafe\.com$ (^|\.)metacafe\.com$
(^|\.)metart\.com$
(^|\.)metarthunter\.com$ (^|\.)metarthunter\.com$
(^|\.)meteorshowersonline\.com$ (^|\.)meteorshowersonline\.com$
(^|\.)metrohk\.com\.hk$ (^|\.)metrohk\.com\.hk$
...@@ -2779,6 +3007,7 @@ ...@@ -2779,6 +3007,7 @@
(^|\.)mjlsh\.usc\.cuhk\.edu\.hk$ (^|\.)mjlsh\.usc\.cuhk\.edu\.hk$
(^|\.)mk5000\.com$ (^|\.)mk5000\.com$
(^|\.)mlcool\.com$ (^|\.)mlcool\.com$
(^|\.)mlzs\.work$
(^|\.)mm-cg\.com$ (^|\.)mm-cg\.com$
(^|\.)mmaaxx\.com$ (^|\.)mmaaxx\.com$
(^|\.)mmmca\.com$ (^|\.)mmmca\.com$
...@@ -2791,9 +3020,13 @@ ...@@ -2791,9 +3020,13 @@
(^|\.)mobypicture\.com$ (^|\.)mobypicture\.com$
(^|\.)moeaic\.gov\.tw$ (^|\.)moeaic\.gov\.tw$
(^|\.)moeerolibrary\.com$ (^|\.)moeerolibrary\.com$
(^|\.)mofa\.gov\.tw$
(^|\.)mofaxiehui\.com$ (^|\.)mofaxiehui\.com$
(^|\.)mofos\.com$ (^|\.)mofos\.com$
(^|\.)mog\.com$ (^|\.)mog\.com$
(^|\.)mohu\.club$
(^|\.)mohu\.ml$
(^|\.)mojim\.com$
(^|\.)mol\.gov\.tw$ (^|\.)mol\.gov\.tw$
(^|\.)molihua\.org$ (^|\.)molihua\.org$
(^|\.)mondex\.org$ (^|\.)mondex\.org$
...@@ -2803,10 +3036,12 @@ ...@@ -2803,10 +3036,12 @@
(^|\.)monster\.com$ (^|\.)monster\.com$
(^|\.)moodyz\.com$ (^|\.)moodyz\.com$
(^|\.)moonbbs\.com$ (^|\.)moonbbs\.com$
(^|\.)moonbingo\.com$
(^|\.)morningsun\.org$ (^|\.)morningsun\.org$
(^|\.)moroneta\.com$ (^|\.)moroneta\.com$
(^|\.)mos\.ru$ (^|\.)mos\.ru$
(^|\.)motherless\.com$ (^|\.)motherless\.com$
(^|\.)motiyun\.com$
(^|\.)motor4ik\.ru$ (^|\.)motor4ik\.ru$
(^|\.)mousebreaker\.com$ (^|\.)mousebreaker\.com$
(^|\.)movements\.org$ (^|\.)movements\.org$
...@@ -2823,6 +3058,7 @@ ...@@ -2823,6 +3058,7 @@
(^|\.)mrface\.com$ (^|\.)mrface\.com$
(^|\.)mrslove\.com$ (^|\.)mrslove\.com$
(^|\.)mrtweet\.com$ (^|\.)mrtweet\.com$
(^|\.)msa-it\.org$
(^|\.)msguancha\.com$ (^|\.)msguancha\.com$
(^|\.)msha\.gov$ (^|\.)msha\.gov$
(^|\.)mswe1\.org$ (^|\.)mswe1\.org$
...@@ -2957,7 +3193,6 @@ ...@@ -2957,7 +3193,6 @@
(^|\.)newipnow\.com$ (^|\.)newipnow\.com$
(^|\.)newlandmagazine\.com\.au$ (^|\.)newlandmagazine\.com\.au$
(^|\.)newnews\.ca$ (^|\.)newnews\.ca$
(^|\.)news\.bbc\.co\.uk$
(^|\.)news\.cnyes\.com$ (^|\.)news\.cnyes\.com$
(^|\.)news\.hk\.msn\.com$ (^|\.)news\.hk\.msn\.com$
(^|\.)news\.hkpeanut\.com$ (^|\.)news\.hkpeanut\.com$
...@@ -2980,7 +3215,6 @@ ...@@ -2980,7 +3215,6 @@
(^|\.)newscn\.org$ (^|\.)newscn\.org$
(^|\.)newsdetox\.ca$ (^|\.)newsdetox\.ca$
(^|\.)newsdh\.com$ (^|\.)newsdh\.com$
(^|\.)newsforums\.bbc\.co\.uk$
(^|\.)newspeak\.cc$ (^|\.)newspeak\.cc$
(^|\.)newstamago\.com$ (^|\.)newstamago\.com$
(^|\.)newstapa\.org$ (^|\.)newstapa\.org$
...@@ -3017,6 +3251,7 @@ ...@@ -3017,6 +3251,7 @@
(^|\.)ninjaproxy\.ninja$ (^|\.)ninjaproxy\.ninja$
(^|\.)nintendium\.com$ (^|\.)nintendium\.com$
(^|\.)ninth\.biz$ (^|\.)ninth\.biz$
(^|\.)niu\.moe$
(^|\.)niusnews\.com$ (^|\.)niusnews\.com$
(^|\.)njactb\.org$ (^|\.)njactb\.org$
(^|\.)njuice\.com$ (^|\.)njuice\.com$
...@@ -3026,6 +3261,7 @@ ...@@ -3026,6 +3261,7 @@
(^|\.)nobel\.se$ (^|\.)nobel\.se$
(^|\.)nobelprize\.org$ (^|\.)nobelprize\.org$
(^|\.)nobodycanstop\.us$ (^|\.)nobodycanstop\.us$
(^|\.)nofile\.io$
(^|\.)nokogiri\.org$ (^|\.)nokogiri\.org$
(^|\.)nokola\.com$ (^|\.)nokola\.com$
(^|\.)noodlevpn\.com$ (^|\.)noodlevpn\.com$
...@@ -3034,8 +3270,10 @@ ...@@ -3034,8 +3270,10 @@
(^|\.)nordstromimage\.com$ (^|\.)nordstromimage\.com$
(^|\.)nordstromrack\.com$ (^|\.)nordstromrack\.com$
(^|\.)nordvpn\.com$ (^|\.)nordvpn\.com$
(^|\.)notify\.dropboxapi\.com$
(^|\.)nottinghampost\.com$ (^|\.)nottinghampost\.com$
(^|\.)novelasia\.com$ (^|\.)novelasia\.com$
(^|\.)now\.com$
(^|\.)now\.im$ (^|\.)now\.im$
(^|\.)nownews\.com$ (^|\.)nownews\.com$
(^|\.)nowtorrents\.com$ (^|\.)nowtorrents\.com$
...@@ -3044,6 +3282,7 @@ ...@@ -3044,6 +3282,7 @@
(^|\.)npa\.gov\.tw$ (^|\.)npa\.gov\.tw$
(^|\.)npnt\.me$ (^|\.)npnt\.me$
(^|\.)nps\.gov$ (^|\.)nps\.gov$
(^|\.)npsboost\.com$
(^|\.)nradio\.me$ (^|\.)nradio\.me$
(^|\.)nrk\.no$ (^|\.)nrk\.no$
(^|\.)ns01\.biz$ (^|\.)ns01\.biz$
...@@ -3085,7 +3324,6 @@ ...@@ -3085,7 +3324,6 @@
(^|\.)ny\.stgloballink\.com$ (^|\.)ny\.stgloballink\.com$
(^|\.)ny\.visiontimes\.com$ (^|\.)ny\.visiontimes\.com$
(^|\.)nyaa\.eu$ (^|\.)nyaa\.eu$
(^|\.)nyaa\.si$
(^|\.)nydus\.ca$ (^|\.)nydus\.ca$
(^|\.)nylon-angel\.com$ (^|\.)nylon-angel\.com$
(^|\.)nylonstockingsonline\.com$ (^|\.)nylonstockingsonline\.com$
...@@ -3110,6 +3348,7 @@ ...@@ -3110,6 +3348,7 @@
(^|\.)october-review\.org$ (^|\.)october-review\.org$
(^|\.)oculus\.com$ (^|\.)oculus\.com$
(^|\.)oculuscdn\.com$ (^|\.)oculuscdn\.com$
(^|\.)oex\.com$
(^|\.)offbeatchina\.com$ (^|\.)offbeatchina\.com$
(^|\.)officeoftibet\.com$ (^|\.)officeoftibet\.com$
(^|\.)ofile\.org$ (^|\.)ofile\.org$
...@@ -3120,6 +3359,7 @@ ...@@ -3120,6 +3359,7 @@
(^|\.)oizoblog\.com$ (^|\.)oizoblog\.com$
(^|\.)ok\.ru$ (^|\.)ok\.ru$
(^|\.)okayfreedom\.com$ (^|\.)okayfreedom\.com$
(^|\.)okex\.com$
(^|\.)okk\.tw$ (^|\.)okk\.tw$
(^|\.)old-cat\.net$ (^|\.)old-cat\.net$
(^|\.)old\.honeynet\.org$ (^|\.)old\.honeynet\.org$
...@@ -3135,6 +3375,7 @@ ...@@ -3135,6 +3375,7 @@
(^|\.)onapp\.com$ (^|\.)onapp\.com$
(^|\.)onedrive\.live\.com$ (^|\.)onedrive\.live\.com$
(^|\.)onedumb\.com$ (^|\.)onedumb\.com$
(^|\.)onejav\.com$
(^|\.)onion\.city$ (^|\.)onion\.city$
(^|\.)online\.recoveryversion\.org$ (^|\.)online\.recoveryversion\.org$
(^|\.)onlinecha\.com$ (^|\.)onlinecha\.com$
...@@ -3158,9 +3399,11 @@ ...@@ -3158,9 +3399,11 @@
(^|\.)openid\.net$ (^|\.)openid\.net$
(^|\.)openleaks\.org$ (^|\.)openleaks\.org$
(^|\.)openvpn\.net$ (^|\.)openvpn\.net$
(^|\.)openvpn\.org$
(^|\.)openwebster\.com$ (^|\.)openwebster\.com$
(^|\.)openwrt\.org\.cn$ (^|\.)openwrt\.org\.cn$
(^|\.)opml\.radiotime\.com$ (^|\.)opml\.radiotime\.com$
(^|\.)opus-gaming\.com$
(^|\.)organcare\.org\.tw$ (^|\.)organcare\.org\.tw$
(^|\.)organharvestinvestigation\.net$ (^|\.)organharvestinvestigation\.net$
(^|\.)organiccrap\.com$ (^|\.)organiccrap\.com$
...@@ -3171,6 +3414,7 @@ ...@@ -3171,6 +3414,7 @@
(^|\.)orn\.jp$ (^|\.)orn\.jp$
(^|\.)orzistic\.org$ (^|\.)orzistic\.org$
(^|\.)osfoora\.com$ (^|\.)osfoora\.com$
(^|\.)otcbtc\.com$
(^|\.)otnd\.org$ (^|\.)otnd\.org$
(^|\.)otto\.de$ (^|\.)otto\.de$
(^|\.)otzo\.com$ (^|\.)otzo\.com$
...@@ -3203,14 +3447,12 @@ ...@@ -3203,14 +3447,12 @@
(^|\.)paldengyal\.com$ (^|\.)paldengyal\.com$
(^|\.)paljorpublications\.com$ (^|\.)paljorpublications\.com$
(^|\.)paltalk\.com$ (^|\.)paltalk\.com$
(^|\.)panacom\.co\.jp$
(^|\.)panamapapers\.sueddeutsche\.de$ (^|\.)panamapapers\.sueddeutsche\.de$
(^|\.)pandapow\.co$ (^|\.)pandapow\.co$
(^|\.)pandapow\.net$ (^|\.)pandapow\.net$
(^|\.)pandavpn-jp\.com$ (^|\.)pandavpn-jp\.com$
(^|\.)pandora\.com$ (^|\.)pandora\.com$
(^|\.)pandora\.tv$ (^|\.)pandora\.tv$
(^|\.)pangci\.net$
(^|\.)panluan\.net$ (^|\.)panluan\.net$
(^|\.)panoramio\.com$ (^|\.)panoramio\.com$
(^|\.)pao-pao\.net$ (^|\.)pao-pao\.net$
...@@ -3223,6 +3465,7 @@ ...@@ -3223,6 +3465,7 @@
(^|\.)partypoker\.com$ (^|\.)partypoker\.com$
(^|\.)passion\.com$ (^|\.)passion\.com$
(^|\.)passiontimes\.hk$ (^|\.)passiontimes\.hk$
(^|\.)paste\.ee$
(^|\.)pastebin\.com$ (^|\.)pastebin\.com$
(^|\.)pastie\.org$ (^|\.)pastie\.org$
(^|\.)pbs\.org$ (^|\.)pbs\.org$
...@@ -3283,6 +3526,7 @@ ...@@ -3283,6 +3526,7 @@
(^|\.)picturesocial\.com$ (^|\.)picturesocial\.com$
(^|\.)pin-cong\.com$ (^|\.)pin-cong\.com$
(^|\.)pin6\.com$ (^|\.)pin6\.com$
(^|\.)pincong\.rocks$
(^|\.)ping\.fm$ (^|\.)ping\.fm$
(^|\.)pinimg\.com$ (^|\.)pinimg\.com$
(^|\.)pinkrod\.com$ (^|\.)pinkrod\.com$
...@@ -3306,6 +3550,7 @@ ...@@ -3306,6 +3550,7 @@
(^|\.)pixiv\.net$ (^|\.)pixiv\.net$
(^|\.)pixnet\.net$ (^|\.)pixnet\.net$
(^|\.)pk\.com$ (^|\.)pk\.com$
(^|\.)pki\.goog$
(^|\.)placemix\.com$ (^|\.)placemix\.com$
(^|\.)playboy\.com$ (^|\.)playboy\.com$
(^|\.)playboyplus\.com$ (^|\.)playboyplus\.com$
...@@ -3315,6 +3560,7 @@ ...@@ -3315,6 +3560,7 @@
(^|\.)plays\.com\.tw$ (^|\.)plays\.com\.tw$
(^|\.)plm\.org\.hk$ (^|\.)plm\.org\.hk$
(^|\.)plunder\.com$ (^|\.)plunder\.com$
(^|\.)plurk\.com$
(^|\.)plus28\.com$ (^|\.)plus28\.com$
(^|\.)plusbb\.com$ (^|\.)plusbb\.com$
(^|\.)pmatehunter\.com$ (^|\.)pmatehunter\.com$
...@@ -3322,9 +3568,11 @@ ...@@ -3322,9 +3568,11 @@
(^|\.)po2b\.com$ (^|\.)po2b\.com$
(^|\.)pobieramy\.top$ (^|\.)pobieramy\.top$
(^|\.)podictionary\.com$ (^|\.)podictionary\.com$
(^|\.)pokerstars\.com$
(^|\.)pokerstars\.net$ (^|\.)pokerstars\.net$
(^|\.)politicalchina\.org$ (^|\.)politicalchina\.org$
(^|\.)politicalconsultation\.org$ (^|\.)politicalconsultation\.org$
(^|\.)politiscales\.net$
(^|\.)poloniex\.com$ (^|\.)poloniex\.com$
(^|\.)polymer-project\.org$ (^|\.)polymer-project\.org$
(^|\.)polymerhk\.com$ (^|\.)polymerhk\.com$
...@@ -3366,9 +3614,11 @@ ...@@ -3366,9 +3614,11 @@
(^|\.)powerphoto\.org$ (^|\.)powerphoto\.org$
(^|\.)prayforchina\.net$ (^|\.)prayforchina\.net$
(^|\.)premeforwindows7\.com$ (^|\.)premeforwindows7\.com$
(^|\.)premproxy\.com$
(^|\.)presentationzen\.com$ (^|\.)presentationzen\.com$
(^|\.)presidentlee\.tw$ (^|\.)presidentlee\.tw$
(^|\.)prestige-av\.com$ (^|\.)prestige-av\.com$
(^|\.)pride\.google$
(^|\.)prisoner-state-secret-journal-premier$ (^|\.)prisoner-state-secret-journal-premier$
(^|\.)prisoneralert\.com$ (^|\.)prisoneralert\.com$
(^|\.)pritunl\.com$ (^|\.)pritunl\.com$
...@@ -3380,6 +3630,7 @@ ...@@ -3380,6 +3630,7 @@
(^|\.)privatevpn\.com$ (^|\.)privatevpn\.com$
(^|\.)procopytips\.com$ (^|\.)procopytips\.com$
(^|\.)prosiben\.de$ (^|\.)prosiben\.de$
(^|\.)protonvpn\.com$
(^|\.)provideocoalition\.com$ (^|\.)provideocoalition\.com$
(^|\.)provpnaccounts\.com$ (^|\.)provpnaccounts\.com$
(^|\.)proxfree\.com$ (^|\.)proxfree\.com$
...@@ -3427,6 +3678,7 @@ ...@@ -3427,6 +3678,7 @@
(^|\.)python\.com$ (^|\.)python\.com$
(^|\.)python\.com\.tw$ (^|\.)python\.com\.tw$
(^|\.)pythonhackers\.com$ (^|\.)pythonhackers\.com$
(^|\.)pytorch\.org$
(^|\.)q%3dfreedom$ (^|\.)q%3dfreedom$
(^|\.)q%3dtriangle$ (^|\.)q%3dtriangle$
(^|\.)q=freedom$ (^|\.)q=freedom$
...@@ -3454,6 +3706,7 @@ ...@@ -3454,6 +3706,7 @@
(^|\.)quitccp\.net$ (^|\.)quitccp\.net$
(^|\.)quitccp\.org$ (^|\.)quitccp\.org$
(^|\.)quora\.com$ (^|\.)quora\.com$
(^|\.)quoracdn\.net$
(^|\.)quran\.com$ (^|\.)quran\.com$
(^|\.)quranexplorer\.com$ (^|\.)quranexplorer\.com$
(^|\.)qusi8\.net$ (^|\.)qusi8\.net$
...@@ -3488,6 +3741,7 @@ ...@@ -3488,6 +3741,7 @@
(^|\.)rawgit\.com$ (^|\.)rawgit\.com$
(^|\.)rawgithub\.com$ (^|\.)rawgithub\.com$
(^|\.)razyboard\.com$ (^|\.)razyboard\.com$
(^|\.)rcam\.target\.com$
(^|\.)rcinet\.ca$ (^|\.)rcinet\.ca$
(^|\.)rconversation\.blogs\.com$ (^|\.)rconversation\.blogs\.com$
(^|\.)rd\.com$ (^|\.)rd\.com$
...@@ -3510,6 +3764,11 @@ ...@@ -3510,6 +3764,11 @@
(^|\.)redballoonsolidarity\.org$ (^|\.)redballoonsolidarity\.org$
(^|\.)redchinacn\.net$ (^|\.)redchinacn\.net$
(^|\.)redchinacn\.org$ (^|\.)redchinacn\.org$
(^|\.)redd\.it$
(^|\.)reddit\.com$
(^|\.)redditlist\.com$
(^|\.)redditmedia\.com$
(^|\.)redditstatic\.com$
(^|\.)redhotlabs\.com$ (^|\.)redhotlabs\.com$
(^|\.)redtube\.com$ (^|\.)redtube\.com$
(^|\.)referer\.us$ (^|\.)referer\.us$
...@@ -3533,16 +3792,21 @@ ...@@ -3533,16 +3792,21 @@
(^|\.)revver\.com$ (^|\.)revver\.com$
(^|\.)rfa\.org$ (^|\.)rfa\.org$
(^|\.)rfachina\.com$ (^|\.)rfachina\.com$
(^|\.)rfalive1\.akacast\.akamaistream\.net$
(^|\.)rfamobile\.org$ (^|\.)rfamobile\.org$
(^|\.)rfaweb\.org$ (^|\.)rfaweb\.org$
(^|\.)rferl\.org$ (^|\.)rferl\.org$
(^|\.)rfi\.fr$ (^|\.)rfi\.fr$
(^|\.)rfi\.my$ (^|\.)rfi\.my$
(^|\.)rg3\.github\.io$
(^|\.)rightbtc\.com$
(^|\.)rigpa\.org$ (^|\.)rigpa\.org$
(^|\.)riku\.me$ (^|\.)riku\.me$
(^|\.)rileyguide\.com$ (^|\.)rileyguide\.com$
(^|\.)ritouki\.jp$ (^|\.)ritouki\.jp$
(^|\.)ritter\.vg$ (^|\.)ritter\.vg$
(^|\.)rixcloud\.com$
(^|\.)rixcloud\.us$
(^|\.)rlwlw\.com$ (^|\.)rlwlw\.com$
(^|\.)rmjdw\.com$ (^|\.)rmjdw\.com$
(^|\.)rmjdw132\.info$ (^|\.)rmjdw132\.info$
...@@ -3557,6 +3821,7 @@ ...@@ -3557,6 +3821,7 @@
(^|\.)roodo\.com$ (^|\.)roodo\.com$
(^|\.)rosechina\.net$ (^|\.)rosechina\.net$
(^|\.)rotten\.com$ (^|\.)rotten\.com$
(^|\.)rsdlmonitor\.com$
(^|\.)rsf-chinese\.org$ (^|\.)rsf-chinese\.org$
(^|\.)rsf\.org$ (^|\.)rsf\.org$
(^|\.)rsgamen\.org$ (^|\.)rsgamen\.org$
...@@ -3564,10 +3829,12 @@ ...@@ -3564,10 +3829,12 @@
(^|\.)rtalabel\.org$ (^|\.)rtalabel\.org$
(^|\.)rthk\.hk$ (^|\.)rthk\.hk$
(^|\.)rthk\.org\.hk$ (^|\.)rthk\.org\.hk$
(^|\.)rthklive2-lh\.akamaihd\.net$
(^|\.)rti\.org\.tw$ (^|\.)rti\.org\.tw$
(^|\.)rtycminnesota\.org$ (^|\.)rtycminnesota\.org$
(^|\.)ruanyifeng\.com$ (^|\.)ruanyifeng\.com$
(^|\.)rukor\.org$ (^|\.)rukor\.org$
(^|\.)runbtx\.com$
(^|\.)rushbee\.com$ (^|\.)rushbee\.com$
(^|\.)ruten\.com\.tw$ (^|\.)ruten\.com\.tw$
(^|\.)rutube\.ru$ (^|\.)rutube\.ru$
...@@ -3587,6 +3854,7 @@ ...@@ -3587,6 +3854,7 @@
(^|\.)sadistic-v\.com$ (^|\.)sadistic-v\.com$
(^|\.)sadpanda\.us$ (^|\.)sadpanda\.us$
(^|\.)safervpn\.com$ (^|\.)safervpn\.com$
(^|\.)safety\.google$
(^|\.)saintyculture\.com$ (^|\.)saintyculture\.com$
(^|\.)saiq\.me$ (^|\.)saiq\.me$
(^|\.)sakuralive\.com$ (^|\.)sakuralive\.com$
...@@ -3599,6 +3867,7 @@ ...@@ -3599,6 +3867,7 @@
(^|\.)saveliuxiaobo\.com$ (^|\.)saveliuxiaobo\.com$
(^|\.)savemedia\.com$ (^|\.)savemedia\.com$
(^|\.)savethedate\.foo$ (^|\.)savethedate\.foo$
(^|\.)savethesounds\.info$
(^|\.)savetibet\.de$ (^|\.)savetibet\.de$
(^|\.)savetibet\.fr$ (^|\.)savetibet\.fr$
(^|\.)savetibet\.nl$ (^|\.)savetibet\.nl$
...@@ -3609,6 +3878,9 @@ ...@@ -3609,6 +3878,9 @@
(^|\.)say2\.info$ (^|\.)say2\.info$
(^|\.)sbme\.me$ (^|\.)sbme\.me$
(^|\.)sbs\.com\.au$ (^|\.)sbs\.com\.au$
(^|\.)scache\.vzw\.com$
(^|\.)scache1\.vzw\.com$
(^|\.)scache2\.vzw\.com$
(^|\.)scasino\.com$ (^|\.)scasino\.com$
(^|\.)schema\.org$ (^|\.)schema\.org$
(^|\.)sciencenets\.com$ (^|\.)sciencenets\.com$
...@@ -3622,6 +3894,7 @@ ...@@ -3622,6 +3894,7 @@
(^|\.)search$ (^|\.)search$
(^|\.)search\.aol\.com$ (^|\.)search\.aol\.com$
(^|\.)search\.yahoo\.co\.jp$ (^|\.)search\.yahoo\.co\.jp$
(^|\.)search\.yahoo\.com$
(^|\.)searchtruth\.com$ (^|\.)searchtruth\.com$
(^|\.)secretchina\.com$ (^|\.)secretchina\.com$
(^|\.)secretgarden\.no$ (^|\.)secretgarden\.no$
...@@ -3630,6 +3903,7 @@ ...@@ -3630,6 +3903,7 @@
(^|\.)secure\.logmein\.com$ (^|\.)secure\.logmein\.com$
(^|\.)secure\.raxcdn\.com$ (^|\.)secure\.raxcdn\.com$
(^|\.)securetunnel\.com$ (^|\.)securetunnel\.com$
(^|\.)securityinabox\.org$
(^|\.)securitykiss\.com$ (^|\.)securitykiss\.com$
(^|\.)seed4\.me$ (^|\.)seed4\.me$
(^|\.)seesmic\.com$ (^|\.)seesmic\.com$
...@@ -3669,6 +3943,7 @@ ...@@ -3669,6 +3943,7 @@
(^|\.)shadowsky\.xyz$ (^|\.)shadowsky\.xyz$
(^|\.)shadowsocks-r\.com$ (^|\.)shadowsocks-r\.com$
(^|\.)shadowsocks\.asia$ (^|\.)shadowsocks\.asia$
(^|\.)shadowsocks\.be$
(^|\.)shadowsocks\.com$ (^|\.)shadowsocks\.com$
(^|\.)shadowsocks\.com\.hk$ (^|\.)shadowsocks\.com\.hk$
(^|\.)shadowsocks\.org$ (^|\.)shadowsocks\.org$
...@@ -3720,6 +3995,7 @@ ...@@ -3720,6 +3995,7 @@
(^|\.)sierrafriendsoftibet\.org$ (^|\.)sierrafriendsoftibet\.org$
(^|\.)sijihuisuo\.club$ (^|\.)sijihuisuo\.club$
(^|\.)sijihuisuo\.com$ (^|\.)sijihuisuo\.com$
(^|\.)sikaozhe1997\.github\.io$
(^|\.)silkbook\.com$ (^|\.)silkbook\.com$
(^|\.)simbolostwitter\.com$ (^|\.)simbolostwitter\.com$
(^|\.)simplecd\.org$ (^|\.)simplecd\.org$
...@@ -3742,11 +4018,13 @@ ...@@ -3742,11 +4018,13 @@
(^|\.)sis\.xxx$ (^|\.)sis\.xxx$
(^|\.)sis001\.com$ (^|\.)sis001\.com$
(^|\.)sis001\.us$ (^|\.)sis001\.us$
(^|\.)site2unblock\.com$
(^|\.)site90\.net$ (^|\.)site90\.net$
(^|\.)sitebro\.tw$ (^|\.)sitebro\.tw$
(^|\.)sitekreator\.com$ (^|\.)sitekreator\.com$
(^|\.)siteks\.uk\.to$ (^|\.)siteks\.uk\.to$
(^|\.)sitemaps\.org$ (^|\.)sitemaps\.org$
(^|\.)six-degrees\.io$
(^|\.)sixth\.biz$ (^|\.)sixth\.biz$
(^|\.)sjrt\.org$ (^|\.)sjrt\.org$
(^|\.)sjum\.cn$ (^|\.)sjum\.cn$
...@@ -3783,6 +4061,7 @@ ...@@ -3783,6 +4061,7 @@
(^|\.)sobees\.com$ (^|\.)sobees\.com$
(^|\.)soc\.mil$ (^|\.)soc\.mil$
(^|\.)socialwhale\.com$ (^|\.)socialwhale\.com$
(^|\.)socks-proxy\.net$
(^|\.)sockscap64\.com$ (^|\.)sockscap64\.com$
(^|\.)sockslist\.net$ (^|\.)sockslist\.net$
(^|\.)socrec\.org$ (^|\.)socrec\.org$
...@@ -3791,6 +4070,7 @@ ...@@ -3791,6 +4070,7 @@
(^|\.)softether-download\.com$ (^|\.)softether-download\.com$
(^|\.)softether\.co\.jp$ (^|\.)softether\.co\.jp$
(^|\.)softether\.org$ (^|\.)softether\.org$
(^|\.)softsmirror\.cf$
(^|\.)softwarebychuck\.com$ (^|\.)softwarebychuck\.com$
(^|\.)softwaredownload\.gitbooks\.io$ (^|\.)softwaredownload\.gitbooks\.io$
(^|\.)sogclub\.com$ (^|\.)sogclub\.com$
...@@ -3827,6 +4107,8 @@ ...@@ -3827,6 +4107,8 @@
(^|\.)sourcewadio\.com$ (^|\.)sourcewadio\.com$
(^|\.)southnews\.com\.tw$ (^|\.)southnews\.com\.tw$
(^|\.)sowers\.org\.hk$ (^|\.)sowers\.org\.hk$
(^|\.)soylentnews\.org$
(^|\.)spaces\.hightail\.com$
(^|\.)spankbang\.com$ (^|\.)spankbang\.com$
(^|\.)spankingtube\.com$ (^|\.)spankingtube\.com$
(^|\.)spankwire\.com$ (^|\.)spankwire\.com$
...@@ -3836,6 +4118,7 @@ ...@@ -3836,6 +4118,7 @@
(^|\.)speedify\.com$ (^|\.)speedify\.com$
(^|\.)spem\.at$ (^|\.)spem\.at$
(^|\.)spencertipping\.com$ (^|\.)spencertipping\.com$
(^|\.)spendee\.com$
(^|\.)spicevpn\.com$ (^|\.)spicevpn\.com$
(^|\.)spideroak\.com$ (^|\.)spideroak\.com$
(^|\.)spike\.com$ (^|\.)spike\.com$
...@@ -3854,12 +4137,17 @@ ...@@ -3854,12 +4137,17 @@
(^|\.)ss-link\.com$ (^|\.)ss-link\.com$
(^|\.)ss\.carryzhou\.com$ (^|\.)ss\.carryzhou\.com$
(^|\.)ss\.levyhsu\.com$ (^|\.)ss\.levyhsu\.com$
(^|\.)ss\.pythonic\.life$
(^|\.)ss7\.vzw\.com$
(^|\.)ssglobal\.co$ (^|\.)ssglobal\.co$
(^|\.)ssglobal\.me$ (^|\.)ssglobal\.me$
(^|\.)ssh91\.com$ (^|\.)ssh91\.com$
(^|\.)ssl\.webpack\.de$ (^|\.)ssl\.webpack\.de$
(^|\.)ssl443\.org$ (^|\.)ssl443\.org$
(^|\.)sspanel\.net$
(^|\.)sspro\.ml$ (^|\.)sspro\.ml$
(^|\.)ssr\.tools$
(^|\.)ssrshare\.com$
(^|\.)sss\.camp$ (^|\.)sss\.camp$
(^|\.)sstmlt\.moe$ (^|\.)sstmlt\.moe$
(^|\.)sstmlt\.net$ (^|\.)sstmlt\.net$
...@@ -3874,6 +4162,7 @@ ...@@ -3874,6 +4162,7 @@
(^|\.)stat\.gov\.tw$ (^|\.)stat\.gov\.tw$
(^|\.)static-economist\.com$ (^|\.)static-economist\.com$
(^|\.)static\.comico\.tw$ (^|\.)static\.comico\.tw$
(^|\.)static\.shemalez\.com$
(^|\.)static01\.nyt\.com$ (^|\.)static01\.nyt\.com$
(^|\.)staticflickr\.com$ (^|\.)staticflickr\.com$
(^|\.)statueofdemocracy\.org$ (^|\.)statueofdemocracy\.org$
...@@ -3916,6 +4205,7 @@ ...@@ -3916,6 +4205,7 @@
(^|\.)sugumiru18\.com$ (^|\.)sugumiru18\.com$
(^|\.)suissl\.com$ (^|\.)suissl\.com$
(^|\.)sujiatun\.wordpress\.com$ (^|\.)sujiatun\.wordpress\.com$
(^|\.)sukebei\.nyaa\.si$
(^|\.)sulian\.me$ (^|\.)sulian\.me$
(^|\.)summify\.com$ (^|\.)summify\.com$
(^|\.)sumrando\.com$ (^|\.)sumrando\.com$
...@@ -3927,19 +4217,23 @@ ...@@ -3927,19 +4217,23 @@
(^|\.)sunvpn\.net$ (^|\.)sunvpn\.net$
(^|\.)sunwinism\.joinbbs\.net$ (^|\.)sunwinism\.joinbbs\.net$
(^|\.)suoluo\.org$ (^|\.)suoluo\.org$
(^|\.)supchina\.com$
(^|\.)superfreevpn\.com$ (^|\.)superfreevpn\.com$
(^|\.)superokayama\.com$ (^|\.)superokayama\.com$
(^|\.)superpages\.com$ (^|\.)superpages\.com$
(^|\.)supervpn\.net$ (^|\.)supervpn\.net$
(^|\.)superzooi\.com$
(^|\.)suppig\.net$ (^|\.)suppig\.net$
(^|\.)suprememastertv\.com$ (^|\.)suprememastertv\.com$
(^|\.)surfeasy\.com$ (^|\.)surfeasy\.com$
(^|\.)surfeasy\.com\.au$ (^|\.)surfeasy\.com\.au$
(^|\.)suroot\.com$ (^|\.)suroot\.com$
(^|\.)surrenderat20\.net$ (^|\.)surrenderat20\.net$
(^|\.)sustainability\.google$
(^|\.)suyangg\.com$ (^|\.)suyangg\.com$
(^|\.)svsfx\.com$ (^|\.)svsfx\.com$
(^|\.)swagbucks\.com$ (^|\.)swagbucks\.com$
(^|\.)swissinfo\.ch$
(^|\.)swissvpn\.net$ (^|\.)swissvpn\.net$
(^|\.)switch1\.jp$ (^|\.)switch1\.jp$
(^|\.)switchvpn\.net$ (^|\.)switchvpn\.net$
...@@ -3977,6 +4271,7 @@ ...@@ -3977,6 +4271,7 @@
(^|\.)taiwandc\.org$ (^|\.)taiwandc\.org$
(^|\.)taiwanjobs\.gov\.tw$ (^|\.)taiwanjobs\.gov\.tw$
(^|\.)taiwanjustice\.com$ (^|\.)taiwanjustice\.com$
(^|\.)taiwanjustice\.net$
(^|\.)taiwankiss\.com$ (^|\.)taiwankiss\.com$
(^|\.)taiwannation\.50webs\.com$ (^|\.)taiwannation\.50webs\.com$
(^|\.)taiwannation\.com$ (^|\.)taiwannation\.com$
...@@ -3990,6 +4285,7 @@ ...@@ -3990,6 +4285,7 @@
(^|\.)taiwanyes\.ning\.com$ (^|\.)taiwanyes\.ning\.com$
(^|\.)talk853\.com$ (^|\.)talk853\.com$
(^|\.)talkboxapp\.com$ (^|\.)talkboxapp\.com$
(^|\.)talkcc\.com$
(^|\.)talkonly\.net$ (^|\.)talkonly\.net$
(^|\.)tamiaode\.tk$ (^|\.)tamiaode\.tk$
(^|\.)tanc\.org$ (^|\.)tanc\.org$
...@@ -4027,10 +4323,15 @@ ...@@ -4027,10 +4323,15 @@
(^|\.)tcpspeed\.com$ (^|\.)tcpspeed\.com$
(^|\.)tcsofbc\.org$ (^|\.)tcsofbc\.org$
(^|\.)tcsovi\.org$ (^|\.)tcsovi\.org$
(^|\.)tdm\.com\.mo$
(^|\.)teachparentstech\.org$ (^|\.)teachparentstech\.org$
(^|\.)teamamericany\.com$ (^|\.)teamamericany\.com$
(^|\.)tech2\.in\.com$ (^|\.)tech2\.in\.com$
(^|\.)techviz\.net$
(^|\.)teck\.in$ (^|\.)teck\.in$
(^|\.)teco-hk\.org$
(^|\.)teco-mo\.org$
(^|\.)teddysun\.com$
(^|\.)teeniefuck\.net$ (^|\.)teeniefuck\.net$
(^|\.)teensinasia\.com$ (^|\.)teensinasia\.com$
(^|\.)telecomspace\.com$ (^|\.)telecomspace\.com$
...@@ -4044,7 +4345,9 @@ ...@@ -4044,7 +4345,9 @@
(^|\.)tenacy\.com$ (^|\.)tenacy\.com$
(^|\.)tensorflow\.org$ (^|\.)tensorflow\.org$
(^|\.)tenzinpalmo\.com$ (^|\.)tenzinpalmo\.com$
(^|\.)terminus2049\.github\.io$
(^|\.)tew\.org$ (^|\.)tew\.org$
(^|\.)textnow\.me$
(^|\.)th\.hao123\.com$ (^|\.)th\.hao123\.com$
(^|\.)thaicn\.com$ (^|\.)thaicn\.com$
(^|\.)thb\.gov\.tw$ (^|\.)thb\.gov\.tw$
...@@ -4067,6 +4370,7 @@ ...@@ -4067,6 +4370,7 @@
(^|\.)theinitium\.com$ (^|\.)theinitium\.com$
(^|\.)thenewslens\.com$ (^|\.)thenewslens\.com$
(^|\.)thepiratebay\.org$ (^|\.)thepiratebay\.org$
(^|\.)theporndude\.com$
(^|\.)theportalwiki\.com$ (^|\.)theportalwiki\.com$
(^|\.)thereallove\.kr$ (^|\.)thereallove\.kr$
(^|\.)therock\.net\.nz$ (^|\.)therock\.net\.nz$
...@@ -4080,6 +4384,8 @@ ...@@ -4080,6 +4384,8 @@
(^|\.)thetrotskymovie\.com$ (^|\.)thetrotskymovie\.com$
(^|\.)thevivekspot\.com$ (^|\.)thevivekspot\.com$
(^|\.)thewgo\.org$ (^|\.)thewgo\.org$
(^|\.)theync\.com$
(^|\.)thinkgeek\.com$
(^|\.)thinkingtaiwan\.com$ (^|\.)thinkingtaiwan\.com$
(^|\.)thinkwithgoogle\.com$ (^|\.)thinkwithgoogle\.com$
(^|\.)thisav\.com$ (^|\.)thisav\.com$
...@@ -4190,6 +4496,8 @@ ...@@ -4190,6 +4496,8 @@
(^|\.)times\.hinet\.net$ (^|\.)times\.hinet\.net$
(^|\.)timesofindia\.indiatimes\.com$ (^|\.)timesofindia\.indiatimes\.com$
(^|\.)timsah\.com$ (^|\.)timsah\.com$
(^|\.)tinc-vpn\.org$
(^|\.)tineye\.com$
(^|\.)tintuc101\.com$ (^|\.)tintuc101\.com$
(^|\.)tiny\.cc$ (^|\.)tiny\.cc$
(^|\.)tinychat\.com$ (^|\.)tinychat\.com$
...@@ -4203,6 +4511,9 @@ ...@@ -4203,6 +4511,9 @@
(^|\.)tmdfish\.com$ (^|\.)tmdfish\.com$
(^|\.)tmi\.me$ (^|\.)tmi\.me$
(^|\.)tmpp\.org$ (^|\.)tmpp\.org$
(^|\.)tn1\.shemalez\.com$
(^|\.)tn2\.shemalez\.com$
(^|\.)tn3\.shemalez\.com$
(^|\.)tnaflix\.com$ (^|\.)tnaflix\.com$
(^|\.)tngrnow\.com$ (^|\.)tngrnow\.com$
(^|\.)tngrnow\.net$ (^|\.)tngrnow\.net$
...@@ -4220,7 +4531,9 @@ ...@@ -4220,7 +4531,9 @@
(^|\.)toodoc\.com$ (^|\.)toodoc\.com$
(^|\.)toonel\.net$ (^|\.)toonel\.net$
(^|\.)top\.tv$ (^|\.)top\.tv$
(^|\.)top10vpn\.com$
(^|\.)top81\.ws$ (^|\.)top81\.ws$
(^|\.)topbtc\.com$
(^|\.)topic\.youthwant\.com\.tw$ (^|\.)topic\.youthwant\.com\.tw$
(^|\.)topnews\.in$ (^|\.)topnews\.in$
(^|\.)toppornsites\.com$ (^|\.)toppornsites\.com$
...@@ -4242,6 +4555,7 @@ ...@@ -4242,6 +4555,7 @@
(^|\.)tosh\.comedycentral\.com$ (^|\.)tosh\.comedycentral\.com$
(^|\.)totalvpn\.com$ (^|\.)totalvpn\.com$
(^|\.)toutiaoabc\.com$ (^|\.)toutiaoabc\.com$
(^|\.)toutyrater\.github\.io$
(^|\.)towngain\.com$ (^|\.)towngain\.com$
(^|\.)toypark\.in$ (^|\.)toypark\.in$
(^|\.)toythieves\.com$ (^|\.)toythieves\.com$
...@@ -4276,6 +4590,7 @@ ...@@ -4276,6 +4590,7 @@
(^|\.)tt-rss\.org$ (^|\.)tt-rss\.org$
(^|\.)tt1069\.com$ (^|\.)tt1069\.com$
(^|\.)tttan\.com$ (^|\.)tttan\.com$
(^|\.)ttvnw\.net$
(^|\.)tu8964\.com$ (^|\.)tu8964\.com$
(^|\.)tubaholic\.com$ (^|\.)tubaholic\.com$
(^|\.)tube\.com$ (^|\.)tube\.com$
...@@ -4284,6 +4599,7 @@ ...@@ -4284,6 +4599,7 @@
(^|\.)tubecup\.com$ (^|\.)tubecup\.com$
(^|\.)tubegals\.com$ (^|\.)tubegals\.com$
(^|\.)tubeislam\.com$ (^|\.)tubeislam\.com$
(^|\.)tubepornclassic\.com$
(^|\.)tubestack\.com$ (^|\.)tubestack\.com$
(^|\.)tubewolf\.com$ (^|\.)tubewolf\.com$
(^|\.)tui\.orzdream\.com$ (^|\.)tui\.orzdream\.com$
...@@ -4330,6 +4646,7 @@ ...@@ -4330,6 +4646,7 @@
(^|\.)tw\.bid\.yahoo\.com$ (^|\.)tw\.bid\.yahoo\.com$
(^|\.)tw\.gigacircle\.com$ (^|\.)tw\.gigacircle\.com$
(^|\.)tw\.hao123\.com$ (^|\.)tw\.hao123\.com$
(^|\.)tw\.iqiyi\.com$
(^|\.)tw\.jiepang\.com$ (^|\.)tw\.jiepang\.com$
(^|\.)tw\.knowledge\.yahoo\.com$ (^|\.)tw\.knowledge\.yahoo\.com$
(^|\.)tw\.mall\.yahoo\.com$ (^|\.)tw\.mall\.yahoo\.com$
...@@ -4377,6 +4694,7 @@ ...@@ -4377,6 +4694,7 @@
(^|\.)twibble\.de$ (^|\.)twibble\.de$
(^|\.)twibbon\.com$ (^|\.)twibbon\.com$
(^|\.)twibs\.com$ (^|\.)twibs\.com$
(^|\.)twicountry\.org$
(^|\.)twicsy\.com$ (^|\.)twicsy\.com$
(^|\.)twiends\.com$ (^|\.)twiends\.com$
(^|\.)twifan\.com$ (^|\.)twifan\.com$
...@@ -4398,6 +4716,8 @@ ...@@ -4398,6 +4716,8 @@
(^|\.)twit2d\.com$ (^|\.)twit2d\.com$
(^|\.)twitbrowser\.net$ (^|\.)twitbrowser\.net$
(^|\.)twitcause\.com$ (^|\.)twitcause\.com$
(^|\.)twitch\.tv$
(^|\.)twitchcdn\.net$
(^|\.)twitgether\.com$ (^|\.)twitgether\.com$
(^|\.)twitgoo\.com$ (^|\.)twitgoo\.com$
(^|\.)twitiq\.com$ (^|\.)twitiq\.com$
...@@ -4449,6 +4769,7 @@ ...@@ -4449,6 +4769,7 @@
(^|\.)udnbkk\.com$ (^|\.)udnbkk\.com$
(^|\.)uforadio\.com\.tw$ (^|\.)uforadio\.com\.tw$
(^|\.)ufreevpn\.com$ (^|\.)ufreevpn\.com$
(^|\.)ug\.m\.wikipedia\.org$
(^|\.)ugo\.com$ (^|\.)ugo\.com$
(^|\.)uhdwallpapers\.org$ (^|\.)uhdwallpapers\.org$
(^|\.)uhrp\.org$ (^|\.)uhrp\.org$
...@@ -4457,6 +4778,7 @@ ...@@ -4457,6 +4778,7 @@
(^|\.)uighurbiz\.net$ (^|\.)uighurbiz\.net$
(^|\.)ukcdp\.co\.uk$ (^|\.)ukcdp\.co\.uk$
(^|\.)ukliferadio\.co\.uk$ (^|\.)ukliferadio\.co\.uk$
(^|\.)uku\.im$
(^|\.)ulike\.net$ (^|\.)ulike\.net$
(^|\.)ulop\.net$ (^|\.)ulop\.net$
(^|\.)ultrareach$ (^|\.)ultrareach$
...@@ -4477,6 +4799,7 @@ ...@@ -4477,6 +4799,7 @@
(^|\.)uni\.cc$ (^|\.)uni\.cc$
(^|\.)unification\.net$ (^|\.)unification\.net$
(^|\.)unification\.org\.tw$ (^|\.)unification\.org\.tw$
(^|\.)unirule\.cloud$
(^|\.)unitedsocialpress\.com$ (^|\.)unitedsocialpress\.com$
(^|\.)unix100\.com$ (^|\.)unix100\.com$
(^|\.)unknownspace\.org$ (^|\.)unknownspace\.org$
...@@ -4497,6 +4820,7 @@ ...@@ -4497,6 +4820,7 @@
(^|\.)uproxy\.org$ (^|\.)uproxy\.org$
(^|\.)upwill\.org$ (^|\.)upwill\.org$
(^|\.)ur7s\.com$ (^|\.)ur7s\.com$
(^|\.)uraban\.me$
(^|\.)urbansurvival\.com$ (^|\.)urbansurvival\.com$
(^|\.)urchin\.com$ (^|\.)urchin\.com$
(^|\.)urlborg\.com$ (^|\.)urlborg\.com$
...@@ -4519,6 +4843,7 @@ ...@@ -4519,6 +4843,7 @@
(^|\.)usus\.cc$ (^|\.)usus\.cc$
(^|\.)utopianpal\.com$ (^|\.)utopianpal\.com$
(^|\.)uu-gg\.com$ (^|\.)uu-gg\.com$
(^|\.)uukanshu\.com$
(^|\.)uvwxyz\.xyz$ (^|\.)uvwxyz\.xyz$
(^|\.)uwants\.com$ (^|\.)uwants\.com$
(^|\.)uwants\.net$ (^|\.)uwants\.net$
...@@ -4542,6 +4867,7 @@ ...@@ -4542,6 +4867,7 @@
(^|\.)vanilla-jp\.com$ (^|\.)vanilla-jp\.com$
(^|\.)vanpeople\.com$ (^|\.)vanpeople\.com$
(^|\.)vansky\.com$ (^|\.)vansky\.com$
(^|\.)vaticannews\.va$
(^|\.)vatn\.org$ (^|\.)vatn\.org$
(^|\.)vcf-online\.org$ (^|\.)vcf-online\.org$
(^|\.)vcfbuilder\.org$ (^|\.)vcfbuilder\.org$
...@@ -4587,6 +4913,7 @@ ...@@ -4587,6 +4913,7 @@
(^|\.)vine\.co$ (^|\.)vine\.co$
(^|\.)vinniev\.com$ (^|\.)vinniev\.com$
(^|\.)vip-enterprise\.com$ (^|\.)vip-enterprise\.com$
(^|\.)virtualrealporn\.com$
(^|\.)visibletweets\.com$ (^|\.)visibletweets\.com$
(^|\.)vital247\.org$ (^|\.)vital247\.org$
(^|\.)viu\.com$ (^|\.)viu\.com$
...@@ -4595,10 +4922,12 @@ ...@@ -4595,10 +4922,12 @@
(^|\.)vivatube\.com$ (^|\.)vivatube\.com$
(^|\.)vivthomas\.com$ (^|\.)vivthomas\.com$
(^|\.)vizvaz\.com$ (^|\.)vizvaz\.com$
(^|\.)vjav\.com$
(^|\.)vjmedia\.com\.hk$ (^|\.)vjmedia\.com\.hk$
(^|\.)vllcs\.org$ (^|\.)vllcs\.org$
(^|\.)vlog\.xuite\.net$ (^|\.)vlog\.xuite\.net$
(^|\.)vmixcore\.com$ (^|\.)vmixcore\.com$
(^|\.)vmpsoft\.com$
(^|\.)vn\.hao123\.com$ (^|\.)vn\.hao123\.com$
(^|\.)vnet\.link$ (^|\.)vnet\.link$
(^|\.)voa-11\.akacast\.akamaistream\.net$ (^|\.)voa-11\.akacast\.akamaistream\.net$
...@@ -4611,6 +4940,7 @@ ...@@ -4611,6 +4940,7 @@
(^|\.)voatibetanenglish\.com$ (^|\.)voatibetanenglish\.com$
(^|\.)vocativ\.com$ (^|\.)vocativ\.com$
(^|\.)vocn\.tv$ (^|\.)vocn\.tv$
(^|\.)vod-abematv\.akamaized\.net$
(^|\.)vod\.wwe\.com$ (^|\.)vod\.wwe\.com$
(^|\.)vot\.org$ (^|\.)vot\.org$
(^|\.)vovo2000\.com$ (^|\.)vovo2000\.com$
...@@ -4630,6 +4960,7 @@ ...@@ -4630,6 +4960,7 @@
(^|\.)vpndada\.com$ (^|\.)vpndada\.com$
(^|\.)vpnfan\.com$ (^|\.)vpnfan\.com$
(^|\.)vpnfire\.com$ (^|\.)vpnfire\.com$
(^|\.)vpnfires\.biz$
(^|\.)vpnforgame\.net$ (^|\.)vpnforgame\.net$
(^|\.)vpngate\.jp$ (^|\.)vpngate\.jp$
(^|\.)vpngate\.net$ (^|\.)vpngate\.net$
...@@ -4660,8 +4991,10 @@ ...@@ -4660,8 +4991,10 @@
(^|\.)vpser\.net$ (^|\.)vpser\.net$
(^|\.)vraiesagesse\.net$ (^|\.)vraiesagesse\.net$
(^|\.)vrmtr\.com$ (^|\.)vrmtr\.com$
(^|\.)vrsmash\.com$
(^|\.)vtunnel\.com$ (^|\.)vtunnel\.com$
(^|\.)vuku\.cc$ (^|\.)vuku\.cc$
(^|\.)vultryhw\.com$
(^|\.)w\.idaiwan\.com$ (^|\.)w\.idaiwan\.com$
(^|\.)w3schools\.com$ (^|\.)w3schools\.com$
(^|\.)waffle1999\.com$ (^|\.)waffle1999\.com$
...@@ -4699,6 +5032,7 @@ ...@@ -4699,6 +5032,7 @@
(^|\.)wdf5\.com$ (^|\.)wdf5\.com$
(^|\.)wearehairy\.com$ (^|\.)wearehairy\.com$
(^|\.)wearn\.com$ (^|\.)wearn\.com$
(^|\.)web\.dev$
(^|\.)web2project\.net$ (^|\.)web2project\.net$
(^|\.)webbang\.net$ (^|\.)webbang\.net$
(^|\.)webevader\.org$ (^|\.)webevader\.org$
...@@ -4716,6 +5050,7 @@ ...@@ -4716,6 +5050,7 @@
(^|\.)weekmag\.info$ (^|\.)weekmag\.info$
(^|\.)wefightcensorship\.org$ (^|\.)wefightcensorship\.org$
(^|\.)wefong\.com$ (^|\.)wefong\.com$
(^|\.)wego\.here\.com$
(^|\.)weiboleak\.com$ (^|\.)weiboleak\.com$
(^|\.)weiboscope\.jmsc\.hku\.hk$ (^|\.)weiboscope\.jmsc\.hku\.hk$
(^|\.)weihuo\.org$ (^|\.)weihuo\.org$
...@@ -4730,6 +5065,7 @@ ...@@ -4730,6 +5065,7 @@
(^|\.)wenhui\.ch$ (^|\.)wenhui\.ch$
(^|\.)wenxuecity\.com$ (^|\.)wenxuecity\.com$
(^|\.)wenyunchao\.com$ (^|\.)wenyunchao\.com$
(^|\.)wenzhao\.ca$
(^|\.)westca\.com$ (^|\.)westca\.com$
(^|\.)westernshugdensociety\.org$ (^|\.)westernshugdensociety\.org$
(^|\.)westernwolves\.com$ (^|\.)westernwolves\.com$
...@@ -4744,7 +5080,9 @@ ...@@ -4744,7 +5080,9 @@
(^|\.)wha\.la$ (^|\.)wha\.la$
(^|\.)whatblocked\.com$ (^|\.)whatblocked\.com$
(^|\.)whatbrowser\.org$ (^|\.)whatbrowser\.org$
(^|\.)whatsapp\.com$
(^|\.)whatsapp\.net$ (^|\.)whatsapp\.net$
(^|\.)whatsonweibo\.com$
(^|\.)wheatseeds\.org$ (^|\.)wheatseeds\.org$
(^|\.)wheelockslatin\.com$ (^|\.)wheelockslatin\.com$
(^|\.)whereiswerner\.com$ (^|\.)whereiswerner\.com$
...@@ -4778,6 +5116,7 @@ ...@@ -4778,6 +5116,7 @@
(^|\.)wikimapia\.org$ (^|\.)wikimapia\.org$
(^|\.)wikiwiki\.jp$ (^|\.)wikiwiki\.jp$
(^|\.)wildammo\.com$ (^|\.)wildammo\.com$
(^|\.)williamhill\.com$
(^|\.)willw\.net$ (^|\.)willw\.net$
(^|\.)windowsphoneme\.com$ (^|\.)windowsphoneme\.com$
(^|\.)windscribe\.com$ (^|\.)windscribe\.com$
...@@ -4813,6 +5152,7 @@ ...@@ -4813,6 +5152,7 @@
(^|\.)wordpress\.com$ (^|\.)wordpress\.com$
(^|\.)workatruna\.com$ (^|\.)workatruna\.com$
(^|\.)workerdemo\.org\.hk$ (^|\.)workerdemo\.org\.hk$
(^|\.)workerempowerment\.org$
(^|\.)workersthebig\.net$ (^|\.)workersthebig\.net$
(^|\.)worldcat\.org$ (^|\.)worldcat\.org$
(^|\.)worldjournal\.com$ (^|\.)worldjournal\.com$
...@@ -4910,6 +5250,7 @@ ...@@ -4910,6 +5250,7 @@
(^|\.)xanga\.com$ (^|\.)xanga\.com$
(^|\.)xbabe\.com$ (^|\.)xbabe\.com$
(^|\.)xbookcn\.com$ (^|\.)xbookcn\.com$
(^|\.)xbtce\.com$
(^|\.)xcafe\.in$ (^|\.)xcafe\.in$
(^|\.)xcity\.jp$ (^|\.)xcity\.jp$
(^|\.)xcritic\.com$ (^|\.)xcritic\.com$
...@@ -4918,6 +5259,7 @@ ...@@ -4918,6 +5259,7 @@
(^|\.)xfm\.pp\.ru$ (^|\.)xfm\.pp\.ru$
(^|\.)xgmyd\.com$ (^|\.)xgmyd\.com$
(^|\.)xhamster\.com$ (^|\.)xhamster\.com$
(^|\.)xianba\.net$
(^|\.)xianchawang\.net$ (^|\.)xianchawang\.net$
(^|\.)xianjian\.tw$ (^|\.)xianjian\.tw$
(^|\.)xianqiao\.net$ (^|\.)xianqiao\.net$
...@@ -4929,6 +5271,7 @@ ...@@ -4929,6 +5271,7 @@
(^|\.)xiaoma\.org$ (^|\.)xiaoma\.org$
(^|\.)xiezhua\.com$ (^|\.)xiezhua\.com$
(^|\.)xihua\.es$ (^|\.)xihua\.es$
(^|\.)xijie\.wordpress\.com$
(^|\.)xing\.com$ (^|\.)xing\.com$
(^|\.)xinhuanet\.org$ (^|\.)xinhuanet\.org$
(^|\.)xinmiao\.com\.hk$ (^|\.)xinmiao\.com\.hk$
...@@ -4951,6 +5294,7 @@ ...@@ -4951,6 +5294,7 @@
(^|\.)xn--czq75pvv1aj5c\.org$ (^|\.)xn--czq75pvv1aj5c\.org$
(^|\.)xn--i2ru8q2qg\.com$ (^|\.)xn--i2ru8q2qg\.com$
(^|\.)xn--ngstr-lra8j\.com$ (^|\.)xn--ngstr-lra8j\.com$
(^|\.)xn--oiq\.cc$
(^|\.)xn--p8j9a0d9c9a\.xn--q9jyb4c$ (^|\.)xn--p8j9a0d9c9a\.xn--q9jyb4c$
(^|\.)xnxx\.com$ (^|\.)xnxx\.com$
(^|\.)xpdo\.net$ (^|\.)xpdo\.net$
...@@ -5012,11 +5356,15 @@ ...@@ -5012,11 +5356,15 @@
(^|\.)yinlei\.org$ (^|\.)yinlei\.org$
(^|\.)yipub\.com$ (^|\.)yipub\.com$
(^|\.)yizhihongxing\.com$ (^|\.)yizhihongxing\.com$
(^|\.)yobit\.net$
(^|\.)yobt\.com$ (^|\.)yobt\.com$
(^|\.)yobt\.tv$ (^|\.)yobt\.tv$
(^|\.)yogichen\.org$ (^|\.)yogichen\.org$
(^|\.)yolasite\.com$
(^|\.)yomiuri\.co\.jp$
(^|\.)yong\.hu$ (^|\.)yong\.hu$
(^|\.)yorkbbs\.ca$ (^|\.)yorkbbs\.ca$
(^|\.)you-get\.org$
(^|\.)youdontcare\.com$ (^|\.)youdontcare\.com$
(^|\.)youjizz\.com$ (^|\.)youjizz\.com$
(^|\.)youmaker\.com$ (^|\.)youmaker\.com$
...@@ -5041,6 +5389,7 @@ ...@@ -5041,6 +5389,7 @@
(^|\.)youtubeeducation\.com$ (^|\.)youtubeeducation\.com$
(^|\.)youtubegaming\.com$ (^|\.)youtubegaming\.com$
(^|\.)youversion\.com$ (^|\.)youversion\.com$
(^|\.)youwin\.com$
(^|\.)youxu\.info$ (^|\.)youxu\.info$
(^|\.)yt\.be$ (^|\.)yt\.be$
(^|\.)ytht\.net$ (^|\.)ytht\.net$
...@@ -5060,10 +5409,12 @@ ...@@ -5060,10 +5409,12 @@
(^|\.)zacebook\.com$ (^|\.)zacebook\.com$
(^|\.)zalmos\.com$ (^|\.)zalmos\.com$
(^|\.)zannel\.com$ (^|\.)zannel\.com$
(^|\.)zaobao\.com$
(^|\.)zaobao\.com\.sg$ (^|\.)zaobao\.com\.sg$
(^|\.)zaozon\.com$ (^|\.)zaozon\.com$
(^|\.)zapto\.org$ (^|\.)zapto\.org$
(^|\.)zattoo\.com$ (^|\.)zattoo\.com$
(^|\.)zb\.com$
(^|\.)zdnet\.com\.tw$ (^|\.)zdnet\.com\.tw$
(^|\.)zello\.com$ (^|\.)zello\.com$
(^|\.)zengjinyan\.org$ (^|\.)zengjinyan\.org$
...@@ -5075,9 +5426,11 @@ ...@@ -5075,9 +5426,11 @@
(^|\.)zgsddh\.com$ (^|\.)zgsddh\.com$
(^|\.)zgzcjj\.net$ (^|\.)zgzcjj\.net$
(^|\.)zh-yue\.wikipedia\.org$ (^|\.)zh-yue\.wikipedia\.org$
(^|\.)zh\.bitterwinter\.org$
(^|\.)zh\.ecdm\.wikia\.com$ (^|\.)zh\.ecdm\.wikia\.com$
(^|\.)zh\.m\.wikipedia\.org$ (^|\.)zh\.m\.wikipedia\.org$
(^|\.)zh\.pokerstrategy\.com$ (^|\.)zh\.pokerstrategy\.com$
(^|\.)zh\.pttpedia\.wikia\.com$
(^|\.)zh\.uncyclopedia\.wikia\.com$ (^|\.)zh\.uncyclopedia\.wikia\.com$
(^|\.)zh\.wikinews\.org$ (^|\.)zh\.wikinews\.org$
(^|\.)zh\.wikipedia\.org$ (^|\.)zh\.wikipedia\.org$
...@@ -5135,4 +5488,5 @@ ...@@ -5135,4 +5488,5 @@
(^|\.)zyns\.com$ (^|\.)zyns\.com$
(^|\.)zyzc9\.com$ (^|\.)zyzc9\.com$
(^|\.)zzcartoon\.com$ (^|\.)zzcartoon\.com$
(^|\.)zzcloud\.me$
(^|\.)zzux\.com$ (^|\.)zzux\.com$
...@@ -32,6 +32,7 @@ import java.io.File ...@@ -32,6 +32,7 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.io.Reader import java.io.Reader
import java.net.URL import java.net.URL
import java.net.URLConnection
class Acl { class Acl {
companion object { companion object {
...@@ -151,11 +152,11 @@ class Acl { ...@@ -151,11 +152,11 @@ class Acl {
fromReader(getFile(id).bufferedReader()) fromReader(getFile(id).bufferedReader())
} catch (_: IOException) { this } } catch (_: IOException) { this }
fun flatten(depth: Int): Acl { suspend fun flatten(depth: Int, connect: suspend (URL) -> URLConnection): Acl {
if (depth > 0) for (url in urls.asIterable()) { if (depth > 0) for (url in urls.asIterable()) {
val child = Acl() val child = Acl()
try { try {
child.fromReader(url.openStream().bufferedReader(), bypass).flatten(depth - 1) child.fromReader(connect(url).getInputStream().bufferedReader(), bypass).flatten(depth - 1, connect)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
continue continue
......
...@@ -28,6 +28,7 @@ import android.os.DeadObjectException ...@@ -28,6 +28,7 @@ import android.os.DeadObjectException
import android.os.Handler import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.RemoteException import android.os.RemoteException
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.bg.ProxyService import com.github.shadowsocks.bg.ProxyService
import com.github.shadowsocks.bg.TransproxyService import com.github.shadowsocks.bg.TransproxyService
import com.github.shadowsocks.bg.VpnService import com.github.shadowsocks.bg.VpnService
...@@ -51,7 +52,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(), ...@@ -51,7 +52,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(),
} }
interface Callback { interface Callback {
fun stateChanged(state: Int, profileName: String?, msg: String?) fun stateChanged(state: BaseService.State, profileName: String?, msg: String?)
fun trafficUpdated(profileId: Long, stats: TrafficStats) { } fun trafficUpdated(profileId: Long, stats: TrafficStats) { }
fun trafficPersisted(profileId: Long) { } fun trafficPersisted(profileId: Long) { }
...@@ -68,13 +69,16 @@ class ShadowsocksConnection(private val handler: Handler = Handler(), ...@@ -68,13 +69,16 @@ class ShadowsocksConnection(private val handler: Handler = Handler(),
private var callback: Callback? = null private var callback: Callback? = null
private val serviceCallback = object : IShadowsocksServiceCallback.Stub() { private val serviceCallback = object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { override fun stateChanged(state: Int, profileName: String?, msg: String?) {
handler.post { callback!!.stateChanged(state, profileName, msg) } val callback = callback ?: return
handler.post { callback.stateChanged(BaseService.State.values()[state], profileName, msg) }
} }
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
handler.post { callback!!.trafficUpdated(profileId, stats) } val callback = callback ?: return
handler.post { callback.trafficUpdated(profileId, stats) }
} }
override fun trafficPersisted(profileId: Long) { override fun trafficPersisted(profileId: Long) {
handler.post { callback!!.trafficPersisted(profileId) } val callback = callback ?: return
handler.post { callback.trafficPersisted(profileId) }
} }
} }
private var binder: IBinder? = null private var binder: IBinder? = null
...@@ -105,14 +109,14 @@ class ShadowsocksConnection(private val handler: Handler = Handler(), ...@@ -105,14 +109,14 @@ class ShadowsocksConnection(private val handler: Handler = Handler(),
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
unregisterCallback() unregisterCallback()
callback!!.onServiceDisconnected() callback?.onServiceDisconnected()
service = null service = null
binder = null binder = null
} }
override fun binderDied() { override fun binderDied() {
service = null service = null
handler.post(callback!!::onBinderDied) callback?.also { handler.post(it::onBinderDied) }
} }
private fun unregisterCallback() { private fun unregisterCallback() {
......
...@@ -39,9 +39,11 @@ import com.github.shadowsocks.plugin.PluginManager ...@@ -39,9 +39,11 @@ import com.github.shadowsocks.plugin.PluginManager
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.broadcastReceiver import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.readableMessage
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.net.BindException
import java.net.InetAddress import java.net.InetAddress
import java.net.URL import java.net.URL
import java.net.UnknownHostException import java.net.UnknownHostException
...@@ -51,20 +53,22 @@ import java.util.* ...@@ -51,20 +53,22 @@ import java.util.*
* This object uses WeakMap to simulate the effects of multi-inheritance. * This object uses WeakMap to simulate the effects of multi-inheritance.
*/ */
object BaseService { object BaseService {
/** enum class State(val canStop: Boolean = false) {
* IDLE state is only used by UI and will never be returned by BaseService. /**
*/ * Idle state is only used by UI and will never be returned by BaseService.
const val IDLE = 0 */
const val CONNECTING = 1 Idle,
const val CONNECTED = 2 Connecting(true),
const val STOPPING = 3 Connected(true),
const val STOPPED = 4 Stopping,
Stopped,
}
const val CONFIG_FILE = "shadowsocks.conf" const val CONFIG_FILE = "shadowsocks.conf"
const val CONFIG_FILE_UDP = "shadowsocks-udp.conf" const val CONFIG_FILE_UDP = "shadowsocks-udp.conf"
class Data internal constructor(private val service: Interface) { class Data internal constructor(private val service: Interface) {
var state = STOPPED var state = State.Stopped
var processes: GuardedProcessPool? = null var processes: GuardedProcessPool? = null
var proxy: ProxyInstance? = null var proxy: ProxyInstance? = null
var udpFallback: ProxyInstance? = null var udpFallback: ProxyInstance? = null
...@@ -81,7 +85,7 @@ object BaseService { ...@@ -81,7 +85,7 @@ object BaseService {
val binder = Binder(this) val binder = Binder(this)
var connectingJob: Job? = null var connectingJob: Job? = null
fun changeState(s: Int, msg: String? = null) { fun changeState(s: State, msg: String? = null) {
if (state == s && msg == null) return if (state == s && msg == null) return
binder.stateChanged(s, msg) binder.stateChanged(s, msg)
state = s state = s
...@@ -102,7 +106,7 @@ object BaseService { ...@@ -102,7 +106,7 @@ object BaseService {
private val bandwidthListeners = mutableMapOf<IBinder, Long>() // the binder is the real identifier private val bandwidthListeners = mutableMapOf<IBinder, Long>() // the binder is the real identifier
private val handler = Handler() private val handler = Handler()
override fun getState(): Int = data!!.state override fun getState(): Int = data!!.state.ordinal
override fun getProfileName(): String = data!!.proxy?.profile?.name ?: "Idle" override fun getProfileName(): String = data!!.proxy?.profile?.name ?: "Idle"
override fun registerCallback(cb: IShadowsocksServiceCallback) { override fun registerCallback(cb: IShadowsocksServiceCallback) {
...@@ -124,12 +128,12 @@ object BaseService { ...@@ -124,12 +128,12 @@ object BaseService {
handler.postDelayed(this::onTimeout, bandwidthListeners.values.min() ?: return) handler.postDelayed(this::onTimeout, bandwidthListeners.values.min() ?: return)
} }
private fun onTimeout() { private fun onTimeout() {
val proxies = listOfNotNull(data!!.proxy, data!!.udpFallback) val proxies = listOfNotNull(data?.proxy, data?.udpFallback)
val stats = proxies val stats = proxies
.map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) } .map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) }
.filter { it.second != null } .filter { it.second != null }
.map { Triple(it.first, it.second!!.first, it.second!!.second) } .map { Triple(it.first, it.second!!.first, it.second!!.second) }
if (stats.any { it.third } && state == CONNECTED && bandwidthListeners.isNotEmpty()) { if (stats.any { it.third } && data!!.state == State.Connected && bandwidthListeners.isNotEmpty()) {
val sum = stats.fold(TrafficStats()) { a, b -> a + b.second } val sum = stats.fold(TrafficStats()) { a, b -> a + b.second }
broadcast { item -> broadcast { item ->
if (bandwidthListeners.contains(item.asBinder())) { if (bandwidthListeners.contains(item.asBinder())) {
...@@ -145,7 +149,7 @@ object BaseService { ...@@ -145,7 +149,7 @@ object BaseService {
val wasEmpty = bandwidthListeners.isEmpty() val wasEmpty = bandwidthListeners.isEmpty()
if (bandwidthListeners.put(cb.asBinder(), timeout) == null) { if (bandwidthListeners.put(cb.asBinder(), timeout) == null) {
if (wasEmpty) registerTimeout() if (wasEmpty) registerTimeout()
if (state != CONNECTED) return if (data!!.state != State.Connected) return
var sum = TrafficStats() var sum = TrafficStats()
val proxy = data!!.proxy ?: return val proxy = data!!.proxy ?: return
proxy.trafficMonitor?.out.also { stats -> proxy.trafficMonitor?.out.also { stats ->
...@@ -177,9 +181,9 @@ object BaseService { ...@@ -177,9 +181,9 @@ object BaseService {
callbacks.unregister(cb) callbacks.unregister(cb)
} }
fun stateChanged(s: Int, msg: String?) { fun stateChanged(s: State, msg: String?) {
val profileName = profileName val profileName = profileName
broadcast { it.stateChanged(s, profileName, msg) } broadcast { it.stateChanged(s.ordinal, profileName, msg) }
} }
fun trafficPersisted(ids: List<Long>) { fun trafficPersisted(ids: List<Long>) {
...@@ -211,9 +215,9 @@ object BaseService { ...@@ -211,9 +215,9 @@ object BaseService {
return return
} }
val s = data.state val s = data.state
when (s) { when {
STOPPED -> startRunner() s == State.Stopped -> startRunner()
CONNECTED -> stopRunner(true) s.canStop -> stopRunner(true)
else -> Crashlytics.log(Log.WARN, tag, "Illegal state when invoking use: $s") else -> Crashlytics.log(Log.WARN, tag, "Illegal state when invoking use: $s")
} }
} }
...@@ -249,11 +253,12 @@ object BaseService { ...@@ -249,11 +253,12 @@ object BaseService {
} }
fun stopRunner(restart: Boolean = false, msg: String? = null) { fun stopRunner(restart: Boolean = false, msg: String? = null) {
if (data.state == STOPPING) return if (data.state == State.Stopping) return
// channge the state // channge the state
data.changeState(STOPPING) data.changeState(State.Stopping)
GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) {
Core.analytics.logEvent("stop", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag))) Core.analytics.logEvent("stop", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag)))
data.connectingJob?.cancelAndJoin() // ensure stop connecting first
this@Interface as Service this@Interface as Service
// we use a coroutineScope here to allow clean-up in parallel // we use a coroutineScope here to allow clean-up in parallel
coroutineScope { coroutineScope {
...@@ -278,7 +283,7 @@ object BaseService { ...@@ -278,7 +283,7 @@ object BaseService {
} }
// change the state // change the state
data.changeState(STOPPED, msg) data.changeState(State.Stopped, msg)
// stop the service if nothing has bound to it // stop the service if nothing has bound to it
if (restart) startRunner() else stopSelf() if (restart) startRunner() else stopSelf()
...@@ -291,7 +296,7 @@ object BaseService { ...@@ -291,7 +296,7 @@ object BaseService {
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY if (data.state != State.Stopped) return Service.START_NOT_STICKY
val profilePair = Core.currentProfile val profilePair = Core.currentProfile
this as Context this as Context
if (profilePair == null) { if (profilePair == null) {
...@@ -318,7 +323,7 @@ object BaseService { ...@@ -318,7 +323,7 @@ object BaseService {
data.notification = createNotification(profile.formattedName) data.notification = createNotification(profile.formattedName)
Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag))) Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag)))
data.changeState(CONNECTING) data.changeState(State.Connecting)
data.connectingJob = GlobalScope.launch(Dispatchers.Main) { data.connectingJob = GlobalScope.launch(Dispatchers.Main) {
try { try {
Executable.killAll() // clean up old processes Executable.killAll() // clean up old processes
...@@ -328,8 +333,7 @@ object BaseService { ...@@ -328,8 +333,7 @@ object BaseService {
data.processes = GuardedProcessPool { data.processes = GuardedProcessPool {
printLog(it) printLog(it)
data.connectingJob?.cancelAndJoin() stopRunner(false, it.readableMessage)
stopRunner(false, it.localizedMessage)
} }
startProcesses() startProcesses()
...@@ -337,16 +341,18 @@ object BaseService { ...@@ -337,16 +341,18 @@ object BaseService {
data.udpFallback?.scheduleUpdate() data.udpFallback?.scheduleUpdate()
RemoteConfig.fetch() RemoteConfig.fetch()
data.changeState(CONNECTED) data.changeState(State.Connected)
} catch (_: CancellationException) { } catch (_: CancellationException) {
// if the job was cancelled, it is canceller's responsibility to call stopRunner // if the job was cancelled, it is canceller's responsibility to call stopRunner
} catch (_: UnknownHostException) { } catch (_: UnknownHostException) {
stopRunner(false, getString(R.string.invalid_server)) stopRunner(false, getString(R.string.invalid_server))
} catch (exc: Throwable) { } catch (exc: Throwable) {
if (exc !is PluginManager.PluginNotFoundException && exc !is VpnService.NullConnectionException) { if (exc !is PluginManager.PluginNotFoundException &&
exc !is BindException &&
exc !is VpnService.NullConnectionException) {
printLog(exc) printLog(exc)
} }
stopRunner(false, "${getString(R.string.service_failed)}: ${exc.localizedMessage}") stopRunner(false, "${getString(R.string.service_failed)}: ${exc.readableMessage}")
} finally { } finally {
data.connectingJob = null data.connectingJob = null
} }
......
...@@ -26,7 +26,6 @@ import android.system.OsConstants ...@@ -26,7 +26,6 @@ import android.system.OsConstants
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
...@@ -40,11 +39,11 @@ object Executable { ...@@ -40,11 +39,11 @@ object Executable {
fun killAll() { fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) { for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
val exe = File(try { val exe = File(try {
File(process, "cmdline").readText() File(process, "cmdline").inputStream().bufferedReader().readText()
} catch (ignore: FileNotFoundException) { } catch (_: FileNotFoundException) {
continue continue
}.split(Character.MIN_VALUE, limit = 2).first()) }.split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) try { if (EXECUTABLES.contains(exe.name)) try {
Os.kill(process.name.toInt(), OsConstants.SIGKILL) Os.kill(process.name.toInt(), OsConstants.SIGKILL)
} catch (e: ErrnoException) { } catch (e: ErrnoException) {
if (e.errno != OsConstants.ESRCH) { if (e.errno != OsConstants.ESRCH) {
......
...@@ -61,7 +61,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -61,7 +61,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
conn.doOutput = true conn.doOutput = true
val proxies = try { val proxies = try {
withTimeoutOrNull(30_000) { withTimeoutOrNull(10_000) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
conn.outputStream.bufferedWriter().use { conn.outputStream.bufferedWriter().use {
it.write("sig=" + Base64.encodeToString(mdg.digest(), Base64.DEFAULT)) it.write("sig=" + Base64.encodeToString(mdg.digest(), Base64.DEFAULT))
...@@ -80,7 +80,9 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -80,7 +80,9 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
profile.method = proxy[3].trim() profile.method = proxy[3].trim()
} }
if (route == Acl.CUSTOM_RULES) Acl.save(Acl.CUSTOM_RULES, Acl.customRules.flatten(10)) if (route == Acl.CUSTOM_RULES) withContext(Dispatchers.IO) {
Acl.save(Acl.CUSTOM_RULES, Acl.customRules.flatten(10, service::openConnection))
}
// it's hard to resolve DNS on a specific interface so we'll do it here // it's hard to resolve DNS on a specific interface so we'll do it here
if (profile.host.parseNumericAddress() == null) profile.host = withTimeoutOrNull(10_000) { if (profile.host.parseNumericAddress() == null) profile.host = withTimeoutOrNull(10_000) {
......
...@@ -94,7 +94,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -94,7 +94,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
} }
private fun update(action: String?, forceShow: Boolean = false) { private fun update(action: String?, forceShow: Boolean = false) {
if (forceShow || service.data.state == BaseService.CONNECTED) when (action) { if (forceShow || service.data.state == BaseService.State.Connected) when (action) {
Intent.ACTION_SCREEN_OFF -> { Intent.ACTION_SCREEN_OFF -> {
setVisible(false, forceShow) setVisible(false, forceShow)
unregisterCallback() // unregister callback to save battery unregisterCallback() // unregister callback to save battery
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.net.Uri import android.net.Uri
import android.os.Parcelable
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import android.util.LongSparseArray import android.util.LongSparseArray
...@@ -32,6 +33,7 @@ import com.github.shadowsocks.preference.DataStore ...@@ -32,6 +33,7 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
import kotlinx.android.parcel.Parcelize
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONTokener import org.json.JSONTokener
...@@ -41,7 +43,31 @@ import java.net.URISyntaxException ...@@ -41,7 +43,31 @@ import java.net.URISyntaxException
import java.util.* import java.util.*
@Entity @Entity
class Profile : Serializable { @Parcelize
data class Profile(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var name: String? = "",
var host: String = "198.199.101.152",
var remotePort: Int = 8388,
var password: String = "u1rRWTssNv0p",
var method: String = "aes-256-cfb",
var route: String = "all",
var remoteDns: String = "8.8.8.8",
var proxyApps: Boolean = false,
var bypass: Boolean = false,
var udpdns: Boolean = false,
var ipv6: Boolean = true,
var individual: String = "",
var tx: Long = 0,
var rx: Long = 0,
var userOrder: Long = 0,
var plugin: String? = null,
var udpFallback: Long? = null,
@Ignore // not persisted in db, only used by direct boot
var dirty: Boolean = false
) : Parcelable, Serializable {
companion object { companion object {
private const val TAG = "ShadowParser" private const val TAG = "ShadowParser"
private const val serialVersionUID = 0L private const val serialVersionUID = 0L
...@@ -200,29 +226,6 @@ class Profile : Serializable { ...@@ -200,29 +226,6 @@ class Profile : Serializable {
fun deleteAll(): Int fun deleteAll(): Int
} }
@PrimaryKey(autoGenerate = true)
var id: Long = 0
var name: String? = ""
var host: String = "198.199.101.152"
var remotePort: Int = 8388
var password: String = "u1rRWTssNv0p"
var method: String = "aes-256-cfb"
var route: String = "all"
var remoteDns: String = "8.8.8.8"
var proxyApps: Boolean = false
var bypass: Boolean = false
var udpdns: Boolean = false
var ipv6: Boolean = true
var individual: String = ""
var tx: Long = 0
var rx: Long = 0
var userOrder: Long = 0
var plugin: String? = null
var udpFallback: Long? = null
@Ignore // not persisted in db, only used by direct boot
var dirty: Boolean = false
val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort) val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort)
val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!! val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!!
......
...@@ -21,11 +21,14 @@ ...@@ -21,11 +21,14 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.database.sqlite.SQLiteCantOpenDatabaseException import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.LongSparseArray
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import org.json.JSONArray
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.sql.SQLException import java.sql.SQLException
/** /**
...@@ -36,6 +39,7 @@ object ProfileManager { ...@@ -36,6 +39,7 @@ object ProfileManager {
interface Listener { interface Listener {
fun onAdd(profile: Profile) fun onAdd(profile: Profile)
fun onRemove(profileId: Long) fun onRemove(profileId: Long)
fun onCleared()
} }
var listener: Listener? = null var listener: Listener? = null
...@@ -48,6 +52,36 @@ object ProfileManager { ...@@ -48,6 +52,36 @@ object ProfileManager {
return profile return profile
} }
fun createProfilesFromJson(jsons: Sequence<InputStream>, replace: Boolean = false) {
val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null
val feature = if (replace) {
profiles?.values?.singleOrNull { it.id == DataStore.profileId }
} else Core.currentProfile?.first
val lazyClear = lazy { clear() }
var result: Exception? = null
for (json in jsons) try {
Profile.parseJson(json.bufferedReader().readText(), feature) {
if (replace) {
lazyClear.value
// if two profiles has the same address, treat them as the same profile and copy stats over
profiles?.get(it.formattedAddress)?.apply {
it.tx = tx
it.rx = rx
}
}
createProfile(it)
}
} catch (e: Exception) {
if (result == null) result = e else result.addSuppressed(e)
}
if (result != null) throw result
}
fun serializeToJson(profiles: List<Profile>? = getAllProfiles()): JSONArray? {
if (profiles == null) return null
val lookup = LongSparseArray<Profile>(profiles.size).apply { profiles.forEach { put(it.id, it) } }
return JSONArray(profiles.map { it.toJson(lookup) }.toTypedArray())
}
/** /**
* Note: It's caller's responsibility to update DirectBoot profile if necessary. * Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/ */
...@@ -78,6 +112,7 @@ object ProfileManager { ...@@ -78,6 +112,7 @@ object ProfileManager {
fun clear() = PrivateDatabase.profileDao.deleteAll().also { fun clear() = PrivateDatabase.profileDao.deleteAll().also {
// listener is not called since this won't be used in mobile submodule // listener is not called since this won't be used in mobile submodule
DirectBoot.clean() DirectBoot.clean()
listener?.onCleared()
} }
@Throws(IOException::class) @Throws(IOException::class)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.net package com.github.shadowsocks.net
import android.os.Build
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
...@@ -65,23 +66,31 @@ class ChannelMonitor : Thread("ChannelMonitor") { ...@@ -65,23 +66,31 @@ class ChannelMonitor : Thread("ChannelMonitor") {
start() start()
} }
/**
* Prevent NetworkOnMainThreadException because people enable strict mode for no reasons.
*/
private suspend fun WritableByteChannel.writeCompat(src: ByteBuffer) =
if (Build.VERSION.SDK_INT <= 23) withContext(Dispatchers.Default) { write(src) } else write(src)
suspend fun register(channel: SelectableChannel, ops: Int, block: (SelectionKey) -> Unit): SelectionKey { suspend fun register(channel: SelectableChannel, ops: Int, block: (SelectionKey) -> Unit): SelectionKey {
val registration = Registration(channel, ops, block) val registration = Registration(channel, ops, block)
pendingRegistrations.send(registration) pendingRegistrations.send(registration)
ByteBuffer.allocateDirect(1).also { junk -> ByteBuffer.allocateDirect(1).also { junk ->
loop@ while (running) when (registrationPipe.sink().write(junk)) { loop@ while (running) when (registrationPipe.sink().writeCompat(junk)) {
0 -> kotlinx.coroutines.yield() 0 -> kotlinx.coroutines.yield()
1 -> break@loop 1 -> break@loop
else -> throw IOException("Failed to register in the channel") else -> throw IOException("Failed to register in the channel")
} }
} }
if (!running) throw ClosedChannelException() if (!running) throw CancellationException()
return registration.result.await() return registration.result.await()
} }
suspend fun wait(channel: SelectableChannel, ops: Int) = CompletableDeferred<SelectionKey>().run { suspend fun wait(channel: SelectableChannel, ops: Int) = CompletableDeferred<SelectionKey>().run {
register(channel, ops) { register(channel, ops) {
if (it.isValid) it.interestOps(0) // stop listening if (it.isValid) try {
it.interestOps(0) // stop listening
} catch (_: CancelledKeyException) { }
complete(it) complete(it)
} }
await() await()
......
...@@ -68,7 +68,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -68,7 +68,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
private fun prepareDnsResponse(request: Message) = Message(request.header.id).apply { private fun prepareDnsResponse(request: Message) = Message(request.header.id).apply {
header.setFlag(Flags.QR.toInt()) // this is a response header.setFlag(Flags.QR.toInt()) // this is a response
if (request.header.getFlag(Flags.RD.toInt())) header.setFlag(Flags.RD.toInt()) if (request.header.getFlag(Flags.RD.toInt())) header.setFlag(Flags.RD.toInt())
addRecord(request.question, Section.QUESTION) request.question?.also { addRecord(it, Section.QUESTION) }
} }
} }
private val monitor = ChannelMonitor() private val monitor = ChannelMonitor()
...@@ -99,30 +99,30 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -99,30 +99,30 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
printLog(e) printLog(e)
return forward(packet) return forward(packet)
} }
return coroutineScope { return supervisorScope {
val remote = async { withTimeout(TIMEOUT) { forward(packet) } } val remote = async { withTimeout(TIMEOUT) { forward(packet) } }
try { try {
if (forwardOnly || request.header.opcode != Opcode.QUERY) return@coroutineScope remote.await() if (forwardOnly || request.header.opcode != Opcode.QUERY) return@supervisorScope remote.await()
val question = request.question val question = request.question
if (question?.type != Type.A) return@coroutineScope remote.await() if (question?.type != Type.A) return@supervisorScope remote.await()
val host = question.name.toString(true) val host = question.name.toString(true)
if (remoteDomainMatcher?.containsMatchIn(host) == true) return@coroutineScope remote.await() if (remoteDomainMatcher?.containsMatchIn(host) == true) return@supervisorScope remote.await()
val localResults = try { val localResults = try {
withTimeout(TIMEOUT) { GlobalScope.async(Dispatchers.IO) { localResolver(host) }.await() } withTimeout(TIMEOUT) { GlobalScope.async(Dispatchers.IO) { localResolver(host) }.await() }
} catch (_: TimeoutCancellationException) { } catch (_: TimeoutCancellationException) {
Crashlytics.log(Log.WARN, TAG, "Local resolving timed out, falling back to remote resolving") Crashlytics.log(Log.WARN, TAG, "Local resolving timed out, falling back to remote resolving")
return@coroutineScope remote.await() return@supervisorScope remote.await()
} catch (_: UnknownHostException) { } catch (_: UnknownHostException) {
return@coroutineScope remote.await() return@supervisorScope remote.await()
} }
if (localResults.isEmpty()) return@coroutineScope remote.await() if (localResults.isEmpty()) return@supervisorScope remote.await()
if (localIpMatcher.isEmpty() || localIpMatcher.any { subnet -> localResults.any(subnet::matches) }) { if (localIpMatcher.isEmpty() || localIpMatcher.any { subnet -> localResults.any(subnet::matches) }) {
remote.cancel() remote.cancel()
ByteBuffer.wrap(prepareDnsResponse(request).apply { ByteBuffer.wrap(prepareDnsResponse(request).apply {
header.setFlag(Flags.RA.toInt()) // recursion available header.setFlag(Flags.RA.toInt()) // recursion available
for (address in localResults) addRecord(when (address) { for (address in localResults) addRecord(when (address) {
is Inet4Address -> ARecord(request.question.name, DClass.IN, TTL, address) is Inet4Address -> ARecord(question.name, DClass.IN, TTL, address)
is Inet6Address -> AAAARecord(request.question.name, DClass.IN, TTL, address) is Inet6Address -> AAAARecord(question.name, DClass.IN, TTL, address)
else -> throw IllegalStateException("Unsupported address $address") else -> throw IllegalStateException("Unsupported address $address")
}, Section.ANSWER) }, Section.ANSWER)
}.toWire()) }.toWire())
...@@ -159,7 +159,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -159,7 +159,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
check(channel.send(remoteDns.udpWrap(packet), proxy) > 0) check(channel.send(remoteDns.udpWrap(packet), proxy) > 0)
monitor.wait(channel, SelectionKey.OP_READ) monitor.wait(channel, SelectionKey.OP_READ)
val result = remoteDns.udpReceiveBuffer(UDP_PACKET_SIZE) val result = remoteDns.udpReceiveBuffer(UDP_PACKET_SIZE)
check(channel.receive(result) == proxy) while (channel.receive(result) != proxy) result.clear()
result.flip() result.flip()
remoteDns.udpUnwrap(result) remoteDns.udpUnwrap(result)
result result
......
...@@ -65,7 +65,7 @@ abstract class LocalSocketListener(name: String, socketFile: File) : Thread(name ...@@ -65,7 +65,7 @@ abstract class LocalSocketListener(name: String, socketFile: File) : Thread(name
open fun shutdown(scope: CoroutineScope) { open fun shutdown(scope: CoroutineScope) {
running = false running = false
localSocket.fileDescriptor.apply { localSocket.fileDescriptor?.apply {
// see also: https://issuetracker.google.com/issues/36945762#comment15 // see also: https://issuetracker.google.com/issues/36945762#comment15
if (valid()) try { if (valid()) try {
Os.shutdown(this, OsConstants.SHUT_RDWR) Os.shutdown(this, OsConstants.SHUT_RDWR)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.net package com.github.shadowsocks.net
import com.github.shadowsocks.utils.readableMessage
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import java.io.File import java.io.File
...@@ -57,7 +58,7 @@ object TcpFastOpen { ...@@ -57,7 +58,7 @@ object TcpFastOpen {
ProcessBuilder("su", "-c", "echo 3 > $PATH").redirectErrorStream(true).start() ProcessBuilder("su", "-c", "echo 3 > $PATH").redirectErrorStream(true).start()
.inputStream.bufferedReader().readText() .inputStream.bufferedReader().readText()
} catch (e: IOException) { } catch (e: IOException) {
e.localizedMessage e.readableMessage
} }
} }
fun enableTimeout() = runBlocking { withTimeoutOrNull(1000) { enable() } } fun enableTimeout() = runBlocking { withTimeoutOrNull(1000) { enable() } }
......
...@@ -36,5 +36,5 @@ class DeviceStorageApp(context: Context) : Application() { ...@@ -36,5 +36,5 @@ class DeviceStorageApp(context: Context) : Application() {
* Thou shalt not get the REAL underlying application context which would no longer be operating under device * Thou shalt not get the REAL underlying application context which would no longer be operating under device
* protected storage. * protected storage.
*/ */
override fun getApplicationContext(): Context = this override fun getApplicationContext() = this
} }
...@@ -42,6 +42,8 @@ import kotlinx.coroutines.launch ...@@ -42,6 +42,8 @@ import kotlinx.coroutines.launch
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.InetAddress import java.net.InetAddress
val Throwable.readableMessage get() = localizedMessage ?: javaClass.name
private val parseNumericAddress by lazy { private val parseNumericAddress by lazy {
InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply { InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply {
isAccessible = true isAccessible = true
......
Subproject commit 7ad2df961505ecddb44984ce34ea86874cd2ecc4 Subproject commit b430124ed82973d4edf345dfadbe556f93377484
...@@ -97,6 +97,7 @@ ...@@ -97,6 +97,7 @@
<string name="action_export">Export to Clipboard</string> <string name="action_export">Export to Clipboard</string>
<string name="action_import">Import from Clipboard</string> <string name="action_import">Import from Clipboard</string>
<string name="action_import_file">Import from file…</string> <string name="action_import_file">Import from file…</string>
<string name="action_replace_file">Replace from file…</string>
<string name="action_export_msg">Successfully export!</string> <string name="action_export_msg">Successfully export!</string>
<string name="action_export_err">Failed to export.</string> <string name="action_export_err">Failed to export.</string>
<string name="action_import_msg">Successfully import!</string> <string name="action_import_msg">Successfully import!</string>
......
...@@ -5,6 +5,7 @@ import java.util.regex.Pattern ...@@ -5,6 +5,7 @@ import java.util.regex.Pattern
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'io.fabric' apply plugin: 'io.fabric'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
def getCurrentFlavor() { def getCurrentFlavor() {
String task = getGradle().getStartParameter().getTaskRequests().toString() String task = getGradle().getStartParameter().getTaskRequests().toString()
...@@ -49,6 +50,10 @@ android { ...@@ -49,6 +50,10 @@ android {
new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni") new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni")
} }
androidExtensions {
experimental = true
}
dependencies { dependencies {
implementation project(':core') implementation project(':core')
implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.browser:browser:1.0.0'
......
...@@ -88,8 +88,8 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { ...@@ -88,8 +88,8 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
portTransproxy.isEnabled = enabledTransproxy portTransproxy.isEnabled = enabledTransproxy
true true
} }
val listener: (Int) -> Unit = { val listener: (BaseService.State) -> Unit = {
if (it == BaseService.STOPPED) { if (it == BaseService.State.Stopped) {
tfo.isEnabled = true tfo.isEnabled = true
serviceMode.isEnabled = true serviceMode.isEnabled = true
portProxy.isEnabled = true portProxy.isEnabled = true
......
...@@ -23,6 +23,7 @@ package com.github.shadowsocks ...@@ -23,6 +23,7 @@ package com.github.shadowsocks
import android.app.Activity import android.app.Activity
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.VpnService import android.net.VpnService
import android.nfc.NdefMessage import android.nfc.NdefMessage
...@@ -30,6 +31,7 @@ import android.nfc.NfcAdapter ...@@ -30,6 +31,7 @@ import android.nfc.NfcAdapter
import android.os.Bundle import android.os.Bundle
import android.os.DeadObjectException import android.os.DeadObjectException
import android.os.Handler import android.os.Handler
import android.os.Parcelable
import android.util.Log import android.util.Log
import android.view.KeyCharacterMap import android.view.KeyCharacterMap
import android.view.KeyEvent import android.view.KeyEvent
...@@ -51,6 +53,8 @@ import com.github.shadowsocks.aidl.TrafficStats ...@@ -51,6 +53,8 @@ import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.plugin.Empty
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
...@@ -58,6 +62,7 @@ import com.github.shadowsocks.widget.ServiceButton ...@@ -58,6 +62,7 @@ import com.github.shadowsocks.widget.ServiceButton
import com.github.shadowsocks.widget.StatsBar import com.github.shadowsocks.widget.StatsBar
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.parcel.Parcelize
class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPreferenceDataStoreChangeListener, class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPreferenceDataStoreChangeListener,
NavigationView.OnNavigationItemSelectedListener { NavigationView.OnNavigationItemSelectedListener {
...@@ -65,7 +70,18 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -65,7 +70,18 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
private const val TAG = "ShadowsocksMainActivity" private const val TAG = "ShadowsocksMainActivity"
private const val REQUEST_CONNECT = 1 private const val REQUEST_CONNECT = 1
var stateListener: ((Int) -> Unit)? = null var stateListener: ((BaseService.State) -> Unit)? = null
}
@Parcelize
data class ProfilesArg(val profiles: List<Profile>) : Parcelable
class ImportProfilesDialogFragment : AlertDialogFragment<ProfilesArg, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.add_profile_dialog)
setPositiveButton(R.string.yes) { _, _ -> arg.profiles.forEach { ProfileManager.createProfile(it) } }
setNegativeButton(R.string.no, null)
setMessage(arg.profiles.joinToString("\n"))
}
} }
// UI // UI
...@@ -91,12 +107,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -91,12 +107,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
} }
// service // service
var state = BaseService.IDLE var state = BaseService.State.Idle
override fun stateChanged(state: Int, profileName: String?, msg: String?) = changeState(state, msg, true) override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = changeState(state, msg, true)
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
if (profileId == 0L) this@MainActivity.stats.updateTraffic( if (profileId == 0L) this@MainActivity.stats.updateTraffic(
stats.txRate, stats.rxRate, stats.txTotal, stats.rxTotal) stats.txRate, stats.rxRate, stats.txTotal, stats.rxTotal)
if (state != BaseService.STOPPING) { if (state != BaseService.State.Stopping) {
(supportFragmentManager.findFragmentById(R.id.fragment_holder) as? ToolbarFragment) (supportFragmentManager.findFragmentById(R.id.fragment_holder) as? ToolbarFragment)
?.onTrafficUpdated(profileId, stats) ?.onTrafficUpdated(profileId, stats)
} }
...@@ -105,7 +121,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -105,7 +121,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
ProfilesFragment.instance?.onTrafficPersisted(profileId) ProfilesFragment.instance?.onTrafficPersisted(profileId)
} }
private fun changeState(state: Int, msg: String? = null, animate: Boolean = false) { private fun changeState(state: BaseService.State, msg: String? = null, animate: Boolean = false) {
fab.changeState(state, animate) fab.changeState(state, animate)
stats.changeState(state) stats.changeState(state)
if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show() if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show()
...@@ -115,7 +131,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -115,7 +131,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
} }
private fun toggle() = when { private fun toggle() = when {
state == BaseService.CONNECTED -> Core.stopService() state.canStop -> Core.stopService()
DataStore.serviceMode == Key.modeVpn -> { DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
...@@ -127,11 +143,11 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -127,11 +143,11 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
private val handler = Handler() private val handler = Handler()
private val connection = ShadowsocksConnection(handler, true) private val connection = ShadowsocksConnection(handler, true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try { override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
service.state BaseService.State.values()[service.state]
} catch (_: DeadObjectException) { } catch (_: DeadObjectException) {
BaseService.IDLE BaseService.State.Idle
}) })
override fun onServiceDisconnected() = changeState(BaseService.IDLE) override fun onServiceDisconnected() = changeState(BaseService.State.Idle)
override fun onBinderDied() { override fun onBinderDied() {
connection.disconnect(this) connection.disconnect(this)
connection.connect(this, this) connection.connect(this, this)
...@@ -152,7 +168,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -152,7 +168,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main) setContentView(R.layout.layout_main)
stats = findViewById(R.id.stats) stats = findViewById(R.id.stats)
stats.setOnClickListener { if (state == BaseService.CONNECTED) stats.testConnection() } stats.setOnClickListener { if (state == BaseService.State.Connected) stats.testConnection() }
drawer = findViewById(R.id.drawer) drawer = findViewById(R.id.drawer)
navigation = findViewById(R.id.navigation) navigation = findViewById(R.id.navigation)
navigation.setNavigationItemSelectedListener(this) navigation.setNavigationItemSelectedListener(this)
...@@ -164,7 +180,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -164,7 +180,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
fab = findViewById(R.id.fab) fab = findViewById(R.id.fab)
fab.setOnClickListener { toggle() } fab.setOnClickListener { toggle() }
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.State.Idle) // reset everything to init state
connection.connect(this, this) connection.connect(this, this)
DataStore.publicStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
...@@ -188,17 +204,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -188,17 +204,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
} }
if (sharedStr.isNullOrEmpty()) return if (sharedStr.isNullOrEmpty()) return
val profiles = Profile.findAllUrls(sharedStr, Core.currentProfile?.first).toList() val profiles = Profile.findAllUrls(sharedStr, Core.currentProfile?.first).toList()
if (profiles.isEmpty()) { if (profiles.isEmpty()) snackbar().setText(R.string.profile_invalid_input).show()
snackbar().setText(R.string.profile_invalid_input).show() else ImportProfilesDialogFragment().withArg(ProfilesArg(profiles)).show(supportFragmentManager, null)
return
}
AlertDialog.Builder(this)
.setTitle(R.string.add_profile_dialog)
.setPositiveButton(R.string.yes) { _, _ -> profiles.forEach { ProfileManager.createProfile(it) } }
.setNegativeButton(R.string.no, null)
.setMessage(profiles.joinToString("\n"))
.create()
.show()
} }
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
......
...@@ -21,12 +21,15 @@ ...@@ -21,12 +21,15 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.Activity import android.app.Activity
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.plugin.Empty
import com.github.shadowsocks.plugin.PluginContract import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
...@@ -35,6 +38,15 @@ class ProfileConfigActivity : AppCompatActivity() { ...@@ -35,6 +38,15 @@ class ProfileConfigActivity : AppCompatActivity() {
const val REQUEST_CODE_PLUGIN_HELP = 1 const val REQUEST_CODE_PLUGIN_HELP = 1
} }
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.unsaved_changes_prompt)
setPositiveButton(R.string.yes, listener)
setNegativeButton(R.string.no, listener)
setNeutralButton(android.R.string.cancel, null)
}
}
private val child by lazy { supportFragmentManager.findFragmentById(R.id.content) as ProfileConfigFragment } private val child by lazy { supportFragmentManager.findFragmentById(R.id.content) as ProfileConfigFragment }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
...@@ -59,13 +71,8 @@ class ProfileConfigActivity : AppCompatActivity() { ...@@ -59,13 +71,8 @@ class ProfileConfigActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)
override fun onBackPressed() { override fun onBackPressed() {
if (DataStore.dirty) AlertDialog.Builder(this) if (DataStore.dirty) UnsavedChangesDialogFragment().show(child, ProfileConfigFragment.REQUEST_UNSAVED_CHANGES)
.setTitle(R.string.unsaved_changes_prompt) else super.onBackPressed()
.setPositiveButton(R.string.yes) { _, _ -> child.saveAndExit() }
.setNegativeButton(R.string.no) { _, _ -> finish() }
.setNeutralButton(android.R.string.cancel, null)
.create()
.show() else super.onBackPressed()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
......
...@@ -22,23 +22,24 @@ package com.github.shadowsocks ...@@ -22,23 +22,24 @@ package com.github.shadowsocks
import android.app.Activity import android.app.Activity
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.preference.* import androidx.preference.*
import com.github.shadowsocks.Core.app import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.*
import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.plugin.PluginManager
import com.github.shadowsocks.plugin.PluginOptions
import com.github.shadowsocks.preference.* import com.github.shadowsocks.preference.*
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.readableMessage
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.parcel.Parcelize
class ProfileConfigFragment : PreferenceFragmentCompat(), class ProfileConfigFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener { Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener {
...@@ -46,6 +47,20 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -46,6 +47,20 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0) override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0)
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1 private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
const val REQUEST_UNSAVED_CHANGES = 2
}
@Parcelize
data class ProfileIdArg(val profileId: Long) : Parcelable
class DeleteConfirmationDialogFragment : AlertDialogFragment<ProfileIdArg, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.delete_confirm_prompt)
setPositiveButton(R.string.yes) { _, _ ->
ProfileManager.delProfile(arg.profileId)
requireActivity().finish()
}
setNegativeButton(R.string.no, null)
}
} }
private var profileId = -1L private var profileId = -1L
...@@ -114,7 +129,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -114,7 +129,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
}.show(fragmentManager ?: return, Key.pluginConfigure) }.show(fragmentManager ?: return, Key.pluginConfigure)
} }
fun saveAndExit() { private fun saveAndExit() {
val profile = ProfileManager.getProfile(profileId) ?: Profile() val profile = ProfileManager.getProfile(profileId) ?: Profile()
profile.id = profileId profile.id = profileId
profile.deserialize() profile.deserialize()
...@@ -140,7 +155,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -140,7 +155,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
DataStore.dirty = true DataStore.dirty = true
true true
} catch (exc: RuntimeException) { } catch (exc: RuntimeException) {
Snackbar.make(view!!, exc.localizedMessage, Snackbar.LENGTH_LONG).show() Snackbar.make(view!!, exc.readableMessage, Snackbar.LENGTH_LONG).show()
false false
} }
...@@ -167,28 +182,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -167,28 +182,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_PLUGIN_CONFIGURE) when (resultCode) { when (requestCode) {
Activity.RESULT_OK -> { REQUEST_CODE_PLUGIN_CONFIGURE -> when (resultCode) {
val options = data?.getStringExtra(PluginContract.EXTRA_OPTIONS) Activity.RESULT_OK -> {
pluginConfigure.text = options val options = data?.getStringExtra(PluginContract.EXTRA_OPTIONS)
onPreferenceChange(null, options) pluginConfigure.text = options
onPreferenceChange(null, options)
}
PluginContract.RESULT_FALLBACK -> showPluginEditor()
}
REQUEST_UNSAVED_CHANGES -> when (resultCode) {
DialogInterface.BUTTON_POSITIVE -> saveAndExit()
DialogInterface.BUTTON_NEGATIVE -> requireActivity().finish()
} }
PluginContract.RESULT_FALLBACK -> showPluginEditor() else -> super.onActivityResult(requestCode, resultCode, data)
} else super.onActivityResult(requestCode, resultCode, data) }
} }
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_delete -> { R.id.action_delete -> {
val activity = requireActivity() DeleteConfirmationDialogFragment().withArg(ProfileIdArg(profileId)).show(this)
AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm_prompt)
.setPositiveButton(R.string.yes) { _, _ ->
ProfileManager.delProfile(profileId)
activity.finish()
}
.setNegativeButton(R.string.no, null)
.create()
.show()
true true
} }
R.id.action_apply -> { R.id.action_apply -> {
......
...@@ -49,12 +49,12 @@ import com.github.shadowsocks.preference.DataStore ...@@ -49,12 +49,12 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.datas import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
import com.google.android.gms.ads.AdRequest import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView import com.google.android.gms.ads.AdView
import net.glxn.qrgen.android.QRCode import net.glxn.qrgen.android.QRCode
import org.json.JSONArray
class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
companion object { companion object {
...@@ -65,21 +65,16 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -65,21 +65,16 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private const val KEY_URL = "com.github.shadowsocks.QRCodeDialog.KEY_URL" private const val KEY_URL = "com.github.shadowsocks.QRCodeDialog.KEY_URL"
private const val REQUEST_IMPORT_PROFILES = 1 private const val REQUEST_IMPORT_PROFILES = 1
private const val REQUEST_REPLACE_PROFILES = 3
private const val REQUEST_EXPORT_PROFILES = 2 private const val REQUEST_EXPORT_PROFILES = 2
} }
/** /**
* Is ProfilesFragment editable at all. * Is ProfilesFragment editable at all.
*/ */
private val isEnabled get() = when ((activity as MainActivity).state) { private val isEnabled get() = (activity as MainActivity).state.let { it.canStop || it == BaseService.State.Stopped }
BaseService.CONNECTED, BaseService.STOPPED -> true private fun isProfileEditable(id: Long) =
else -> false (activity as MainActivity).state == BaseService.State.Stopped || id !in Core.activeProfileIds
}
private fun isProfileEditable(id: Long) = when ((activity as MainActivity).state) {
BaseService.CONNECTED -> id !in Core.activeProfileIds
BaseService.STOPPED -> true
else -> false
}
@SuppressLint("ValidFragment") @SuppressLint("ValidFragment")
class QRCodeDialog() : DialogFragment() { class QRCodeDialog() : DialogFragment() {
...@@ -175,21 +170,20 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -175,21 +170,20 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
var adView = adView var adView = adView
if (item.host == "198.199.101.152") { if (item.host == "198.199.101.152") {
if (adView == null) { if (adView == null) {
val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT) AdSize.SMART_BANNER.getHeightInPixels(context))
params.gravity = Gravity.CENTER_HORIZONTAL params.gravity = Gravity.CENTER_HORIZONTAL
adView = AdView(context) adView = AdView(context)
adView.layoutParams = params adView.layoutParams = params
adView.adUnitId = "ca-app-pub-9097031975646651/7760346322" adView.adUnitId = "ca-app-pub-9097031975646651/7760346322"
adView.adSize = AdSize.FLUID adView.adSize = AdSize.SMART_BANNER
val padding = context.resources.getDimensionPixelOffset(R.dimen.profile_padding)
adView.setPadding(padding, 0, 0, padding)
itemView.findViewById<LinearLayout>(R.id.content).addView(adView) itemView.findViewById<LinearLayout>(R.id.content).addView(adView)
// Load Ad // Load Ad
val adBuilder = AdRequest.Builder() val adBuilder = AdRequest.Builder()
adBuilder.addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208") adBuilder.addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208")
adBuilder.addTestDevice("7509D18EB8AF82F915874FEF53877A64")
adView.loadAd(adBuilder.build()) adView.loadAd(adBuilder.build())
this.adView = adView this.adView = adView
} else adView.visibility = View.VISIBLE } else adView.visibility = View.VISIBLE
...@@ -203,7 +197,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -203,7 +197,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
Core.switchProfile(item.id) Core.switchProfile(item.id)
profilesAdapter.refreshId(old) profilesAdapter.refreshId(old)
itemView.isSelected = true itemView.isSelected = true
if (activity.state == BaseService.CONNECTED) Core.reloadService() if (activity.state.canStop) Core.reloadService()
} }
} }
...@@ -296,6 +290,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -296,6 +290,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
notifyItemRemoved(index) notifyItemRemoved(index)
if (profileId == DataStore.profileId) DataStore.profileId = 0 // switch to null profile if (profileId == DataStore.profileId) DataStore.profileId = 0 // switch to null profile
} }
override fun onCleared() {
profiles.clear()
notifyDataSetChanged()
}
} }
private var selectedItem: ProfileViewHolder? = null private var selectedItem: ProfileViewHolder? = null
...@@ -383,12 +382,20 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -383,12 +382,20 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
R.id.action_import_file -> { R.id.action_import_file -> {
startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply { startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) type = "application/*"
type = "application/json"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
}, REQUEST_IMPORT_PROFILES) }, REQUEST_IMPORT_PROFILES)
true true
} }
R.id.action_replace_file -> {
startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
}, REQUEST_REPLACE_PROFILES)
true
}
R.id.action_manual_settings -> { R.id.action_manual_settings -> {
startConfig(ProfileManager.createProfile( startConfig(ProfileManager.createProfile(
Profile().also { Core.currentProfile?.first?.copyFeatureSettingsTo(it) })) Profile().also { Core.currentProfile?.first?.copyFeatureSettingsTo(it) }))
...@@ -404,7 +411,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -404,7 +411,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
R.id.action_export_file -> { R.id.action_export_file -> {
startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json" type = "application/json"
putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited
}, REQUEST_EXPORT_PROFILES) }, REQUEST_EXPORT_PROFILES)
...@@ -414,9 +420,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -414,9 +420,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
} }
private fun startFilesForResult(intent: Intent?, requestCode: Int) { private fun startFilesForResult(intent: Intent, requestCode: Int) {
try { try {
startActivityForResult(intent, requestCode) startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
return return
} catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { }
(activity as MainActivity).snackbar(getString(R.string.file_manager_missing)).show() (activity as MainActivity).snackbar(getString(R.string.file_manager_missing)).show()
...@@ -426,31 +432,34 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -426,31 +432,34 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
if (resultCode != Activity.RESULT_OK) super.onActivityResult(requestCode, resultCode, data) if (resultCode != Activity.RESULT_OK) super.onActivityResult(requestCode, resultCode, data)
else when (requestCode) { else when (requestCode) {
REQUEST_IMPORT_PROFILES -> { REQUEST_IMPORT_PROFILES -> {
val feature = Core.currentProfile?.first
var success = false
val activity = activity as MainActivity val activity = activity as MainActivity
for (uri in data!!.datas) try { try {
Profile.parseJson(activity.contentResolver.openInputStream(uri)!!.bufferedReader().readText(), ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
feature) { activity.contentResolver.openInputStream(it)
ProfileManager.createProfile(it) })
success = true
}
} catch (e: Exception) { } catch (e: Exception) {
printLog(e) activity.snackbar(e.readableMessage).show()
}
}
REQUEST_REPLACE_PROFILES -> {
val activity = activity as MainActivity
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it)
}, true)
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
} }
activity.snackbar().setText(if (success) R.string.action_import_msg else R.string.action_import_err)
.show()
} }
REQUEST_EXPORT_PROFILES -> { REQUEST_EXPORT_PROFILES -> {
val profiles = ProfileManager.getAllProfiles() val profiles = ProfileManager.serializeToJson()
if (profiles != null) try { if (profiles != null) try {
val lookup = LongSparseArray<Profile>(profiles.size).apply { profiles.forEach { put(it.id, it) } }
requireContext().contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use { requireContext().contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use {
it.write(JSONArray(profiles.map { it.toJson(lookup) }.toTypedArray()).toString(2)) it.write(profiles.toString(2))
} }
} catch (e: Exception) { } catch (e: Exception) {
printLog(e) printLog(e)
(activity as MainActivity).snackbar(e.localizedMessage).show() (activity as MainActivity).snackbar(e.readableMessage).show()
} }
} }
else -> super.onActivityResult(requestCode, resultCode, data) else -> super.onActivityResult(requestCode, resultCode, data)
......
...@@ -55,14 +55,15 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback { ...@@ -55,14 +55,15 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback {
} }
override fun onServiceConnected(service: IShadowsocksService) { override fun onServiceConnected(service: IShadowsocksService) {
when (service.state) { val state = BaseService.State.values()[service.state]
BaseService.STOPPED -> Core.startService() when {
BaseService.CONNECTED -> Core.stopService() state.canStop -> Core.stopService()
state == BaseService.State.Stopped -> Core.startService()
} }
finish() finish()
} }
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) { }
override fun onDestroy() { override fun onDestroy() {
connection.disconnect(this) connection.disconnect(this)
......
...@@ -22,9 +22,12 @@ package com.github.shadowsocks.acl ...@@ -22,9 +22,12 @@ package com.github.shadowsocks.acl
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.* import android.view.*
...@@ -43,10 +46,13 @@ import com.github.shadowsocks.R ...@@ -43,10 +46,13 @@ import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.net.Subnet import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.utils.resolveResourceId import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.parcel.Parcelize
import java.net.IDN import java.net.IDN
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL
...@@ -55,6 +61,8 @@ import java.util.regex.PatternSyntaxException ...@@ -55,6 +61,8 @@ import java.util.regex.PatternSyntaxException
class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback { class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback {
companion object { companion object {
private const val REQUEST_CODE_ADD = 1
private const val REQUEST_CODE_EDIT = 2
private const val TEMPLATE_REGEX_DOMAIN = "(^|\\.)%s$" private const val TEMPLATE_REGEX_DOMAIN = "(^|\\.)%s$"
private const val SELECTED_SUBNETS = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_SUBNETS" private const val SELECTED_SUBNETS = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_SUBNETS"
...@@ -63,6 +71,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -63,6 +71,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
// unescaped: (?<=^(\(\^\|\\\.\)|\^\(\.\*\\\.\)\?)).*(?=\$$) // unescaped: (?<=^(\(\^\|\\\.\)|\^\(\.\*\\\.\)\?)).*(?=\$$)
private val domainPattern = "(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)".toRegex() private val domainPattern = "(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)".toRegex()
@Suppress("FunctionName")
private fun AclItem(item: Any) = when (item) {
is String -> AclItem(item, false)
is Subnet -> AclItem(item.toString(), false)
is URL -> AclItem(item.toString(), true)
else -> throw IllegalArgumentException("item")
}
} }
private enum class Template { private enum class Template {
...@@ -70,53 +86,50 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -70,53 +86,50 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
Domain, Domain,
Url; Url;
} }
private inner class AclRuleDialog(item: Any = "") : TextWatcher, AdapterView.OnItemSelectedListener { @Parcelize
val builder: AlertDialog.Builder data class AclItem(val item: String = "", val isUrl: Boolean = false) : Parcelable {
val templateSelector: Spinner fun toAny() = if (isUrl) URL(item) else Subnet.fromString(item) ?: item
val editText: EditText }
private val inputLayout: TextInputLayout @Parcelize
private lateinit var dialog: AlertDialog data class AclEditResult(val edited: AclItem, val replacing: AclItem) : Parcelable
class AclRuleDialogFragment : AlertDialogFragment<AclItem, AclEditResult>(),
TextWatcher, AdapterView.OnItemSelectedListener {
private lateinit var templateSelector: Spinner
private lateinit var editText: EditText
private lateinit var inputLayout: TextInputLayout
private lateinit var positive: Button private lateinit var positive: Button
init { override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
val activity = requireActivity() val activity = requireActivity()
val view = activity.layoutInflater.inflate(R.layout.dialog_acl_rule, null) val view = activity.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
templateSelector = view.findViewById(R.id.template_selector) templateSelector = view.findViewById(R.id.template_selector)
editText = view.findViewById(R.id.content) editText = view.findViewById(R.id.content)
inputLayout = view.findViewById(R.id.content_layout) inputLayout = view.findViewById(R.id.content_layout)
when (item) { templateSelector.setSelection(Template.Generic.ordinal)
is String -> { editText.setText(arg.item)
val match = domainPattern.find(item) when {
arg.isUrl -> templateSelector.setSelection(Template.Url.ordinal)
Subnet.fromString(arg.item) == null -> {
val match = domainPattern.find(arg.item)
if (match != null) { if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal) templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."), editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES)) IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES))
} else {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item)
} }
} }
is URL -> {
templateSelector.setSelection(Template.Url.ordinal)
editText.setText(item.toString())
}
else -> {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item.toString())
}
} }
templateSelector.onItemSelectedListener = this templateSelector.onItemSelectedListener = this@AclRuleDialogFragment
editText.addTextChangedListener(this) editText.addTextChangedListener(this@AclRuleDialogFragment)
builder = AlertDialog.Builder(activity) setTitle(R.string.edit_rule)
.setTitle(R.string.edit_rule) setPositiveButton(android.R.string.ok, listener)
.setNegativeButton(android.R.string.cancel, null) setNegativeButton(android.R.string.cancel, null)
.setView(view) if (arg.item.isNotEmpty()) setNeutralButton(R.string.delete, listener)
} setView(view)
}
fun show() {
dialog = builder.create() override fun onStart() {
dialog.show() super.onStart()
positive = dialog.getButton(AlertDialog.BUTTON_POSITIVE) positive = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
validate() validate()
} }
...@@ -134,7 +147,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -134,7 +147,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
if (Subnet.fromString(this) == null) toPattern() if (Subnet.fromString(this) == null) toPattern()
true true
} catch (e: PatternSyntaxException) { } catch (e: PatternSyntaxException) {
message = e.localizedMessage message = e.readableMessage
false false
} }
} }
...@@ -142,7 +155,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -142,7 +155,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
IDN.toASCII(value.toString(), IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES) IDN.toASCII(value.toString(), IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES)
true true
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
message = e.cause?.localizedMessage ?: e.localizedMessage message = e.cause?.readableMessage ?: e.readableMessage
false false
} }
Template.Url -> try { Template.Url -> try {
...@@ -150,22 +163,21 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -150,22 +163,21 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
if ("http".equals(url.protocol, true)) message = getString(R.string.cleartext_http_warning) if ("http".equals(url.protocol, true)) message = getString(R.string.cleartext_http_warning)
true true
} catch (e: MalformedURLException) { } catch (e: MalformedURLException) {
message = e.localizedMessage message = e.readableMessage
false false
} }
} }
inputLayout.error = message inputLayout.error = message
} }
fun add(): Int? { override val ret get() = AclEditResult(editText.text.toString().let { text ->
val text = editText.text.toString() when (Template.values()[templateSelector.selectedItemPosition]) {
return when (Template.values()[templateSelector.selectedItemPosition]) { Template.Generic -> AclItem(text)
Template.Generic -> adapter.addToProxy(text) Template.Domain -> AclItem(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
Template.Domain -> adapter.addHostname(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\."))) IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\.")))
Template.Url -> adapter.addURL(URL(text)) Template.Url -> AclItem(text, true)
} }
} }, arg)
} }
private inner class AclRuleViewHolder(view: View) : RecyclerView.ViewHolder(view), private inner class AclRuleViewHolder(view: View) : RecyclerView.ViewHolder(view),
...@@ -197,22 +209,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -197,22 +209,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {
if (selectedItems.isNotEmpty()) onLongClick(v) else { if (selectedItems.isNotEmpty()) onLongClick(v)
val dialog = AclRuleDialog(item) else AclRuleDialogFragment().withArg(AclItem(item)).show(this@CustomRulesFragment, REQUEST_CODE_EDIT)
dialog.builder
.setNeutralButton(R.string.delete) { _, _ ->
adapter.remove(item)
undoManager.remove(Pair(-1, item))
}
.setPositiveButton(android.R.string.ok) { _, _ ->
adapter.remove(item)
val index = dialog.add() ?: adapter.add(item)
if (index != null) list.post { list.scrollToPosition(index) }
}
dialog.show()
}
} }
override fun onLongClick(p0: View?): Boolean { override fun onLongClick(v: View?): Boolean {
if (!selectedItems.add(item)) selectedItems.remove(item) // toggle if (!selectedItems.add(item)) selectedItems.remove(item) // toggle
onSelectedItemsUpdated() onSelectedItemsUpdated()
itemView.isSelected = !itemView.isSelected itemView.isSelected = !itemView.isSelected
...@@ -346,11 +346,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -346,11 +346,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
} }
} }
private val isEnabled get() = when ((activity as MainActivity).state) { private val isEnabled get() = (activity as MainActivity).state == BaseService.State.Stopped ||
BaseService.CONNECTED -> Core.currentProfile?.first?.route != Acl.CUSTOM_RULES Core.currentProfile?.first?.route != Acl.CUSTOM_RULES
BaseService.STOPPED -> true
else -> false
}
private val selectedItems = HashSet<Any>() private val selectedItems = HashSet<Any>()
private val adapter by lazy { AclRulesAdapter() } private val adapter by lazy { AclRulesAdapter() }
...@@ -427,9 +424,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -427,9 +424,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) { override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_manual_settings -> { R.id.action_manual_settings -> {
val dialog = AclRuleDialog() AclRuleDialogFragment().withArg(AclItem()).show(this, REQUEST_CODE_ADD)
dialog.builder.setPositiveButton(android.R.string.ok) { _, _ -> dialog.add() }
dialog.show()
true true
} }
R.id.action_import_clipboard -> { R.id.action_import_clipboard -> {
...@@ -451,6 +446,25 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -451,6 +446,25 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
else -> false else -> false
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val editing = when (requestCode) {
REQUEST_CODE_ADD -> false
REQUEST_CODE_EDIT -> true
else -> return super.onActivityResult(requestCode, resultCode, data)
}
val ret by lazy { AlertDialogFragment.getRet<AclEditResult>(data!!) }
when (resultCode) {
DialogInterface.BUTTON_POSITIVE -> {
if (editing) adapter.remove(ret.replacing.toAny())
adapter.add(ret.edited.toAny())?.also { list.post { list.scrollToPosition(it) } }
}
DialogInterface.BUTTON_NEUTRAL -> ret.replacing.toAny().let { item ->
adapter.remove(item)
undoManager.remove(Pair(-1, item))
}
}
}
override fun onDetach() { override fun onDetach() {
undoManager.flush() undoManager.flush()
mode?.finish() mode?.finish()
......
...@@ -41,9 +41,9 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -41,9 +41,9 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
private var tapPending = false private var tapPending = false
private val connection = ShadowsocksConnection() private val connection = ShadowsocksConnection()
override fun stateChanged(state: Int, profileName: String?, msg: String?) = updateTile(state) { profileName } override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = updateTile(state) { profileName }
override fun onServiceConnected(service: IShadowsocksService) { override fun onServiceConnected(service: IShadowsocksService) {
updateTile(service.state) { service.profileName } updateTile(BaseService.State.values()[service.state]) { service.profileName }
if (tapPending) { if (tapPending) {
tapPending = false tapPending = false
onClick() onClick()
...@@ -63,23 +63,28 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -63,23 +63,28 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle() if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle()
} }
private fun updateTile(serviceState: Int, profileName: () -> String?) { private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) {
qsTile?.apply { qsTile?.apply {
label = null label = null
when (serviceState) { when (serviceState) {
BaseService.STOPPED -> { BaseService.State.Idle -> throw IllegalStateException("serviceState")
icon = iconIdle BaseService.State.Connecting -> {
state = Tile.STATE_INACTIVE icon = iconBusy
state = Tile.STATE_ACTIVE
} }
BaseService.CONNECTED -> { BaseService.State.Connected -> {
icon = iconConnected icon = iconConnected
if (!keyguard.isDeviceLocked) label = profileName() if (!keyguard.isDeviceLocked) label = profileName()
state = Tile.STATE_ACTIVE state = Tile.STATE_ACTIVE
} }
else -> { BaseService.State.Stopping -> {
icon = iconBusy icon = iconBusy
state = Tile.STATE_UNAVAILABLE state = Tile.STATE_UNAVAILABLE
} }
BaseService.State.Stopped -> {
icon = iconIdle
state = Tile.STATE_INACTIVE
}
} }
label = label ?: getString(R.string.app_name) label = label ?: getString(R.string.app_name)
updateTile() updateTile()
...@@ -88,9 +93,11 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -88,9 +93,11 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
private fun toggle() { private fun toggle() {
val service = connection.service val service = connection.service
if (service == null) tapPending = true else when (service.state) { if (service == null) tapPending = true else BaseService.State.values()[service.state].let { state ->
BaseService.STOPPED -> Core.startService() when {
BaseService.CONNECTED -> Core.stopService() state.canStop -> Core.stopService()
state == BaseService.State.Stopped -> Core.startService()
}
} }
} }
} }
...@@ -68,14 +68,14 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute ...@@ -68,14 +68,14 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
return drawableState return drawableState
} }
fun changeState(state: Int, animate: Boolean) { fun changeState(state: BaseService.State, animate: Boolean) {
when (state) { when (state) {
BaseService.CONNECTING -> changeState(iconConnecting, animate) BaseService.State.Connecting -> changeState(iconConnecting, animate)
BaseService.CONNECTED -> changeState(iconConnected, animate) BaseService.State.Connected -> changeState(iconConnected, animate)
BaseService.STOPPING -> changeState(iconStopping, animate) BaseService.State.Stopping -> changeState(iconStopping, animate)
else -> changeState(iconStopped, animate) else -> changeState(iconStopped, animate)
} }
if (state == BaseService.CONNECTED) { if (state == BaseService.State.Connected) {
checked = true checked = true
TooltipCompat.setTooltipText(this, context.getString(R.string.stop)) TooltipCompat.setTooltipText(this, context.getString(R.string.stop))
} else { } else {
...@@ -83,7 +83,7 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute ...@@ -83,7 +83,7 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
TooltipCompat.setTooltipText(this, context.getString(R.string.connect)) TooltipCompat.setTooltipText(this, context.getString(R.string.connect))
} }
refreshDrawableState() refreshDrawableState()
isEnabled = state == BaseService.CONNECTED || state == BaseService.STOPPED isEnabled = state.canStop || state == BaseService.State.Stopped
} }
private fun counters(a: AnimatedVectorDrawableCompat, b: AnimatedVectorDrawableCompat): Boolean = private fun counters(a: AnimatedVectorDrawableCompat, b: AnimatedVectorDrawableCompat): Boolean =
......
...@@ -65,15 +65,15 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? ...@@ -65,15 +65,15 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet?
super.setOnClickListener(l) super.setOnClickListener(l)
} }
fun changeState(state: Int) { fun changeState(state: BaseService.State) {
val activity = context as MainActivity val activity = context as MainActivity
if (state != BaseService.CONNECTED) { if (state != BaseService.State.Connected) {
updateTraffic(0, 0, 0, 0) updateTraffic(0, 0, 0, 0)
tester.status.removeObservers(activity) tester.status.removeObservers(activity)
if (state != BaseService.IDLE) tester.invalidate() if (state != BaseService.State.Idle) tester.invalidate()
statusText.setText(when (state) { statusText.setText(when (state) {
BaseService.CONNECTING -> R.string.connecting BaseService.State.Connecting -> R.string.connecting
BaseService.STOPPING -> R.string.stopping BaseService.State.Stopping -> R.string.stopping
else -> R.string.not_connected else -> R.string.not_connected
}) })
} else { } else {
......
...@@ -18,6 +18,11 @@ ...@@ -18,6 +18,11 @@
android:id="@+id/action_import_file" android:id="@+id/action_import_file"
android:alphabeticShortcut="o" android:alphabeticShortcut="o"
android:title="@string/action_import_file"/> android:title="@string/action_import_file"/>
<item
android:id="@+id/action_replace_file"
android:alphabeticShortcut="o"
android:title="@string/action_replace_file"
app:alphabeticModifiers="CTRL|SHIFT"/>
<item <item
android:id="@+id/action_manual_settings" android:id="@+id/action_manual_settings"
android:title="@string/add_profile_methods_manual_settings" android:title="@string/add_profile_methods_manual_settings"
......
* 1.2.0:
* New helper class `AlertDialogFragment` for creating `AlertDialog` that persists through configuration changes;
* Dependency update: `com.google.android.material:material:1.1.0-alpha03`.
* 1.1.0: * 1.1.0:
* Having control characters in plugin options is no longer allowed. * Having control characters in plugin options is no longer allowed.
If this breaks your plugin, you are doing it wrong. If this breaks your plugin, you are doing it wrong.
......
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.vanniktech.maven.publish'
apply from: 'gradle-mvn-push.gradle' @SuppressWarnings("GroovyUnusedDeclaration")
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
@SuppressWarnings("GroovyUnusedDeclaration")
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
android { android {
buildToolsVersion rootProject.buildToolsVersion buildToolsVersion rootProject.buildToolsVersion
...@@ -10,10 +20,10 @@ android { ...@@ -10,10 +20,10 @@ android {
defaultConfig { defaultConfig {
minSdkVersion rootProject.minSdkVersion minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion targetSdkVersion rootProject.sdkVersion
versionCode 8 versionCode Integer.parseInt(VERSION_CODE)
versionName "1.1.0" versionName VERSION_NAME
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.testgetRepositoryPassword().runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
...@@ -23,6 +33,21 @@ android { ...@@ -23,6 +33,21 @@ android {
} }
} }
androidExtensions {
experimental = true
}
mavenPublish {
targets {
uploadArchives {
releaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
snapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
repositoryUsername = getRepositoryUsername()
repositoryPassword = getRepositoryPassword()
}
}
}
dependencies { dependencies {
api 'androidx.core:core-ktx:1.0.1' api 'androidx.core:core-ktx:1.0.1'
api 'com.google.android.material:material:1.1.0-alpha03' api 'com.google.android.material:material:1.1.0-alpha03'
......
/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'maven'
apply plugin: 'signing'
@SuppressWarnings(["GroovyUnusedDeclaration", "GrMethodMayBeStatic"])
def isReleaseBuild() {
return !android.defaultConfig.versionName.contains("SNAPSHOT")
}
@SuppressWarnings("GroovyUnusedDeclaration")
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
@SuppressWarnings("GroovyUnusedDeclaration")
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
@SuppressWarnings("GroovyUnusedDeclaration")
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
@SuppressWarnings("GroovyUnusedDeclaration")
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = 'com.github.shadowsocks'
pom.artifactId = 'plugin'
pom.version = android.defaultConfig.versionName
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name 'Shadowsocks Plugin'
packaging 'aar'
description 'Shadowsocks Plugin'
url 'https://github.com/shadowsocks/shadowsocks-android'
scm {
url 'https://github.com/shadowsocks/shadowsocks-android.git'
connection 'scm:git:git://github.com/shadowsocks/shadowsocks-android.git'
developerConnection 'scm:git:git@github.com:shadowsocks/shadowsocks-android.git'
}
licenses {
license {
name 'GPLv3'
url 'https://www.gnu.org/licenses/gpl-3.0.html'
distribution 'repo'
}
}
developers {
developer {
id 'madeye'
name 'Max Lv'
url 'https://github.com/madeye'
}
developer {
id 'Mygod'
name 'Mygod Studio'
url 'https://github.com/Mygod'
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
android.libraryVariants.all { variant ->
def javadocTask = task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
description "Generates Javadoc for $variant.name."
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
exclude '**/*.kt'
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
def jarJavadocTask = task("jar${variant.name.capitalize()}Javadoc", type: Jar) {
description "Generate Javadoc Jar for $variant.name"
classifier = 'javadoc'
from javadocTask.destinationDir
}
jarJavadocTask.dependsOn javadocTask
artifacts.add('archives', jarJavadocTask)
def jarSourceTask = task("jar${variant.name.capitalize()}Sources", type: Jar) {
description "Generates Java Sources for $variant.name."
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
artifacts.add('archives', jarSourceTask)
}
}
GROUP=com.github.shadowsocks
VERSION_NAME=1.2.0
VERSION_CODE=9
POM_ARTIFACT_ID=plugin
POM_NAME=Shadowsocks Plugin
POM_PACKAGING=aar
POM_DESCRIPTION=SIP003 plugin for Shadowsocks
POM_INCEPTION_YEAR=2018
POM_URL=https://github.com/shadowsocks/shadowsocks-android
POM_SCM_URL=https://github.com/shadowsocks/shadowsocks-android
POM_SCM_CONNECTION=scm:git:git://github.com/shadowsocks/shadowsocks-android.git
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/shadowsocks/shadowsocks-android.git
POM_LICENCE_NAME=The GNU General Public License v3.0
POM_LICENCE_URL=https://www.gnu.org/licenses/gpl-3.0.html
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=Mygod
POM_DEVELOPER_NAME=Mygod Studio
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.plugin
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.fragment.app.Fragment
import kotlinx.android.parcel.Parcelize
/**
* Based on: https://android.googlesource.com/platform/packages/apps/ExactCalculator/+/8c43f06/src/com/android/calculator2/AlertDialogFragment.java
*/
abstract class AlertDialogFragment<Arg : Parcelable, Ret : Parcelable> :
AppCompatDialogFragment(), DialogInterface.OnClickListener {
companion object {
private const val KEY_ARG = "arg"
private const val KEY_RET = "ret"
fun <T : Parcelable> getRet(data: Intent) = data.extras!!.getParcelable<T>(KEY_RET)!!
}
protected abstract fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener)
protected val arg by lazy { arguments!!.getParcelable<Arg>(KEY_ARG)!! }
protected open val ret: Ret? get() = null
fun withArg(arg: Arg) = apply { arguments = Bundle().apply { putParcelable(KEY_ARG, arg) } }
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =
AlertDialog.Builder(requireContext()).also { it.prepare(this) }.create()
override fun onClick(dialog: DialogInterface?, which: Int) {
targetFragment?.onActivityResult(targetRequestCode, which, ret?.let {
Intent().replaceExtras(Bundle().apply { putParcelable(KEY_RET, it) })
})
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
targetFragment?.onActivityResult(targetRequestCode, Activity.RESULT_CANCELED, null)
}
fun show(target: Fragment, requestCode: Int = 0, tag: String = javaClass.simpleName) {
setTargetFragment(target, requestCode)
show(target.fragmentManager ?: return, tag)
}
}
@Parcelize
class Empty : Parcelable
...@@ -38,7 +38,7 @@ class MainFragment : LeanbackSettingsFragmentCompat() { ...@@ -38,7 +38,7 @@ class MainFragment : LeanbackSettingsFragmentCompat() {
override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, pref: Preference?): Boolean { override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, pref: Preference?): Boolean {
if (pref?.key == Key.id) { if (pref?.key == Key.id) {
if ((childFragmentManager.findFragmentById(R.id.settings_preference_fragment_container) if ((childFragmentManager.findFragmentById(R.id.settings_preference_fragment_container)
as MainPreferenceFragment).state == BaseService.STOPPED) { as MainPreferenceFragment).state == BaseService.State.Stopped) {
startPreferenceFragment(ProfilesDialogFragment().apply { startPreferenceFragment(ProfilesDialogFragment().apply {
arguments = bundleOf(Pair(LeanbackPreferenceDialogFragmentCompat.ARG_KEY, Key.id)) arguments = bundleOf(Pair(LeanbackPreferenceDialogFragmentCompat.ARG_KEY, Key.id))
setTargetFragment(caller, 0) setTargetFragment(caller, 0)
......
...@@ -30,7 +30,6 @@ import android.os.DeadObjectException ...@@ -30,7 +30,6 @@ import android.os.DeadObjectException
import android.os.Handler import android.os.Handler
import android.text.format.Formatter import android.text.format.Formatter
import android.util.Log import android.util.Log
import android.util.LongSparseArray
import android.widget.Toast import android.widget.Toast
import androidx.leanback.preference.LeanbackPreferenceFragmentCompat import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
...@@ -44,7 +43,6 @@ import com.github.shadowsocks.aidl.IShadowsocksService ...@@ -44,7 +43,6 @@ import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.ShadowsocksConnection import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.aidl.TrafficStats import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.HttpsTest import com.github.shadowsocks.net.HttpsTest
import com.github.shadowsocks.net.TcpFastOpen import com.github.shadowsocks.net.TcpFastOpen
...@@ -54,13 +52,13 @@ import com.github.shadowsocks.preference.PortPreferenceListener ...@@ -54,13 +52,13 @@ import com.github.shadowsocks.preference.PortPreferenceListener
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.datas import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import org.json.JSONArray import com.github.shadowsocks.utils.readableMessage
class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksConnection.Callback, class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksConnection.Callback,
OnPreferenceDataStoreChangeListener { OnPreferenceDataStoreChangeListener {
companion object { companion object {
private const val REQUEST_CONNECT = 1 private const val REQUEST_CONNECT = 1
private const val REQUEST_IMPORT_PROFILES = 2 private const val REQUEST_REPLACE_PROFILES = 2
private const val REQUEST_EXPORT_PROFILES = 3 private const val REQUEST_EXPORT_PROFILES = 3
private const val TAG = "MainPreferenceFragment" private const val TAG = "MainPreferenceFragment"
} }
...@@ -88,9 +86,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -88,9 +86,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private lateinit var tester: HttpsTest private lateinit var tester: HttpsTest
// service // service
var state = BaseService.IDLE var state = BaseService.State.Idle
private set private set
override fun stateChanged(state: Int, profileName: String?, msg: String?) = changeState(state, msg) override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = changeState(state, msg)
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
if (profileId == 0L) requireContext().let { context -> if (profileId == 0L) requireContext().let { context ->
this.stats.summary = getString(R.string.stat_summary, this.stats.summary = getString(R.string.stat_summary,
...@@ -101,26 +99,26 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -101,26 +99,26 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
} }
} }
private fun changeState(state: Int, msg: String? = null) { private fun changeState(state: BaseService.State, msg: String? = null) {
fab.isEnabled = state == BaseService.STOPPED || state == BaseService.CONNECTED fab.isEnabled = state.canStop || state == BaseService.State.Stopped
fab.setTitle(when (state) { fab.setTitle(when (state) {
BaseService.CONNECTING -> R.string.connecting BaseService.State.Connecting -> R.string.connecting
BaseService.CONNECTED -> R.string.stop BaseService.State.Connected -> R.string.stop
BaseService.STOPPING -> R.string.stopping BaseService.State.Stopping -> R.string.stopping
else -> R.string.connect else -> R.string.connect
}) })
stats.setTitle(R.string.connection_test_pending) stats.setTitle(R.string.connection_test_pending)
stats.isVisible = state == BaseService.CONNECTED stats.isVisible = state == BaseService.State.Connected
if (state != BaseService.CONNECTED) { if (state != BaseService.State.Connected) {
trafficUpdated(0, TrafficStats()) trafficUpdated(0, TrafficStats())
tester.status.removeObservers(this) tester.status.removeObservers(this)
if (state != BaseService.IDLE) tester.invalidate() if (state != BaseService.State.Idle) tester.invalidate()
} else tester.status.observe(this, Observer { } else tester.status.observe(this, Observer {
it.retrieve(stats::setTitle) { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() } it.retrieve(stats::setTitle) { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
}) })
if (msg != null) Toast.makeText(requireContext(), getString(R.string.vpn_error, msg), Toast.LENGTH_SHORT).show() if (msg != null) Toast.makeText(requireContext(), getString(R.string.vpn_error, msg), Toast.LENGTH_SHORT).show()
this.state = state this.state = state
if (state == BaseService.STOPPED) { if (state == BaseService.State.Stopped) {
controlImport.isEnabled = true controlImport.isEnabled = true
tfo.isEnabled = true tfo.isEnabled = true
serviceMode.isEnabled = true serviceMode.isEnabled = true
...@@ -141,11 +139,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -141,11 +139,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private val handler = Handler() private val handler = Handler()
private val connection = ShadowsocksConnection(handler, true) private val connection = ShadowsocksConnection(handler, true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try { override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
service.state BaseService.State.values()[service.state]
} catch (_: DeadObjectException) { } catch (_: DeadObjectException) {
BaseService.IDLE BaseService.State.Idle
}) })
override fun onServiceDisconnected() = changeState(BaseService.IDLE) override fun onServiceDisconnected() = changeState(BaseService.State.Idle)
override fun onBinderDied() { override fun onBinderDied() {
connection.disconnect(requireContext()) connection.disconnect(requireContext())
connection.connect(requireContext(), this) connection.connect(requireContext(), this)
...@@ -203,7 +201,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -203,7 +201,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
} }
tester = ViewModelProviders.of(this).get() tester = ViewModelProviders.of(this).get()
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.State.Idle) // reset everything to init state
connection.connect(requireContext(), this) connection.connect(requireContext(), this)
DataStore.publicStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
} }
...@@ -228,7 +226,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -228,7 +226,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
fun startService() { fun startService() {
when { when {
state != BaseService.STOPPED -> return state != BaseService.State.Stopped -> return
DataStore.serviceMode == Key.modeVpn -> { DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(requireContext()) val intent = VpnService.prepare(requireContext())
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
...@@ -254,7 +252,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -254,7 +252,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
override fun onPreferenceTreeClick(preference: Preference?) = when (preference?.key) { override fun onPreferenceTreeClick(preference: Preference?) = when (preference?.key) {
Key.id -> { Key.id -> {
if (state == BaseService.CONNECTED) Core.stopService() if (state == BaseService.State.Connected) Core.stopService()
true true
} }
Key.controlStats -> { Key.controlStats -> {
...@@ -263,15 +261,14 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -263,15 +261,14 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
} }
Key.controlImport -> { Key.controlImport -> {
startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply { startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) type = "application/*"
type = "application/json"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}, REQUEST_IMPORT_PROFILES) putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
}, REQUEST_REPLACE_PROFILES)
true true
} }
Key.controlExport -> { Key.controlExport -> {
startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json" type = "application/json"
putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited
}, REQUEST_EXPORT_PROFILES) }, REQUEST_EXPORT_PROFILES)
...@@ -280,9 +277,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -280,9 +277,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
else -> super.onPreferenceTreeClick(preference) else -> super.onPreferenceTreeClick(preference)
} }
private fun startFilesForResult(intent: Intent?, requestCode: Int) { private fun startFilesForResult(intent: Intent, requestCode: Int) {
try { try {
startActivityForResult(intent, requestCode) startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
return return
} catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { }
Toast.makeText(requireContext(), R.string.file_manager_missing, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.file_manager_missing, Toast.LENGTH_SHORT).show()
...@@ -294,41 +291,30 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -294,41 +291,30 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
Toast.makeText(requireContext(), R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data") Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
} }
REQUEST_IMPORT_PROFILES -> { REQUEST_REPLACE_PROFILES -> {
if (resultCode != Activity.RESULT_OK) return if (resultCode != Activity.RESULT_OK) return
val profiles = ProfileManager.getAllProfiles()?.associateBy { it.formattedAddress }
val feature = profiles?.values?.singleOrNull { it.id == DataStore.profileId }
val lazyClear = lazy { ProfileManager.clear() }
val context = requireContext() val context = requireContext()
for (uri in data!!.datas) try { try {
Profile.parseJson(context.contentResolver.openInputStream(uri)!!.bufferedReader().readText(), ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
feature) { context.contentResolver.openInputStream(it)
lazyClear.value }, true)
// if two profiles has the same address, treat them as the same profile and copy stats over
profiles?.get(it.formattedAddress)?.apply {
it.tx = tx
it.rx = rx
}
ProfileManager.createProfile(it)
}
} catch (e: Exception) { } catch (e: Exception) {
printLog(e) printLog(e)
Toast.makeText(context, e.localizedMessage, Toast.LENGTH_SHORT).show() Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
} }
populateProfiles() populateProfiles()
} }
REQUEST_EXPORT_PROFILES -> { REQUEST_EXPORT_PROFILES -> {
if (resultCode != Activity.RESULT_OK) return if (resultCode != Activity.RESULT_OK) return
val profiles = ProfileManager.getAllProfiles() val profiles = ProfileManager.serializeToJson()
val context = requireContext() val context = requireContext()
if (profiles != null) try { if (profiles != null) try {
val lookup = LongSparseArray<Profile>(profiles.size).apply { profiles.forEach { put(it.id, it) } }
context.contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use { context.contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use {
it.write(JSONArray(profiles.map { it.toJson(lookup) }.toTypedArray()).toString(2)) it.write(profiles.toString(2))
} }
} catch (e: Exception) { } catch (e: Exception) {
printLog(e) printLog(e)
Toast.makeText(context, e.localizedMessage, Toast.LENGTH_SHORT).show() Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
} }
} }
else -> super.onActivityResult(requestCode, resultCode, data) else -> super.onActivityResult(requestCode, resultCode, data)
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
app:summary="@string/stat_summary"/> app:summary="@string/stat_summary"/>
<Preference <Preference
app:key="control.import" app:key="control.import"
app:title="@string/action_import_file"/> app:title="@string/action_replace_file"/>
<Preference <Preference
app:key="control.export" app:key="control.export"
app:title="@string/action_export_file"/> app:title="@string/action_export_file"/>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment