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 { val lines = string.split("\n") val result = ArrayList() 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, 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, 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() 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") } }