Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an efficient Android CookieJar (sqldelight?) #2890

Open
swankjesse opened this issue Oct 4, 2016 · 25 comments
Open

Implement an efficient Android CookieJar (sqldelight?) #2890

swankjesse opened this issue Oct 4, 2016 · 25 comments
Assignees
Labels
enhancement Feature not a bug
Milestone

Comments

@swankjesse
Copy link
Collaborator

The JavaNetCookieJar is a hazard to use, particularly because it uses HttpCookie behind the scenes. That class unnecessarily quotes cookie values, and we need to do gross things to strip them off. This behavior is particularly bad because sometimes it doesn’t strip the quotes and we end up stripping off quotes that we shouldn’t.

@leeight
Copy link

leeight commented Oct 5, 2016

Is it possible add QuotePreservingCookieJar.java as a replacement for JavaNetCookieJar.java
https://gist.github.com/swankjesse/e283f2ddf264abd9a0d45bc80e7ec2d8

@swankjesse
Copy link
Collaborator Author

Yeah, just copy it into your codebase.

@leeight
Copy link

leeight commented Oct 6, 2016

Could you release a new version okhttp3? React-Native need QuotePreservingCookieJar.java to address Issue 10121.

@swankjesse
Copy link
Collaborator Author

We will not be adding QuotePreservingCookieJar.java to OkHttp. You’ll have to copy it into your own project.

@dave-r12
Copy link
Collaborator

Still up for this one? Mind if I take a crack at it?

I've started reviewing https://tools.ietf.org/html/rfc6265 and the internal Java (CookieManager/InMemoryCookieStore) implementations. I assume this will be replacing those things. It seems okhttp3.Cookie is already doing a lot of the heavy lifting, so thinking this shouldn't be a huge undertaking.

@swankjesse
Copy link
Collaborator Author

Yeah for sure. The most difficult part is deciding how the persistence model works. I’d love to have Sqlite but sadly we don’t. What do you think?

@dave-r12
Copy link
Collaborator

Ahh, I see. So this should support automatic disk persistence, in addition to in memory.

RE: SQLite - I'm still getting up to speed so I'm not entirely sure the role there. My hunch is that would provide faster access when reading cookies from disk on a fresh load. I wonder if a simple index would work there. I don't know enough yet on the access patterns, so maybe it wouldn't be enough.

@brillenheini
Copy link

I, from my perspective as a user, would be very happy with an in-memory implementation as a first addition.

@swankjesse
Copy link
Collaborator Author

There’s a handful of interesting problems we’d be able to solve with a java.util.Properties style interface:

  • cookies
  • HPKP
  • remembering which hosts support HTTP/2?
  • remembering proxy precedence?

Maybe that’s sufficient to start?

@dave-r12
Copy link
Collaborator

Alright, I'll attempt to draft something using the CookieJar as a first client and see where that takes me.

@dave-r12
Copy link
Collaborator

I did some experimenting here and realized, would it make sense to back this with DiskLruCache? It seems any key-value like storage would behave in a similar way:

  • Store arbitrary data with a given key.
  • Set an upper bound on the size of the store.
  • Have a policy for evicting entries when size exceeds upper bound.
  • Handle faulty filesystems.

All of these are supported by DiskLruCache.

@swankjesse
Copy link
Collaborator Author

You’re blowing my mind a bit, I never thought of that as an option! Yeah, for sure.

Keys would be domain names?

@franmontiel
Copy link
Contributor

franmontiel commented Jan 24, 2017

In case it can be of any help you can check my implementation of a PersistentCookieJar.

Some problems that I see with it for the moment:

  • It uses SharedPreferences to store the cookies so is not platform independent.
  • It loads all cookies in memory, maybe not problematic with the majority of apps but I suppose it won't scale well with cookie intensive usage (a browser for example).

Both problems can be solved with your proposals. For example using the DiskLruCache and storing all cookies of the same domain(ignoring subdomains) into .properties files and deserializing the .properties into a collection in memory after the first call to loadForRequestUrl() for any URL of the domain.

@dave-r12
Copy link
Collaborator

@swankjesse yes, that's my thought right now.

Thanks @franmontiel, I'll have a look.

@swankjesse swankjesse modified the milestones: 3.6, 3.7 Jan 29, 2017
@swankjesse swankjesse modified the milestones: 3.7, 3.8 Apr 28, 2017
@swankjesse swankjesse modified the milestones: 3.10, 3.9 Aug 30, 2017
@hey99xx
Copy link

hey99xx commented Jan 18, 2018

@swankjesse I'm a little confused why QuotePreservingCookieJar does not become the standard implementation if JavaNetCookieJar is a hazard for cookies with quotes.

I have a use case for explicitly using cookie values surrounded by quotes such as x-mycookie="3NO432" but I just realized the quotes are manually stripped off.

Is there any plan for future OkHttp3 releases stopping the deletion of quotes in JavaNetCookieJar?

I'm syncing cookies between android.webkit.CookieManager and OkHttp client. In Chrome dev console network tab, the cookies are shown correctly with quotes, but with OkHttp they're incorrect.


Just realized 3.10 milestone on the sidebar. Is that when the new default cookie jar implementation aimed to be released in? Is the recommendation just replacing JavaNetCookieJar with QuotePreservingCookieJar until then?

@swankjesse
Copy link
Collaborator Author

We'll probably punt this beyond 3.10. Getting this right is challenging because OkHttp can't use Android-only APIs. I recommend the persistent cookie jar code linked above if you're targeting Android and not the JVM.

@cogman
Copy link

cogman commented Oct 23, 2019

With the release of 4.x, where does this issue stand?

@arcao
Copy link

arcao commented Mar 17, 2020

I use this implementation to store cookies in memory (Inspired by PersistentCookieJar).

class MemoryCookieJar : CookieJar {
    private val cache = mutableSetOf<WrappedCookie>()

    @Synchronized
    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        val cookiesToRemove = mutableSetOf<WrappedCookie>()
        val validCookies = mutableSetOf<WrappedCookie>()

        cache.forEach { cookie ->
            if (cookie.isExpired()) {
                cookiesToRemove.add(cookie)
            } else if (cookie.matches(url)) {
                validCookies.add(cookie)
            }
        }

        cache.removeAll(cookiesToRemove)

        return validCookies.toList().map(WrappedCookie::unwrap)
    }

    @Synchronized
    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        val cookiesToAdd = cookies.map { WrappedCookie.wrap(it) }

        cache.removeAll(cookiesToAdd)
        cache.addAll(cookiesToAdd)
    }

    @Synchronized
    fun clear() {
        cache.clear()
    }
}

class WrappedCookie private constructor(val cookie: Cookie) {
    fun unwrap() = cookie

    fun isExpired() = cookie.expiresAt < System.currentTimeMillis()

    fun matches(url: HttpUrl) = cookie.matches(url)

    override fun equals(other: Any?): Boolean {
        if (other !is WrappedCookie) return false

        return other.cookie.name == cookie.name &&
            other.cookie.domain == cookie.domain &&
            other.cookie.path == cookie.path &&
            other.cookie.secure == cookie.secure &&
            other.cookie.hostOnly == cookie.hostOnly
    }

    override fun hashCode(): Int {
        var hash = 17
        hash = 31 * hash + cookie.name.hashCode()
        hash = 31 * hash + cookie.domain.hashCode()
        hash = 31 * hash + cookie.path.hashCode()
        hash = 31 * hash + if (cookie.secure) 0 else 1
        hash = 31 * hash + if (cookie.hostOnly) 0 else 1
        return hash
    }

    companion object {
        fun wrap(cookie: Cookie) = WrappedCookie(cookie)
    }
}

Usage:

val okHttp = OkHttpClient.Builder()
    .cookieJar(MemoryCookieJar())
    .build()

@headsvk
Copy link

headsvk commented Dec 7, 2020

I would also like to see OkHttp's own CookieJar implementation. I found another issue with JavaNetCookieJar on Android caused by an Android-only change to java's CookieManager class which forced me to create my own simple CookieJar. https://issuetracker.google.com/issues/174647435

@swankjesse
Copy link
Collaborator Author

@headsvk yikes. Looks like they inexplicably introduced a regression.

@yschimke
Copy link
Collaborator

yschimke commented Dec 7, 2020

Arguably this paragraph in the spec says that a Set-Cookie with non matching path can be rejected

Cookies do not always provide isolation by path. Although the network-level protocol does not send cookies stored for one path to another, some user agents expose cookies via non-HTTP APIs, such as HTML's document.cookie API. Because some of these user agents (e.g., web browsers) do not isolate resources received from different paths, a resource retrieved from one path might be able to access cookies stored for another path.

But this is a stretch, and it isn't clearly spelled out in MUST, SHOULD terms. Just implied here.

@yschimke
Copy link
Collaborator

yschimke commented Dec 8, 2020

We'll probably punt this beyond 3.10. Getting this right is challenging because OkHttp can't use Android-only APIs. I recommend the persistent cookie jar code linked above if you're targeting Android and not the JVM.

Any opinion as of 4.9.0 of whether you'd be happy with an okhttp-android module using Android APIs here. I think we have test infra now (locally, not CI yet) to build against this correctly.

@swankjesse
Copy link
Collaborator Author

The feature we want is sqlite storage, or perhaps even MySQL storage. Doing a module that uses SQDelight to store cookies to a DB would be a nice win. Could also do similarly for HSTS, for example.

@yschimke
Copy link
Collaborator

@swankjesse is this worthwhile for 5.0? We can now use android APIs.

@yschimke yschimke self-assigned this May 20, 2023
@yschimke yschimke changed the title Provide our own CookieJar implementation Implement an efficient Android CookieJar (sqldelight?) May 21, 2023
@swankjesse
Copy link
Collaborator Author

Not for 5.0. Good for the backlog.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature not a bug
Projects
None yet
Development

No branches or pull requests

10 participants