Skip to content

Commit

Permalink
Improve cookie display and deletion
Browse files Browse the repository at this point in the history
Tap a cookie to copy it to clipboard.
Close #617
  • Loading branch information
Slion committed Mar 25, 2024
1 parent 9ffd6ff commit 6406cc4
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import android.content.ClipboardManager
/**
* Copies the [text] to the clipboard with the label `URL`.
*/
fun ClipboardManager.copyToClipboard(text: String) {
setPrimaryClip(ClipData.newPlainText("URL", text))
fun ClipboardManager.copyToClipboard(text: String, label: String = "URL") {
setPrimaryClip(ClipData.newPlainText(label, text))
}
2 changes: 1 addition & 1 deletion app/src/main/java/fulguris/preference/BasicPreference.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class BasicPreference :
val title = holder.findViewById(android.R.id.title) as TextView
// Enable multiple line support
summary.isSingleLine = false
summary.maxLines = 10 // Just need to be high enough I guess
summary.maxLines = 100 // Just need to be high enough I guess

if (swapTitleSummary) {
// Just do it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,25 @@ import fulguris.activity.WebBrowserActivity
import fulguris.di.UserPrefs
import fulguris.extensions.resizeAndShow
import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import android.webkit.CookieManager
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.webkit.CookieManagerCompat
import androidx.webkit.WebViewFeature
import androidx.webkit.WebViewFeature.GET_COOKIE_INFO
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import fulguris.app
import fulguris.extensions.copyToClipboard
import fulguris.extensions.snackbar
import fulguris.extensions.toast
import fulguris.preference.BasicPreference
import fulguris.utils.WebUtils
import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber
import javax.inject.Inject
Expand Down Expand Up @@ -104,15 +114,15 @@ class CookiesSettingsFragment : AbstractSettingsFragment() {
}

/**
*
* Try that stuff against http://setcookie.net which conveniently allows you to specify a path too
*/
private fun deleteAllPageCookies() {
Timber.v("Domain: $domain")
(requireActivity() as? WebBrowserActivity)?.let { browser ->
browser.tabsManager.currentTab?.url?.let { url ->
Timber.d("URL: $url")
val httpUrl = url.toHttpUrl()
val paths = httpUrl.encodedPathSegments.scan("/") { prefix, segment -> "$prefix$segment/" }
val paths = httpUrl.encodedPathSegments.scan("") { prefix, segment -> "$prefix/$segment" }
Timber.d("Paths: $paths")
val domains = ArrayList<String>()
var host = httpUrl.host
Expand All @@ -132,26 +142,54 @@ class CookiesSettingsFragment : AbstractSettingsFragment() {

// Build our list of cookies
val cm = CookieManager.getInstance()
val cookies = cm.getCookie(url)?.split(';')
Timber.v("Cookies count: ${cookies?.count()}")
cookies?.forEach { cookie ->
val cookies = WebUtils.getCookies(url)
Timber.v("Cookies count: ${cookies.count()}")
cookies.forEach { cookie ->
Timber.v(cookie.trim())
val parsed = cookie.split('=', limit = 2)
if (parsed.isNotEmpty()) {

val attrs = cookie.split(";").map { it.split("=", limit=2) }.map { it[0].trim() to if (it.count()==2) it[1].trim() else null }
val name = attrs[0].first
val value = attrs[0].second
val pathAttr = attrs.find { it.first.equals("path",true) }
val domainAttr = attrs.find { it.first.equals("domain",true) }

// If we have a path attribute we don't need to use brut force
if (pathAttr==null || domainAttr==null) {
//if (true) {
// Use legacy brut force way to delete cookies
// See: https://stackoverflow.com/q/35390590/3969362
val cookieName = parsed[0].trim()
// In our chain of domains delete all cookies on our path
// That's the only way to make sure we hit the cookie cause we can't tell where it was defined
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
// We can't delete cookies with __Secure- and __Host- prefixes
// I wonder how that works with our WebkitCookieManager?
domains.forEach { domain ->
paths.forEach { path ->
cm.setCookie(url,"$cookieName=;Domain=$domain;Path=$path;Max-Age=0") {
Timber.v("Cookie $cookieName deleted: $it")
val delCookie = "$name=;Domain=$domain;Path=$path;Max-Age=0"
Timber.d("Delete cookie: $delCookie")
cm.setCookie(url,delCookie) {
Timber.v("Cookie $name deleted: $it")
}
}
}
} else {
// New slightly more sane way to delete cookies
// Do it once without domain as this is needed when a path is specified
var delCookie = "$name=;Path=${pathAttr.second};Max-Age=0"
Timber.d("Delete cookie: $delCookie")
cm.setCookie(url,delCookie) {
Timber.v("Cookie $name deleted: $it")
}

// Do it once with domain as this is needed path is root /
delCookie = "$name=;Domain=${domainAttr.second};Path=${pathAttr.second};Max-Age=0"
//val delCookie = "$name=;Path=${pathAttr.second};Max-Age=0"
Timber.d("Delete cookie: $delCookie")
cm.setCookie(url,delCookie) {
Timber.v("Cookie $name deleted: $it")
}

// Just don't ask
}
}

Expand All @@ -168,44 +206,52 @@ class CookiesSettingsFragment : AbstractSettingsFragment() {
fun populateCookieList() {

catCookies?.removeAll()


var cookiesCount = 0

(requireActivity() as? WebBrowserActivity)?.let { browser ->
browser.tabsManager.currentTab?.url?.let { url ->
Timber.d("URL: $url")
// Build our list of cookies
val cookies = CookieManager.getInstance().getCookie(url)?.apply{Timber.v("Raw cookies: $this")}?.split(';')
Timber.v("Cookies count: ${cookies?.count()}")
cookiesCount = cookies?.count() ?: 0
cookies?.forEach { cookie ->
val cookies = WebUtils.getCookies(url)

Timber.v("Cookies count: ${cookies.count()}")
cookiesCount = cookies.count() ?: 0
cookies.forEach { cookie ->
Timber.v(cookie.trim())
val parsed = cookie.split('=', limit = 2)
if (parsed.count() == 2) {

// Create cookie preference
val pref = Preference(requireContext())
// Make sure domains and not reversed domains are shown as titles
pref.isSingleLineTitle = false
//pref.key = title
// We are using preference built-in alphabetical sorting by title
// We want sorting to group sub-domains together so we use reverse domain
pref.title = parsed[0].trim()
pref.summary = parsed[1].trim()
//pref.breadcrumb = domain

catCookies?.addPreference(pref)

/*
pref.fragment = "fulguris.settings.fragment.DomainSettingsFragment"
pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
this.domain = domain
app.domain = domain
false
}*/
// Create cookie preference
val pref = BasicPreference(requireContext())
// Make sure domains and not reversed domains are shown as titles
pref.isSingleLineTitle = false
//pref
var summary = ""
// For each attribute of that cookie
cookie.split(';').forEach { attr ->
if (pref.title.isNullOrEmpty()) {
// First deal with name and value
attr.split('=', limit = 2).forEach { comp ->
if (pref.title.isNullOrEmpty()) {
pref.title = comp.trim()
} else {
summary = comp.trim()
}
}
} else {
// We do not parse attributes beyond name and value
// Just display them as they are then
summary += "\n${attr.trim()}"
}
}

// Tapping on the preference copies the cookie to the clipboard
pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.copyToClipboard(cookie,"Cookie")
activity?.toast(R.string.message_cookie_copied)
false
}
pref.summary = summary
catCookies?.addPreference(pref)

}
}
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/fulguris/utils/WebUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import fulguris.database.history.HistoryRepository
import fulguris.utils.Utils.trimCache
import android.content.Context
import android.webkit.*
import androidx.webkit.CookieManagerCompat
import androidx.webkit.WebViewFeature
import io.reactivex.Scheduler
import timber.log.Timber
import java.io.File
Expand All @@ -37,6 +39,21 @@ import java.io.File
*/
object WebUtils {

/**
* Provide a list of cookies either using newest API which provides all cookie attributes or using legacy method which only provides cookie name and value
*/
fun getCookies(url: String): List<String> {
val cm = CookieManager.getInstance()

return if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_COOKIE_INFO) /*&& false*/) {
// New way provides all cookies attributes
CookieManagerCompat.getCookieInfo(cm,url)
} else {
// Legacy method provides only cookie name and value
cm.getCookie(url)?.apply{Timber.v("Raw cookies: $this")}?.split(';') ?: emptyList()
}
}

fun clearCookies(callback: ValueCallback<Boolean>? = null) {
val c = CookieManager.getInstance()
c.removeAllCookies {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<string name="message_cookies_cleared">Cookies cleared</string>
<string name="message_cookies_clear_error">Could not clear cookies</string>
<string name="max_tabs">Max tabs reached. Unlock more by purchasing a subscription.</string>
<string name="message_cookie_copied">Cookie copied to clipboard</string>
<string name="message_text_copied">Text copied to clipboard</string>
<string name="message_link_copied">Link copied to clipboard</string>
<string name="message_blocked_local">Local file has been blocked from loading</string>
Expand Down

0 comments on commit 6406cc4

Please sign in to comment.