diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt
index 2184ced6..7cc513b6 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt
@@ -9,21 +9,17 @@ import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.util.connectCancellable
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
-import java.io.File
-import java.io.FileNotFoundException
import java.io.IOException
-import java.net.HttpCookie
-import java.util.Scanner
-import java.util.regex.Pattern
+import java.math.BigInteger
+import java.security.MessageDigest
/**
* This class generates a default nickname for new clients.
@@ -37,43 +33,7 @@ object MacLookup {
override fun getLocalizedMessage() = formatMessage(app)
}
- private object SessionManager {
- private const val CACHE_FILENAME = "maclookup_sessioncache"
- private const val COOKIE_SESSION = "mac_address_vendor_lookup_session"
- private val csrfPattern = Pattern.compile("?
- get() = try {
- File(app.deviceStorage.cacheDir, CACHE_FILENAME).readText().split('\n', limit = 2)
- } catch (_: FileNotFoundException) {
- null
- }
- set(value) = File(app.deviceStorage.cacheDir, CACHE_FILENAME).run {
- if (value != null) writeText(value.joinToString("\n")) else if (!delete()) writeText("")
- }
- private val mutex = Mutex()
-
- private suspend fun refreshSessionCache() = connectCancellable("https://macaddress.io/api") { conn ->
- val cookies = conn.headerFields["set-cookie"] ?: throw IOException("Missing cookies")
- var mavls: HttpCookie? = null
- for (header in cookies) for (cookie in HttpCookie.parse(header)) {
- if (cookie.name == COOKIE_SESSION) mavls = cookie
- }
- if (mavls == null) throw IOException("Missing set-cookie $COOKIE_SESSION")
- val token = conn.inputStream.use { Scanner(it).findWithinHorizon(csrfPattern, 0) }
- ?: throw IOException("Missing csrf-token")
- listOf(mavls.toString(), csrfPattern.matcher(token).run {
- check(matches())
- group(1)!!
- }).also { sessionCache = it }
- }
-
- suspend fun obtain(forceNew: Boolean): Pair = mutex.withLock {
- val sessionCache = (if (forceNew) null else sessionCache) ?: refreshSessionCache()
- HttpCookie.parse(sessionCache[0]).single() to sessionCache[1]
- }
- }
-
+ private val sha1 = MessageDigest.getInstance("SHA-1")
private val macLookupBusy = mutableMapOf()
// http://en.wikipedia.org/wiki/ISO_3166-1
private val countryCodeRegex = "(?:^|[^A-Z])([A-Z]{2})[\\s\\d]*$".toRegex()
@@ -84,31 +44,29 @@ object MacLookup {
@MainThread
fun perform(mac: MacAddress, explicit: Boolean = false) {
abort(mac)
- macLookupBusy[mac] = GlobalScope.launch(Dispatchers.IO) {
+ macLookupBusy[mac] = GlobalScope.launch(Dispatchers.Unconfined, CoroutineStart.UNDISPATCHED) {
+ var response: String? = null
try {
- var response: String? = null
- for (tries in 0 until 5) {
- val (cookie, csrf) = SessionManager.obtain(tries > 0)
- response = connectCancellable("https://macaddress.io/mac-address-lookup") { conn ->
- conn.requestMethod = "POST"
- conn.setRequestProperty("content-type", "application/json")
- conn.setRequestProperty("cookie", "${cookie.name}=${cookie.value}")
- conn.setRequestProperty("x-csrf-token", csrf)
- conn.outputStream.writer().use { it.write("{\"macAddress\":\"$mac\",\"not-web-search\":true}") }
- when (val responseCode = conn.responseCode) {
- 200 -> conn.inputStream.bufferedReader().readText()
- 419 -> null
- else -> throw IOException("Unhandled response code $responseCode")
- }
+ response = connectCancellable("https://api.maclookup.app/v2/macs/$mac") { conn ->
+// conn.setRequestProperty("X-App-Id", "net.mobizme.macaddress")
+// conn.setRequestProperty("X-App-Version", "2.0.11")
+// conn.setRequestProperty("X-App-Version-Code", "111")
+ val epoch = System.currentTimeMillis()
+ conn.setRequestProperty("X-App-Epoch", epoch.toString())
+ conn.setRequestProperty("X-App-Sign", "%032x".format(BigInteger(1,
+ sha1.digest("aBA6AEkfg8cbHlWrBDYX_${mac}_$epoch".toByteArray()))))
+ when (val responseCode = conn.responseCode) {
+ 200 -> conn.inputStream.bufferedReader().readText()
+ 400, 401, 429 -> throw UnexpectedError(mac, conn.inputStream.bufferedReader().readText())
+ else -> throw UnexpectedError(mac, "Unhandled response code $responseCode: " +
+ conn.inputStream.bufferedReader().readText())
}
- if (response != null) break
}
- if (response == null) throw IOException("Session creation failure")
val obj = JSONObject(response)
- val result = if (obj.getJSONObject("blockDetails").getBoolean("blockFound")) {
- val vendor = obj.getJSONObject("vendorDetails")
- val company = vendor.getString("companyName")
- val match = extractCountry(mac, response, vendor)
+ if (!obj.getBoolean("success")) throw UnexpectedError(mac, response)
+ val result = if (obj.getBoolean("found")) {
+ val company = obj.getString("company")
+ val match = extractCountry(mac, response, obj)
if (match != null) {
String(match.groupValues[1].flatMap { listOf('\uD83C', it + 0xDDA5) }.toCharArray()) + ' ' +
company
@@ -120,15 +78,19 @@ object MacLookup {
}
} catch (_: CancellationException) {
} catch (e: Throwable) {
- Timber.w(e)
+ when (e) {
+ is UnexpectedError -> Timber.w(e)
+ is IOException -> Timber.d(e)
+ else -> Timber.w(UnexpectedError(mac, "Got response: $response").initCause(e))
+ }
if (explicit) SmartSnackbar.make(e).show()
}
}
}
private fun extractCountry(mac: MacAddress, response: String, obj: JSONObject): MatchResult? {
- countryCodeRegex.matchEntire(obj.optString("countryCode"))?.also { return it }
- val address = obj.optString("companyAddress")
+ countryCodeRegex.matchEntire(obj.optString("country"))?.also { return it }
+ val address = obj.optString("address")
if (address.isBlank()) return null
countryCodeRegex.find(address)?.also { return it }
Timber.w(UnexpectedError(mac, response))