Skip to content

Instantly share code, notes, and snippets.

@leviyehonatan
Last active June 1, 2024 17:20
Show Gist options
  • Select an option

  • Save leviyehonatan/0c53e89864a0890c2e524d87c6c70c2a to your computer and use it in GitHub Desktop.

Select an option

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

Dmb-999 commented Jul 31, 2020

Copy link
Copy Markdown

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
Copy Markdown

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

@vresetnikov

Copy link
Copy Markdown

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