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 26d0fe24..b67f0d5e 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala @@ -26,6 +26,27 @@ class Acl { @DatabaseField var bypass: Boolean = _ + def isUrl(url: String): Boolean = url.startsWith("http://") || url.startsWith("https://") + def getBypassHostnamesString: String = bypassHostnames.mkString("\n") + def getProxyHostnamesString: String = proxyHostnames.mkString("\n") + def getSubnetsString: String = subnets.mkString("\n") + def setBypassHostnamesString(value: String) { + bypassHostnames.clear() + bypassHostnames ++= value.split("\n") + } + def setProxyHostnamesString(value: String) { + proxyHostnames.clear() + proxyHostnames ++= value.split("\n") + } + def setSubnetsString(value: String) { + 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() bypassHostnames ++= other.bypassHostnames @@ -50,11 +71,18 @@ class Acl { var subnets: mutable.SortedList[Subnet] = if (defaultBypass) proxySubnets else bypassSubnets var in_urls = false for (line <- value.getLines()) (line.indexOf('#') match { - case -1 => if (!in_urls) line else "" - case index => - if (line.contains("URLS_BEGIN")) in_urls = true - if (line.contains("URLS_END")) in_urls = false - line.substring(0, index) // trim comments + case -1 => if (!in_urls) line else "" + case index => { + line.indexOf("NETWORK_ACL_BEGIN") match { + case -1 => + case index => in_urls = true + } + line.indexOf("NETWORK_ACL_END") match { + case -1 => + case index => in_urls = false + } + "" // ignore any comment lines + } }).trim match { case "[outbound_block_list]" => hostnames = null @@ -68,8 +96,11 @@ 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 => if (input.startsWith("http://") || input.startsWith("https://")) - urls += input else hostnames += input + case _: IllegalArgumentException => if (isUrl(input)) { + urls += input + } else { + hostnames += input + } } case _ => } @@ -81,17 +112,16 @@ class Acl { def getAclString(network: Boolean): String = { val result = new StringBuilder() if (urls.nonEmpty) { - result.append("#URLS_BEGIN\n") result.append(urls.mkString("\n")) + result.append("#NETWORK_ACL_BEGIN\n") if (network) { try { urls.foreach((url: String) => result.append(Source.fromURL(url).mkString)) } catch { - case _: IOException => // ignore + case e: IOException => // ignore } } - result.append("#URLS_END\n") - + result.append("#NETWORK_ACL_END\n") } if (result.isEmpty) { result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n") @@ -112,7 +142,9 @@ class Acl { result.toString } - override def toString: String = getAclString(false) + override def toString: String = { + getAclString(false) + } def isValidCustomRules: Boolean = bypass && bypassHostnames.isEmpty diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala index 74a6fdc4..53dd665f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala @@ -125,22 +125,34 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi }) } - def getItemCount: Int = acl.subnets.size + acl.proxyHostnames.size + def getItemCount: Int = acl.subnets.size + acl.proxyHostnames.size + acl.urls.size def onBindViewHolder(vh: AclRuleViewHolder, i: Int): Unit = { - val j = i - acl.subnets.size - if (j < 0) vh.bind(acl.subnets(i)) else vh.bind(acl.proxyHostnames(j)) + val j = i - acl.urls.size + val k = i - acl.subnets.size - acl.urls.size + if (j < 0) + vh.bind(acl.urls(i)) + else if (k < 0) + vh.bind(acl.subnets(j)) + else + vh.bind(acl.proxyHostnames(k)) } def onCreateViewHolder(vg: ViewGroup, i: Int) = new AclRuleViewHolder(LayoutInflater.from(vg.getContext) .inflate(android.R.layout.simple_list_item_1, vg, false)) + def addUrl(url: String): Int = if (acl.urls.add(url)) { + val index = acl.urls.indexOf(url) + notifyItemInserted(index) + apply() + index + } else -1 def addSubnet(subnet: Subnet): Int = if (acl.subnets.add(subnet)) { - val index = acl.subnets.indexOf(subnet) + val index = acl.subnets.indexOf(subnet) + acl.urls.size notifyItemInserted(index) apply() index } else -1 def addHostname(hostname: String): Int = if (acl.proxyHostnames.add(hostname)) { - val index = acl.proxyHostnames.indexOf(hostname) + acl.subnets.size + val index = acl.proxyHostnames.indexOf(hostname) + acl.urls.size + acl.subnets.size notifyItemInserted(index) apply() index @@ -148,6 +160,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi def addToProxy(input: String): Int = { val acl = new Acl().fromSource(Source.fromString(input), defaultBypass = true) var result = -1 + for (url <- acl.urls) result = addUrl(url) for (hostname <- acl.proxyHostnames) result = addHostname(hostname) if (acl.bypass) for (subnet <- acl.subnets) result = addSubnet(subnet) result @@ -164,13 +177,17 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi } def remove(i: Int) { - val j = i - acl.subnets.size + val j = i - acl.urls.size + val k = i - acl.urls.size - acl.subnets.size if (j < 0) { - undoManager.remove((i, acl.subnets(i))) - acl.subnets.remove(i) + undoManager.remove((i, acl.urls(i))) + acl.urls.remove(i) + } else if (k < 0) { + undoManager.remove((j, acl.subnets(j))) + acl.subnets.remove(j) } else { - undoManager.remove((j, acl.proxyHostnames(j))) - acl.proxyHostnames.remove(j) + undoManager.remove((k, acl.proxyHostnames(k))) + acl.proxyHostnames.remove(k) } notifyItemRemoved(i) apply() @@ -180,10 +197,15 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi notifyItemRemoved(acl.subnets.indexOf(subnet)) acl.subnets.remove(subnet) apply() - case hostname: String => + case hostname: String => if (acl.isUrl(hostname)) { + notifyItemRemoved(acl.urls.indexOf(hostname)) + acl.urls.remove(hostname) + apply() + } else { notifyItemRemoved(acl.proxyHostnames.indexOf(hostname)) acl.proxyHostnames.remove(hostname) apply() + } } def removeSelected() { undoManager.remove(selectedItems.map((-1, _)).toSeq: _*) @@ -192,12 +214,19 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi onSelectedItemsUpdated() } def undo(actions: Iterator[(Int, AnyRef)]): Unit = for ((_, item) <- actions) item match { - case hostname: String => if (acl.proxyHostnames.insert(hostname)) { - notifyItemInserted(acl.proxyHostnames.indexOf(hostname) + acl.subnets.size) - apply() + case hostname: String => if (acl.isUrl(hostname)) { + if (acl.urls.insert(hostname)) { + notifyItemInserted(acl.urls.indexOf(hostname)) + apply() + } + } else { + if (acl.proxyHostnames.insert(hostname)) { + notifyItemInserted(acl.proxyHostnames.indexOf(hostname) + acl.urls.size + acl.subnets.size) + apply() + } } case subnet: Subnet => if (acl.subnets.insert(subnet)) { - notifyItemInserted(acl.subnets.indexOf(subnet)) + notifyItemInserted(acl.subnets.indexOf(subnet) + acl.urls.size) apply() } }