454 lines
16 KiB
Kotlin
454 lines
16 KiB
Kotlin
package com.tutpro.baresip
|
|
|
|
import android.app.Activity
|
|
import android.app.ActivityManager
|
|
import android.content.Context
|
|
import android.support.v7.app.AlertDialog
|
|
import android.content.Intent
|
|
import android.content.pm.PackageManager
|
|
import android.os.Bundle
|
|
import android.support.v4.app.ActivityCompat
|
|
import android.support.v4.content.ContextCompat
|
|
import android.text.Editable
|
|
import android.text.TextWatcher
|
|
|
|
import kotlin.collections.ArrayList
|
|
import kotlin.Exception
|
|
|
|
import java.io.*
|
|
import java.security.SecureRandom
|
|
import java.util.*
|
|
import java.util.zip.*
|
|
|
|
import javax.crypto.Cipher
|
|
import javax.crypto.SecretKeyFactory
|
|
import javax.crypto.spec.IvParameterSpec
|
|
import javax.crypto.spec.PBEKeySpec
|
|
import javax.crypto.spec.SecretKeySpec
|
|
|
|
object Utils {
|
|
|
|
fun getNameValue(string: String, name: String): ArrayList<String> {
|
|
val lines = string.split("\n")
|
|
val result = ArrayList<String>()
|
|
for (line in lines) {
|
|
if (line.startsWith(name))
|
|
result.add((line.substring(name.length).trim()).split(" \t")[0])
|
|
}
|
|
return result
|
|
}
|
|
|
|
fun removeLinesStartingWithName(string: String, name: String): String {
|
|
var result = ""
|
|
for (line in string.split("\n"))
|
|
if (!line.startsWith(name) && (line.length > 0)) result += line + "\n"
|
|
return result
|
|
}
|
|
|
|
fun alertView(context: Context, title: String, message: String) {
|
|
val builder = AlertDialog.Builder(context)
|
|
builder.setTitle(title)
|
|
.setMessage(message)
|
|
.setPositiveButton(R.string.ok)
|
|
{ dialog, _ -> dialog.dismiss() }
|
|
.show()
|
|
}
|
|
|
|
fun uriHostPart(uri: String): String {
|
|
return uri.substringAfter("@")
|
|
.substringBefore(":")
|
|
.substringBefore(";")
|
|
.substringBefore(">")
|
|
}
|
|
|
|
fun uriUserPart(uri: String): String {
|
|
return uri.substringAfter(":").substringBefore("@")
|
|
}
|
|
|
|
fun friendlyUri(uri: String, domain: String): String {
|
|
var u = uri
|
|
if (uri.startsWith("<") && (uri.endsWith(">")))
|
|
u = uri.substring(1).substringBeforeLast(">")
|
|
if (u.contains("@") && !u.substring(4).contains(":") &&
|
|
!u.contains(";")) {
|
|
val user = uriUserPart(u)
|
|
val host = uriHostPart(u)
|
|
if (host == domain) return user else return "$user@$host"
|
|
} else {
|
|
return uri
|
|
}
|
|
}
|
|
|
|
private fun aorUser(aor: String): String {
|
|
val user = aor.substringBefore("@")
|
|
return if (user == aor) "" else user
|
|
}
|
|
|
|
fun aorDomain(aor: String): String {
|
|
val domain = aor.substringAfter("@")
|
|
return if (domain == aor) "" else domain
|
|
}
|
|
|
|
fun checkAor(aor: String): Boolean {
|
|
return checkUriUser(aorUser(aor)) && checkDomain(aorDomain(aor))
|
|
}
|
|
|
|
fun checkTelNo(no: String): Boolean {
|
|
return Regex("^[+]?[0-9]{1,16}\$").matches(no)
|
|
}
|
|
|
|
fun checkIpV4(ip: String): Boolean {
|
|
return Regex("^(([0-1]?[0-9]{1,2}\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$").matches(ip)
|
|
}
|
|
|
|
private fun checkIpV6(ip: String): Boolean {
|
|
return Regex("^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$").matches(ip)
|
|
}
|
|
|
|
private fun checkIpv6InBrackets(bracketedIp: String): Boolean {
|
|
return bracketedIp.startsWith("[") && bracketedIp.endsWith("]") &&
|
|
checkIpV6(bracketedIp.substring(1, bracketedIp.length - 2))
|
|
}
|
|
|
|
private fun checkIp(ip: String): Boolean {
|
|
return checkIpV4(ip) || checkIpV6(ip)
|
|
}
|
|
|
|
private fun checkUriUser(user: String): Boolean {
|
|
for (c in user)
|
|
if (!(c.isLetterOrDigit() || c in "-_.!~*'()&=+$,;?/")) return false
|
|
return user.length > 0
|
|
}
|
|
|
|
fun checkDomain(domain: String): Boolean {
|
|
val parts = domain.split(".")
|
|
for (p in parts) {
|
|
if (p.endsWith("-") || p.startsWith("-") ||
|
|
!Regex("^[-a-zA-Z0-9]+\$").matches(p))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
private fun checkPort(port: String): Boolean {
|
|
val number = port.toIntOrNull()
|
|
if (number == null) return false
|
|
return (number > 0) && (number < 65536)
|
|
}
|
|
|
|
fun checkIpPort(ipPort: String): Boolean {
|
|
if (ipPort.startsWith("["))
|
|
return checkIpv6InBrackets(ipPort.substringBeforeLast(":")) &&
|
|
checkPort(ipPort.substringAfterLast(":"))
|
|
else
|
|
return checkIpV4(ipPort.substringBeforeLast(":")) &&
|
|
checkPort(ipPort.substringAfterLast(":"))
|
|
}
|
|
|
|
private fun checkDomainPort(domainPort: String): Boolean {
|
|
return checkDomain(domainPort.substringBeforeLast(":")) &&
|
|
checkPort(domainPort.substringAfterLast(":"))
|
|
}
|
|
|
|
fun checkHostPort(hostPort: String): Boolean {
|
|
return checkIp(hostPort) || checkDomain(hostPort) ||
|
|
checkIpPort(hostPort) || checkDomainPort(hostPort)
|
|
}
|
|
|
|
private fun checkParams(params: String): Boolean {
|
|
for (param in params.split(";"))
|
|
if (!checkParam(param)) return false
|
|
return true
|
|
}
|
|
|
|
private fun checkParam(param: String): Boolean {
|
|
val nameValue = param.split("=")
|
|
if (nameValue.size == 1)
|
|
/* Todo: do proper check */
|
|
return true
|
|
if (nameValue.size == 2)
|
|
/* Todo: do proper check */
|
|
return true
|
|
return false
|
|
}
|
|
|
|
fun paramValue(params: String, name: String): String {
|
|
if (params == "") return ""
|
|
for (param in params.split(";")) {
|
|
val nameValue = param.split("=")
|
|
if (nameValue.size == 2) {
|
|
if (nameValue[0] == name) return nameValue[1]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
fun checkHostPortParams(hpp: String) : Boolean {
|
|
val restParams = hpp.split(";", limit = 2)
|
|
if (restParams.size == 1)
|
|
return checkHostPort(restParams[0])
|
|
else
|
|
return checkHostPort(restParams[0]) && checkParams(restParams[1])
|
|
}
|
|
|
|
fun checkSipUri(uri: String): Boolean {
|
|
if (!uri.startsWith("sip:")) return false
|
|
val userRest = uri.substring(4).split("@")
|
|
if (userRest.size == 1) {
|
|
return checkHostPortParams(userRest[0])
|
|
} else if (userRest.size == 2) {
|
|
return checkUriUser(userRest[0]) && checkHostPortParams(userRest[1])
|
|
} else
|
|
return false
|
|
}
|
|
|
|
fun checkPrintAscii(s: String): Boolean {
|
|
if (s == "") return true
|
|
return Regex("^[ -~]*\$").matches(s)
|
|
}
|
|
|
|
fun checkName(name: String): Boolean {
|
|
if (name.length < 2) return false
|
|
for (c in name) {
|
|
if (!c.isLetterOrDigit() && !(c in "-.!%*_+`'~ ")) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fun implode(list: List<String>, sep: String): String {
|
|
var res = ""
|
|
for (s in list) {
|
|
if (res == "")
|
|
res = s
|
|
else
|
|
res = res + sep + s
|
|
}
|
|
return res
|
|
}
|
|
|
|
fun isVisible(): Boolean {
|
|
val appProcessInfo = ActivityManager.RunningAppProcessInfo()
|
|
ActivityManager.getMyMemoryState(appProcessInfo)
|
|
return appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
|
|
}
|
|
|
|
fun dtmfWatcher(callp: String): TextWatcher {
|
|
return object : TextWatcher {
|
|
override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {}
|
|
override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) {
|
|
val text = sequence.subSequence(start, start + count).toString()
|
|
if (text.length > 0) {
|
|
val digit = text[0]
|
|
Log.d("Baresip", "Got DTMF digit '$digit'")
|
|
if (((digit >= '0') && (digit <= '9')) || (digit == '*') || (digit == '#'))
|
|
Api.call_send_digit(callp, digit)
|
|
}
|
|
}
|
|
override fun afterTextChanged(sequence: Editable) {
|
|
// KEYCODE_REL
|
|
// call_send_digit(callp, 4.toChar())
|
|
}
|
|
}
|
|
}
|
|
|
|
fun requestPermission(ctx: Context, permission: String) : Boolean {
|
|
if (ContextCompat.checkSelfPermission(ctx, permission) != PackageManager.PERMISSION_GRANTED) {
|
|
Log.w("Baresip", "Baresip does not have $permission permission")
|
|
ActivityCompat.requestPermissions(ctx as Activity, arrayOf(permission),
|
|
MainActivity.PERMISSION_REQUEST_CODE)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fun copyAssetToFile(context: Context, asset: String, path: String) {
|
|
try {
|
|
val `is` = context.assets.open(asset)
|
|
val os = FileOutputStream(path)
|
|
val buffer = ByteArray(512)
|
|
var byteRead: Int = `is`.read(buffer)
|
|
while (byteRead != -1) {
|
|
os.write(buffer, 0, byteRead)
|
|
byteRead = `is`.read(buffer)
|
|
}
|
|
os.close()
|
|
`is`.close()
|
|
} catch (e: IOException) {
|
|
Log.e("Baresip", "Failed to copy asset '$asset' to file: $e")
|
|
}
|
|
}
|
|
|
|
fun deleteFile(filePath: String) {
|
|
val file = File(filePath)
|
|
if (file.exists()) {
|
|
Log.d("Baresip", "Deleting file '$filePath'")
|
|
file.delete()
|
|
}
|
|
}
|
|
|
|
fun getFileContents(filePath: String): ByteArray? {
|
|
try {
|
|
return File(filePath).readBytes()
|
|
} catch(e: FileNotFoundException) {
|
|
Log.e("Baresip", "File '$filePath' not found: ${e.printStackTrace()}")
|
|
return null
|
|
} catch (e: Exception) {
|
|
Log.e("Baresip", "Failed to read file '$filePath': ${e.printStackTrace()}")
|
|
return null
|
|
}
|
|
}
|
|
|
|
fun putFileContents(filePath: String, contents: ByteArray): Boolean {
|
|
try {
|
|
File(filePath).writeBytes(contents)
|
|
}
|
|
catch (e: IOException) {
|
|
Log.e("Baresip", "Failed to write file '$filePath': $e")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
class Crypto(val salt: ByteArray, val iter: Int, val iv: ByteArray, val data: ByteArray):
|
|
Serializable {
|
|
val serialVersionUID = -29238082928391L
|
|
}
|
|
|
|
private fun encrypt(content: ByteArray, password: CharArray): Crypto? {
|
|
var obj: Crypto? = null
|
|
try {
|
|
val sr = SecureRandom()
|
|
val salt = ByteArray(128)
|
|
sr.nextBytes(salt)
|
|
val iterationCount = Random().nextInt(1024) + 512
|
|
val pbKeySpec = PBEKeySpec(password, salt, iterationCount, 128)
|
|
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
|
val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded
|
|
val keySpec = SecretKeySpec(keyBytes, "AES")
|
|
val ivRandom = SecureRandom()
|
|
val iv = ByteArray(16)
|
|
ivRandom.nextBytes(iv)
|
|
val ivSpec = IvParameterSpec(iv)
|
|
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
|
|
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
|
|
val cipherData = cipher.doFinal(content)
|
|
obj = Crypto(salt, iterationCount, iv, cipherData)
|
|
} catch (e: Exception) {
|
|
Log.e("Baresip", "Encrypt failed: ${e.printStackTrace()}")
|
|
}
|
|
return obj
|
|
|
|
}
|
|
|
|
private fun decrypt(obj: Crypto, password: CharArray): ByteArray? {
|
|
var plainData: ByteArray? = null
|
|
try {
|
|
val pbKeySpec = PBEKeySpec(password, obj.salt, obj.iter, 128)
|
|
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
|
val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded
|
|
val keySpec = SecretKeySpec(keyBytes, "AES")
|
|
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
|
|
val ivSpec = IvParameterSpec(obj.iv)
|
|
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
|
|
plainData = cipher.doFinal(obj.data)
|
|
} catch (e: Exception) {
|
|
Log.e("Baresip", "Decrypt failed: ${e.printStackTrace()}")
|
|
}
|
|
return plainData
|
|
}
|
|
|
|
|
|
fun encryptToFile(filePath: String, content: ByteArray, password: String): Boolean {
|
|
val obj = encrypt(content, password.toCharArray())
|
|
try {
|
|
ObjectOutputStream(FileOutputStream(File(filePath))).use {
|
|
it.writeObject(obj)
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e("Baresip", "Write failed: ${e.printStackTrace()}")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fun decryptFromFile(filePath: String, password: String): ByteArray? {
|
|
var plainData: ByteArray? = null
|
|
try {
|
|
ObjectInputStream(FileInputStream(File(filePath))).use { it ->
|
|
val obj = it.readObject() as Crypto
|
|
plainData = decrypt(obj, password.toCharArray())
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e("Baresip", "Decrypt failed from file '$filePath'")
|
|
}
|
|
return plainData
|
|
}
|
|
|
|
fun zip(fileNames: Array<String>, zipFileName: String): Boolean {
|
|
val zipFilePath = BaresipService.filesPath + "/" + zipFileName
|
|
try {
|
|
ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFilePath))).use { out ->
|
|
val data = ByteArray(1024)
|
|
for (file in fileNames) {
|
|
val filePath = BaresipService.filesPath + "/" + file
|
|
if (File(filePath).exists()) {
|
|
FileInputStream(filePath).use { fi ->
|
|
BufferedInputStream(fi).use { origin ->
|
|
val entry = ZipEntry(filePath)
|
|
out.putNextEntry(entry)
|
|
while (true) {
|
|
val readBytes = origin.read(data)
|
|
if (readBytes == -1) break
|
|
out.write(data, 0, readBytes)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e: IOException) {
|
|
Log.e("Baresip", "Failed to zip file '$zipFilePath': $e")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fun unZip(zipFilePath: String): Boolean {
|
|
val allFiles = listOf("accounts", "calls", "config", "contacts", "messages", "uuid",
|
|
"zrtp_cache.dat", "zrtp_zid", "cert.pem", "ca_cert", "ca_certs.crt")
|
|
val zipFiles = mutableListOf<String>()
|
|
try {
|
|
ZipFile(zipFilePath).use { zip ->
|
|
zip.entries().asSequence().forEach { entry ->
|
|
zipFiles.add(entry.name.substringAfterLast("/"))
|
|
zip.getInputStream(entry).use { input ->
|
|
File(entry.name).outputStream().use { output ->
|
|
input.copyTo(output)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e: IOException) {
|
|
Log.e("Baresip", "Failed to unzip file '$zipFilePath': $e")
|
|
return false
|
|
}
|
|
(allFiles - zipFiles).iterator().forEach {
|
|
deleteFile(BaresipService.filesPath + "/$it")
|
|
}
|
|
return true
|
|
}
|
|
|
|
fun dumpIntent(intent: Intent) {
|
|
val bundle: Bundle = intent.extras ?: return
|
|
val keys = bundle.keySet()
|
|
val it = keys.iterator()
|
|
Log.d("Baresip", "Dumping intent start")
|
|
while (it.hasNext()) {
|
|
val key = it.next()
|
|
Log.d("Baresip","[" + key + "=" + bundle.get(key)+"]");
|
|
}
|
|
Log.d("Baresip", "Dumping intent finish")
|
|
}
|
|
|
|
}
|