owenclave / app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * 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" }