diff --git a/mobile/src/main/res/values-ja/strings.xml b/mobile/src/main/res/values-ja/strings.xml
index 366833f1..fa52639a 100644
--- a/mobile/src/main/res/values-ja/strings.xml
+++ b/mobile/src/main/res/values-ja/strings.xml
@@ -123,7 +123,7 @@
"LAN 及び中国本土のアドレスを迂回する"
"中国本土のアドレス以外を迂回する"
"選択したアプリにプロキシを設定する"
-"サブネット/ホスト名 PCRE パターン"
+"URL/サブネット/ホスト名 PCRE パターン"
"ドメイン及び全てのサブドメイン"
diff --git a/mobile/src/main/res/values-ko/strings.xml b/mobile/src/main/res/values-ko/strings.xml
index c7eab897..013fb557 100644
--- a/mobile/src/main/res/values-ko/strings.xml
+++ b/mobile/src/main/res/values-ko/strings.xml
@@ -45,4 +45,4 @@
"지원하지 않는 버전의 커널입니다: %s < 3.7.1"
"DNS 포워딩"
"모든 DNS 요청을 외부로 포워딩 합니다"
-
\ No newline at end of file
+
diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml
index a36dbe08..37db95cf 100644
--- a/mobile/src/main/res/values-ru/strings.xml
+++ b/mobile/src/main/res/values-ru/strings.xml
@@ -131,7 +131,7 @@
"Все, кроме LAN и Китая"
"Список Китай"
"Установить прокси для выбранных приложений"
-"Подсеть/Регулярное выражение (PCRE) имени хоста"
+"URL/Подсеть/Регулярное выражение (PCRE) имени хоста"
"Доменное имя и все его поддомены"
diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml
index ed29ac07..390e2e3e 100644
--- a/mobile/src/main/res/values-zh-rCN/strings.xml
+++ b/mobile/src/main/res/values-zh-rCN/strings.xml
@@ -124,7 +124,7 @@
"绕过局域网及中国大陆地址"
"仅代理中国大陆地址"
"为应用程序分别设置代理"
-"子网/域名 PCRE 正则表达式"
+"URL/子网/域名 PCRE 正则表达式"
"域名及其子域名"
diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml
index 431d1830..3b05c65d 100644
--- a/mobile/src/main/res/values-zh-rTW/strings.xml
+++ b/mobile/src/main/res/values-zh-rTW/strings.xml
@@ -122,7 +122,7 @@
"略過區域網路及中國大陸"
"China List"
"為已選擇的應用程式設定 Proxy"
-"子網路/主機名稱 PCRE 模式"
+"URL/子網路/主機名稱 PCRE 模式"
"網域及其所有子網域"
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index 60faa91f..d97b17f9 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -130,7 +130,7 @@
Custom rules
Selection…
Add rule(s)…
- Subnet/Hostname PCRE pattern
+ URL, Subnet or Hostname PCRE pattern
Domain name and all its subdomain names
Edit rule
diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
index b9a8ac02..3727d405 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
@@ -154,7 +154,7 @@ trait BaseService extends Service {
}
if (profile.route == Acl.CUSTOM_RULES) // rationalize custom rules
- Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES))
+ Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES), true)
plugin = new PluginConfiguration(profile.plugin).selectedOptions
pluginPath = PluginManager.init(plugin)
diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala
index 676f09e4..dc953f6c 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala
@@ -1,6 +1,6 @@
package com.github.shadowsocks.acl
-import java.io.{File, FileNotFoundException}
+import java.io.{File, FileNotFoundException, IOException}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.IOUtils
@@ -22,6 +22,7 @@ class Acl {
val bypassHostnames = new mutable.SortedList[String]()
val proxyHostnames = new mutable.SortedList[String]()
val subnets = new mutable.SortedList[Subnet]()
+ val urls = new mutable.SortedList[String]()
@DatabaseField
var bypass: Boolean = _
@@ -40,6 +41,10 @@ class Acl {
subnets.clear()
subnets ++= value.split("\n").map(Subnet.fromString)
}
+ def setUrlRules(value: String) {
+ urls.clear()
+ urls ++= value.split("\n")
+ }
def fromAcl(other: Acl): Acl = {
bypassHostnames.clear()
@@ -48,21 +53,35 @@ class Acl {
proxyHostnames ++= other.proxyHostnames
subnets.clear()
subnets ++= other.subnets
+ urls.clear()
+ urls ++= other.urls
bypass = other.bypass
this
}
- def fromSource(value: Source, defaultBypass: Boolean = false): Acl = {
+ def fromSource(value: Source, defaultBypass: Boolean = true): Acl = {
bypassHostnames.clear()
proxyHostnames.clear()
this.subnets.clear()
+ this.urls.clear()
bypass = defaultBypass
lazy val bypassSubnets = new mutable.SortedList[Subnet]()
lazy val proxySubnets = new mutable.SortedList[Subnet]()
var hostnames: mutable.SortedList[String] = if (defaultBypass) proxyHostnames else bypassHostnames
var subnets: mutable.SortedList[Subnet] = if (defaultBypass) proxySubnets else bypassSubnets
+ var in_urls = false
for (line <- value.getLines()) (line.indexOf('#') match {
- case -1 => line
- case index => line.substring(0, index) // trim comments
+ case -1 => if (!in_urls) line else ""
+ case index => {
+ line.indexOf("URLS_BEGIN") match {
+ case -1 =>
+ case index => in_urls = true
+ }
+ line.indexOf("URLS_END") match {
+ case -1 =>
+ case index => in_urls = false
+ }
+ "" // ignore any comment lines
+ }
}).trim match {
case "[outbound_block_list]" =>
hostnames = null
@@ -76,7 +95,10 @@ class Acl {
case "[reject_all]" | "[bypass_all]" => bypass = true
case "[accept_all]" | "[proxy_all]" => bypass = false
case input if subnets != null && input.nonEmpty => try subnets += Subnet.fromString(input) catch {
- case _: IllegalArgumentException => hostnames += input
+ case _: IllegalArgumentException => if (input.startsWith("http://") || input.startsWith("https://")) {
+ urls += input
+ }
+ hostnames += input
}
case _ =>
}
@@ -85,9 +107,24 @@ class Acl {
}
final def fromId(id: String): Acl = fromSource(Source.fromFile(Acl.getFile(id)))
- override def toString: String = {
+ def getAclString(network: Boolean): String = {
val result = new StringBuilder()
- result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
+ if (urls.nonEmpty) {
+ result.append("#URLS_BEGIN\n")
+ result.append(urls.mkString("\n"))
+ if (network) {
+ try {
+ urls.foreach((url: String) => result.append(Source.fromURL(url).mkString))
+ } catch {
+ case e: IOException => // ignore
+ }
+ }
+ result.append("#URLS_END\n")
+
+ }
+ if (result.isEmpty) {
+ result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
+ }
val (bypassList, proxyList) =
if (bypass) (bypassHostnames.toStream, subnets.toStream.map(_.toString) #::: proxyHostnames.toStream)
else (subnets.toStream.map(_.toString) #::: bypassHostnames.toStream, proxyHostnames.toStream)
@@ -104,6 +141,10 @@ class Acl {
result.toString
}
+ override def toString: String = {
+ getAclString(false)
+ }
+
def isValidCustomRules: Boolean = bypass && bypassHostnames.isEmpty
// Don't change: dummy fields for OrmLite interaction
@@ -134,9 +175,9 @@ object Acl {
try acl.fromId(CUSTOM_RULES) catch {
case _: FileNotFoundException =>
}
- acl.bypass = true
- acl.bypassHostnames.clear() // everything is bypassed
acl
}
- def save(id: String, acl: Acl): Unit = IOUtils.writeString(getFile(id), acl.toString)
+ def save(id: String, acl: Acl, network: Boolean = false): Unit = {
+ IOUtils.writeString(getFile(id), acl.getAclString(network))
+ }
}