Skip to content

Instantly share code, notes, and snippets.

@leviyehonatan
Last active June 1, 2024 17:20
Show Gist options
  • Save leviyehonatan/0c53e89864a0890c2e524d87c6c70c2a to your computer and use it in GitHub Desktop.
Save leviyehonatan/0c53e89864a0890c2e524d87c6c70c2a to your computer and use it in GitHub Desktop.
This is a kotlin implementation of a persistent cookie store for android, using SharedPreferences and Gson for the actual persistence
internal class PersistentCookieStore(val context: Context) : CookieStore {
inner class DatedCookie(val cookie: HttpCookie, val creationDate: Date)
val SP_COOKIE_STORE = "cookieStore"
val SP_KEY_DELIMITER = "|"
val SP_KEY_DELIMITER_REGEX = "\\" + SP_KEY_DELIMITER
val cookieSharedPreferences = context.getSharedPreferences(SP_COOKIE_STORE, Context.MODE_PRIVATE)
val allCookies: MutableMap<URI, MutableSet<DatedCookie>> = mutableMapOf()
init {
cookieSharedPreferences.all.entries.map {
val uriAndName = it.key.split(Regex(SP_KEY_DELIMITER_REGEX), 2)
val uri = URI(uriAndName[0])
val cookie: DatedCookie = gson.fromJson<DatedCookie?>(it.value as String)!!
if (allCookies[uri] == null) {
allCookies.put(uri, mutableSetOf())
}
allCookies[uri]?.add(cookie)
}
}
fun getValidCookies(uri: URI) : List<HttpCookie> {
return allCookies.keys.filter {
HttpCookie.domainMatches(it.host, uri.host)
//checkDomainsMatch(it.host, uri.host)
&& checkPathsMatch(it.path, uri.path)
} .flatMap {
// clean expired cookies
allCookies[it].orEmpty().filter { it.cookie.maxAge > 0 && Date().time > it.creationDate.time + (it.cookie.maxAge * 1000) }
.forEach {
remove(uri, it.cookie)
}
allCookies[it].orEmpty().map { it.cookie }
}
}
private fun checkDomainsMatch(cookieHost: String, requestHost: String): Boolean {
return requestHost == cookieHost || requestHost.endsWith("." + cookieHost)
}
private fun checkPathsMatch(cookiePath: String, requestPath: String): Boolean {
return requestPath == cookiePath ||
requestPath.startsWith(cookiePath) && cookiePath[cookiePath.length - 1] == '/' ||
requestPath.startsWith(cookiePath) && requestPath.substring(cookiePath.length)[0] == '/'
}
@Synchronized override fun add(uri: URI, cookie: HttpCookie) {
var uri = uri
uri = cookie.cookieUri(uri)
var exists = allCookies[uri]
if (exists == null)
allCookies[uri] = HashSet<DatedCookie>()
val datedCookie = DatedCookie(cookie, Date())
allCookies[uri]?.add(datedCookie)
saveToPersistence(uri, datedCookie)
}
private inline fun HttpCookie.cookieUri(uri: URI): URI {
var cookieUri = uri
if (domain != null) {
// Remove the starting dot character of the domain, if exists (e.g: .domain.com -> domain.com)
var domain = domain
if (domain[0] == '.') {
domain = domain.substring(1)
}
try {
cookieUri = URI(if (uri.scheme == null)
"http"
else
uri.scheme, domain,
if (path == null) "/" else path, null)
} catch (e: URISyntaxException) {
Log.w(TAG, e)
}
}
return cookieUri
}
private fun saveToPersistence(uri: URI, cookie: DatedCookie) {
val editor = cookieSharedPreferences.edit()
editor.putString(uri.toString() + SP_KEY_DELIMITER + cookie.cookie.name,gson.toJson(cookie))
editor.apply()
}
@Synchronized override fun remove(uri: URI, cookie: HttpCookie): Boolean {
val targetCookies = allCookies[uri]
val cookieRemoved = targetCookies != null && targetCookies.remove(targetCookies.find{ it.cookie == cookie })
if (cookieRemoved) {
removeFromPersistence(uri, cookie)
}
return cookieRemoved
}
private fun removeFromPersistence(uri: URI, cookieToRemove: HttpCookie) {
val editor = cookieSharedPreferences.edit()
editor.remove(uri.toString() + SP_KEY_DELIMITER
+ cookieToRemove.name)
editor.apply()
}
override fun removeAll(): Boolean {
allCookies.clear()
cookieSharedPreferences.edit().clear().apply()
return true
}
override fun get(uri: URI?): MutableList<HttpCookie>? = getValidCookies(uri!!).toMutableList()
override fun getURIs(): MutableList<URI>? = allCookies.keys.toMutableList()
override fun getCookies(): MutableList<HttpCookie>? = allCookies.keys.flatMap { getValidCookies(it) }.toMutableList()
}
@Dmb-999
Copy link

Dmb-999 commented Jul 31, 2020

Hello! I'm new on app development, I'm using your class however it gives me an error on line 17, asking me for a second argument on gson.fromJson<DatedCookie?>(it.value as String)!!, do you know what can it be?

@DONUT235
Copy link

@Dmb-999 pass in DatedCookie::class.java

@vresetnikov
Copy link

Hello, how do you access this cookie storage after the plugin HttpCookies has been installed onto the client? I would like to addCookie() after the client has been initialized.

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment