Pith - kerf
kerf/app/src/main/java/com/vgmlr/kerf/KerfSanitizer.kt [4.9 kb]
Modified: 23:58:54 152 026 (18 Aug 026)
5 Days Ago
package com.vgmlr.kerf

import android.content.Context
import android.net.Uri
import org.json.JSONArray
import org.json.JSONObject

object KerfSanitizer {

    private data class Provider(
        val urlPattern: Regex,
        val completeProvider: Boolean,
        val rules: List<Regex>,
        val rawRules: List<Regex>,
        val referralMarketing: List<Regex>,
        val exceptions: List<Regex>,
        val redirections: List<Regex>
    )

    @Volatile
    private var providers: List<Provider>? = null

    private const val MAX_REDIRECTS = 5

    fun ensureLoaded(context: Context) {
        if (providers != null) return
        synchronized(this) {
            if (providers != null) return
            providers = runCatching { load(context) }.getOrDefault(emptyList())
        }
    }

    private fun load(context: Context): List<Provider> {
        val raw = context.resources.openRawResource(R.raw.clearurls_rules)
            .bufferedReader().use { it.readText() }
        val root = JSONObject(raw).getJSONObject("providers")
        val out = mutableListOf<Provider>()
        val keys = root.keys()
        while (keys.hasNext()) {
            val obj = root.getJSONObject(keys.next())
            val pattern = obj.optString("urlPattern")
            if (pattern.isBlank()) continue
            val provider = runCatching {
                Provider(
                    urlPattern = Regex(pattern, RegexOption.IGNORE_CASE),
                    completeProvider = obj.optBoolean("completeProvider", false),
                    rules = obj.optJSONArray("rules").toRegexList(),
                    rawRules = obj.optJSONArray("rawRules").toRegexList(),
                    referralMarketing = obj.optJSONArray("referralMarketing").toRegexList(),
                    exceptions = obj.optJSONArray("exceptions").toRegexList(),
                    redirections = obj.optJSONArray("redirections").toRegexList()
                )
            }.getOrNull()
            if (provider != null) out.add(provider)
        }
        return out
    }

    private fun JSONArray?.toRegexList(): List<Regex> {
        if (this == null) return emptyList()
        val list = ArrayList<Regex>(length())
        for (i in 0 until length()) {
            runCatching { Regex(getString(i), RegexOption.IGNORE_CASE) }
                .getOrNull()?.let { list.add(it) }
        }
        return list
    }

    fun cleanText(context: Context, text: String): String {
        ensureLoaded(context)
        if (providers.isNullOrEmpty()) return text
        return KerfUtils.UrlRegex.replace(text) { match ->
            val url = match.value
            if (url.startsWith("http://", true) || url.startsWith("https://", true)) {
                runCatching { cleanUrl(url) }.getOrDefault(url)
            } else {
                url
            }
        }
    }

    fun cleanUrl(url: String): String {
        var current = url
        var hops = 0
        while (hops < MAX_REDIRECTS) {
            val (result, redirected) = pass(current)
            current = result
            if (!redirected) break
            hops++
        }
        return current
    }

    private fun pass(input: String): Pair<String, Boolean> {
        val ps = providers ?: return input to false
        var url = input
        for (p in ps) {
            if (!p.urlPattern.containsMatchIn(url)) continue
            if (p.exceptions.any { it.containsMatchIn(url) }) continue
            if (p.completeProvider) continue 

            for (r in p.redirections) {
                val cap = r.find(url)?.groupValues?.getOrNull(1)
                if (!cap.isNullOrEmpty()) {
                    val target = Uri.decode(cap)
                    if (target.startsWith("http", true)) return target to true
                }
            }

            for (raw in p.rawRules) url = raw.replace(url, "")
            url = stripParams(url, p.rules, p.referralMarketing)
        }
        return url to false
    }

    private fun stripParams(url: String, rules: List<Regex>, referral: List<Regex>): String {
        if (rules.isEmpty() && referral.isEmpty()) return url
        val qIndex = url.indexOf('?')
        if (qIndex == -1) return url

        val base = url.substring(0, qIndex)
        var query = url.substring(qIndex + 1)
        var fragment = ""
        val hashIndex = query.indexOf('#')
        if (hashIndex != -1) {
            fragment = query.substring(hashIndex)
            query = query.substring(0, hashIndex)
        }

        val kept = query.split('&').filter { part ->
            if (part.isEmpty()) return@filter false
            val name = part.substringBefore('=')
            val drop = rules.any { it.matches(name) } || referral.any { it.matches(name) }
            !drop
        }

        val newQuery = kept.joinToString("&")
        return buildString {
            append(base)
            if (newQuery.isNotEmpty()) append('?').append(newQuery)
            append(fragment)
        }
    }
}
Updates
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00284
256,802 (+56)