____ _ _______ __
/ __ \ | /| / / __/ |/ /
/ /_/ / |/ |/ / _// /
\____/|__/|__/___/_||_/
owenclave / app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai *
* *
* 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 . *
* *
******************************************************************************/
package io.nekohasekai.sagernet.ktx
import io.nekohasekai.sagernet.BuildConfig
import libexclavecore.Libexclavecore
import libexclavecore.URL
import java.net.IDN
import java.net.InetSocketAddress
import java.net.Socket
import java.util.Properties
import kotlin.random.Random
fun URL.queryParameter(key: String) = getQueryParameter(key).takeIf { it.isNotEmpty() }
fun URL.queryParameterNotBlank(key: String) = getQueryParameter(key).takeIf { it.isNotBlank() }
var URL.pathSegments: List
get() = path.split("/").filter { it.isNotEmpty() }
set(value) {
path = value.joinToString("/")
}
fun URL.addPathSegments(vararg segments: String) {
pathSegments = pathSegments.toMutableList().apply {
addAll(segments)
}
}
fun String.wrapIDN(): String {
if (Libexclavecore.isIP(this)) {
return this
}
return IDN.toUnicode(this, IDN.ALLOW_UNASSIGNED)
}
fun String.unwrapIDN(): String {
if (Libexclavecore.isIP(this) || this.all { it.code < 128 }) {
return this
}
return try {
IDN.toASCII(this, IDN.ALLOW_UNASSIGNED)
} catch (_: IllegalArgumentException) {
this
}
}
fun joinHostPort(host: String, port: Int): String {
if (Libexclavecore.isIPv6(host)) {
return "[$host]:$port"
}
return "$host:$port"
}
fun String.unwrapHost(): String {
if (startsWith("[") && endsWith("]")) {
return substring(1, length - 1).unwrapHost()
}
return this
}
fun isHTTPorHTTPSURL(url: String): Boolean {
try {
val u = Libexclavecore.parseURL(url)
return (u.scheme == "http" || u.scheme == "https")
} catch (_: Exception) {
return false
}
}
fun mkPort(): Int {
val socket = Socket()
socket.reuseAddress = true
socket.bind(InetSocketAddress(0))
val port = socket.localPort
socket.close()
return port
}
fun String.listByLine(): List {
return this.split("\n").map { it.trim() }.filter { it.isNotEmpty() }
}
fun String.listByLineOrComma(): List {
return this.split(",", "\n").map { it.trim() }.filter { it.isNotEmpty() }
}
fun String.isValidHysteriaPort(disallowFromGreaterThanTo: Boolean = false): Boolean {
if (this.toIntOrNull() != null) {
return this.toInt() in 0..65535
}
val portRanges = this.split(",")
for (portRange in portRanges) {
if (portRange.toIntOrNull() != null) {
if (portRange.toInt() !in 0..65535) {
return false
}
} else {
val parts = portRange.split("-")
if (parts.size != 2) {
return false
}
val from = parts[0].toIntOrNull()
val to = parts[1].toIntOrNull()
if (from == null || to == null || from < 0 || from > 65535 || to < 0 || to > 65535) {
return false
}
if (disallowFromGreaterThanTo && from > to) {
return false
}
}
}
return true
}
fun String.isValidHysteriaMultiPort(disallowFromGreaterThanTo: Boolean = false): Boolean {
return this.toIntOrNull() == null && this.isValidHysteriaPort(disallowFromGreaterThanTo)
}
fun String.toHysteriaPort(disallowFromGreaterThanTo: Boolean = false): Int {
if (this.toIntOrNull() != null) {
if (this.toInt() in 0..65535) {
return this.toInt()
}
error("invalid port range")
}
val portRanges = this.split(",")
val fromList: MutableList = mutableListOf()
val toList: MutableList = mutableListOf()
var len = 0
for (portRange in portRanges) {
if (portRange.toIntOrNull() != null) {
if (portRange.toInt() !in 0..65535) {
error("invalid port range")
}
fromList.add(portRange.toInt())
toList.add(portRange.toInt())
len++
} else {
val parts = portRange.split("-")
if (parts.size != 2) {
error("invalid port range")
}
var from = parts[0].toIntOrNull()
var to = parts[1].toIntOrNull()
if (from == null || to == null || from < 0 || from > 65535 || to < 0 || to > 65535) {
error("invalid port range")
}
if (from > to) {
if (disallowFromGreaterThanTo) {
error("invalid port range")
}
from = to.also { to = from }
}
fromList.add(from)
toList.add(to)
len += to - from + 1
}
}
val portIndex = Random.nextInt(0, len)
var oldLen = 0
var newLen: Int
for (i in fromList.indices) {
newLen = oldLen + toList[i] - fromList[i] + 1
if (portIndex < newLen) {
return portIndex - oldLen + fromList[i]
}
oldLen = newLen
}
error("invalid port range")
}
const val USER_AGENT = "Owenclave/${BuildConfig.VERSION_NAME}"
// Taken from https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/blob/main/client/torrc with unsupported servers removed.
val PUBLIC_STUN_SERVERS = arrayOf(
"stun.epygi.com:3478",
"stun.uls.co.za:3478",
"stun.voipgate.com:3478",
"stun.mixvoip.com:3478"
)
fun String.unescapeLineFeed(): String {
val chars = this.toCharArray()
var from = 0
var to = 0
val length = chars.size
while (from < length) {
val ch = chars[from]
if (ch == '\\' && from + 1 < length) {
when (chars[from+1]) {
'n' -> {
chars[to] = '\n'
to += 1
from += 2
continue
}
'\\' -> {
chars[to] = '\\'
to += 1
from += 2
continue
}
}
}
chars[to] = ch
to += 1
from += 1
}
return String(chars, 0, to)
}
fun Properties.getBooleanProperty(key: String): Boolean {
return getProperty(key) == "true"
}