1

I need to access all the cookies from a JavaFX WebView. As far as I can see there's a com.sun.webkit that implements its own CookieManager, Cookie, etc, etc. In that implementation, the cookies are hidden away and there's no way to access them.

Am I correct in thinking there are only two solutions:

  • Implement my own CookieManager
  • Use reflection to access the internals of CookieManager

Am I missing anything here?

Implementing my own CookieManager is no trivial task. com.sun.webkit's is more than 1500 lines of code.

Using reflection to access the internals is quite horrible. So far, I managed to dump the cookies to a JSON file but look at the code:

 private void saveCookies() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, IOException {
    CookieManager cookieManager = (CookieManager) CookieHandler.getDefault();
    Field f = cookieManager.getClass().getDeclaredField("store");
    f.setAccessible(true);
    Object cookieStore = f.get(cookieManager);

    Field bucketsField = Class.forName("com.sun.webkit.network.CookieStore").getDeclaredField("buckets");
    bucketsField.setAccessible(true);
    Map buckets = (Map) bucketsField.get(cookieStore);
    f.setAccessible(true);
    Map<String, Collection> cookiesToSave = new HashMap<>();
    for (Object o : buckets.entrySet()) {
        Map.Entry pair = (Map.Entry) o;
        String domain = (String) pair.getKey();
        Map cookies = (Map) pair.getValue();
        cookiesToSave.put(domain, cookies.values());
    }

    Gson gson = new GsonBuilder().create();
    String json = gson.toJson(cookiesToSave);
    System.out.println(json);

    Files.write(Paths.get("cookies.json"), json.getBytes());
}

Following @James_D comments, I tried making a wrapper for CookieManager, something like this:

package sample;

import com.sun.webkit.network.CookieManager;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.CookieHandler;
import java.net.URI;
import java.util.*;

public class MyCookieManager extends CookieHandler {
    private final CookieManager cookieManager = new CookieManager();
    private final Set<URI> uris = new HashSet<>();

    MyCookieManager() {

    }

    @Override
    public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException {
        uris.add(uri);
        return cookieManager.get(uri, requestHeaders);
    }

    @Override
    public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException {
        uris.add(uri);
        cookieManager.put(uri, responseHeaders);
    }

    void save() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("Saving cookies");
        System.out.println(uris);
        for (URI uri : uris) {
            System.out.println(uri);
            System.out.println(cookieManager.get(uri, new HashMap<>()));
        }
    }
}

After accessing Google, when I call save, I get this:

https://ssl.gstatic.com/gb/images/b_8d5afc09.png
{}
https://www.google.co.uk/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png
{Cookie=[NID=110=TnISuEnlryFjSzvLgWXIuTBRDx_CTBlgPOqFzCPXM88M0xQY_1nSJEG2uyCxk-RFNbdexuen5A_3CZXHevseXhRSKJmRSHis0pUb4kvFCxliWT8RsKvRXoAxBYJhGGd2]}
https://www.google.co.uk/client_204?&atyp=i&biw=500&bih=455&ei=Q72cWa_IJ4KhUc6xpqAH
{Cookie=[NID=110=TnISuEnlryFjSzvLgWXIuTBRDx_CTBlgPOqFzCPXM88M0xQY_1nSJEG2uyCxk-RFNbdexuen5A_3CZXHevseXhRSKJmRSHis0pUb4kvFCxliWT8RsKvRXoAxBYJhGGd2]}
https://clients1.google.co.uk/generate_204
{Cookie=[NID=110=TnISuEnlryFjSzvLgWXIuTBRDx_CTBlgPOqFzCPXM88M0xQY_1nSJEG2uyCxk-RFNbdexuen5A_3CZXHevseXhRSKJmRSHis0pUb4kvFCxliWT8RsKvRXoAxBYJhGGd2]}
https://www.google.co.uk/xjs/_/js/k=xjs.hp.en_US.HWKnNOqE224.O/m=sb_he,d/am=ABg/rt=j/d=1/t=zcms/rs=ACT90oFd7WTU33BO8_uKNZdqfket_iKTeg
{Cookie=[NID=110=TnISuEnlryFjSzvLgWXIuTBRDx_CTBlgPOqFzCPXM88M0xQY_1nSJEG2uyCxk-RFNbdexuen5A_3CZXHevseXhRSKJmRSHis0pUb4kvFCxliWT8RsKvRXoAxBYJhGGd2]}
https://google.com/
{}
https://www.google.co.uk/?gfe_rd=cr&ei=Q72cWb30H9P38Ae7lKagCw
{Cookie=[NID=110=TnISuEnlryFjSzvLgWXIuTBRDx_CTBlgPOqFzCPXM88M0xQY_1nSJEG2uyCxk-RFNbdexuen5A_3CZXHevseXhRSKJmRSHis0pUb4kvFCxliWT8RsKvRXoAxBYJhGGd2]}
https://www.google.co.uk/images/nav_logo229.png
{Cookie=[NID=110=TnISuEnlryFjSzvLgWXIuTBRDx_CTBlgPOqFzCPXM88M0xQY_1nSJEG2uyCxk-RFNbdexuen5A_3CZXHevseXhRSKJmRSHis0pUb4kvFCxliWT8RsKvRXoAxBYJhGGd2]}
https://ssl.gstatic.com/gb/js/sem_e63c08fa6f0a34d4ef28b1bece13b39d.js
{}

As you can see there's a lot of repetition and I don't have access to a lot of the original information of the cookie. In this case, it should be just one cookie (I think) that looks like this:

{
  "google.co.uk": [
    {
      "name": "NID",
      "value": "110\u003dZVaVkKbDxN3aQJYtBTWUQ9G5yc4pGb4TpB4EmTJA9bUB2a27ukBdWh28arRI52lw2Tt9hTtXxLhuWFQl55JaQXUJ3hwQzAFXKI0FB-RCU67MmgvCj9wxgqs9yg7BzBCo",
      "expiryTime": 1519255457000,
      "domain": "google.co.uk",
      "path": "/",
      "creationTime": {
        "baseTime": 1503444256974,
        "subtime": 0
      },
      "lastAccessTime": 1503444257350,
      "persistent": true,
      "hostOnly": false,
      "secureOnly": false,
      "httpOnly": true
    }
  ]
}
Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • You can get the cookies by URI from the default cookie handler, if you know all the relevant URIs. Is that enough, or do you need to programmatically figure out the URIs for which the cookies are set? – James_D Aug 22 '17 at 22:39
  • I need to programatically get all the cookies. I won't have all the URIs. – Pablo Fernandez Aug 22 '17 at 22:40
  • So I think you have to implement your own `CookieHandler`. It might not be too bad: you can probably just wrap the default implementation and forward the two abstract methods to it; just decorate your `put` implementation so it stores all the `URI`s in a set. Admittedly, I have not tried this :). – James_D Aug 22 '17 at 22:44
  • @James_D: I can do that, but what do I do with the set of URIs? – Pablo Fernandez Aug 22 '17 at 23:10
  • The set of URIs would tell you all the URIs for which cookies had been stored. So you could iterate over them and call `cookieHandler.get(uri, new HashMap<>())` for each one. – James_D Aug 22 '17 at 23:20
  • When I do that, I get the cookie for the domain of each uri. I haven't tried to see if that works with put, because the actual domain got lost. If I go to google.com I get only one cookie, but there are about 10 different URIs, all that return the cookie when I call get. – Pablo Fernandez Aug 22 '17 at 23:26
  • Not sure I understand. Aren't cookies stored on a domain-by-domain basis? Browsers will typically only send cookies when they send requests to domains from which that cookie was set. So having each domain listed is exactly what you want, I think. If I open up my Chrome preferences and look at the cookies it has stored, I see a similar thing: the same cookie for different subdomains of some sites. – James_D Aug 22 '17 at 23:32
  • If you really want unique cookies, couldn't you just iterate through the URIs, get the cookies from each URI, and accumulate them into a single `Map>`. Then you would have a unique collection of cookies across all domains. – James_D Aug 22 '17 at 23:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/152578/discussion-between-pablo-and-james-d). – Pablo Fernandez Aug 22 '17 at 23:36
  • Yeah... don't use `com.sun` classes, ever. I tried wrapping `CookieManager` instead of `CookieHandler` and using the cookie store to get at the higher-level API, but that didn't seem to work. I'll leave the answer I have for now, but see if I can improve (or maybe someone with better working knowledge of the cookie API in general may give a better answer). – James_D Aug 23 '17 at 00:07
  • Why not using the com.sun classes? – Pablo Fernandez Aug 23 '17 at 00:14
  • They are not part of the public API and there is no guarantee they will remain from one release to another. In Java 9, which is due to be released in just a few weeks, they will not be accessible. – James_D Aug 23 '17 at 00:26
  • I don't see how accessing com.sun would be worst than having to re-write the thing though. – Pablo Fernandez Aug 23 '17 at 00:32
  • Because it might completely stop working when a user upgraded their JRE version... – James_D Aug 23 '17 at 00:34
  • It would only stop on a Java upgrade and I'll notice it because the app will not work. On the other hand, if I implement my own cookie manager, I might have bugs that would never get fixed because that code is not being maintained like Java's CookieManager. – Pablo Fernandez Aug 23 '17 at 00:36
  • But you generally have absolutely no control over what JRE your users are using, apart from specifying a minimum version. Seriously, using non-public API is always an error. – James_D Aug 23 '17 at 00:38
  • Don't you normally ship the JRE with the installer/executable? – Pablo Fernandez Aug 23 '17 at 00:39
  • Not usually, no. It depends a bit on the application and target audience, but the problem with self-contained application packages is you have to build for every platform you want to support (and you have to do each build on that particular platform). So I typically always provide and support a plain vanilla jar file, even if I provide self-contained application packages too. – James_D Aug 23 '17 at 00:44
  • I'll have to support each and every platform individually anyway. I need to provide .exes as well. – Pablo Fernandez Aug 23 '17 at 00:46
  • Well, you still prohibit yourself from upgrading to Java 9, so I'd still (very) strongly recommend against using non-public API. What happens if you simply wrap (or subclass) `java.net.CookieManager`? – James_D Aug 23 '17 at 00:51
  • Either wrapping or subclassing CookieManager, I need to implement cookie parsing at the very least. – Pablo Fernandez Aug 23 '17 at 00:56
  • Doesn't `java.net.CookieManager` already implement all of that, via `getCookieStore().getCookies()` etc? (You probably don't even need to subclass or wrap it, the default implementation may work; I previously assumed this was an abstract class and that there was an internal implementation class.) The downside, which is hinted at in https://stackoverflow.com/questions/14385233/setting-a-cookie-using-javafxs-webengine-webview, is that it implements RFC2965, which has been obsoleted. I don't know the details of the different RFCs, but it may work well enough. – James_D Aug 23 '17 at 01:21
  • @James_D: I don't understand how I missed that. Thanks. I'm going to experiment with that. – Pablo Fernandez Aug 23 '17 at 10:13
  • @James_D ah... found the issue... java.net.CookieHandler doesn't implement/require getCookieStore. java.net.CookieManager implements that method but the default cookie manager when using webkit is com.sun.webkit.network.CookieManager, which doesn't implement that method. I read Oracle recommends staying away from java.net.CookieManager as it's not RFC compliant. – Pablo Fernandez Aug 23 '17 at 10:33

2 Answers2

3

(Updated after doing some research...)

Java's default implementation of CookieHandler is java.net.CookieManager, which provides the API and functionality I think you are looking for (supported by the CookieStore and HttpCookie classes). Referring to Setting a cookie using JavaFX's WebEngine/WebView and reading between the lines a little, it seems that java.net.CookieManager supports an obsolete standard (RFC 2965), and because of that, the JavaFX team replaced it with a non-public-API class that (I am assuming) supports the later RFC 6265. The private-API implementation, com.sun.webkit.network.CookieManager is not a subclass of java.net.CookieManager and doesn't provide access to any of the other java.net supporting classes. Note also that using any of the API from that class directly will prevent you updating to Java 9 at any point.

So I think you have two choices. If you can live with a RFC 2965-compliant implementation, all you need to do is to revert to the java.net.CookieManager implementation: just call

CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);

after instantiating the WebView. Then you can use the standard API to access details of the cookies:

cookieManager.getCookieStore().getCookies().forEach(cookie -> {
    String name = cookie.getName();
    String value = cookie.getValue();
    String domain = cookie.getDomain();
    long maxAge = cookie.getMaxAge(); // seconds
    boolean secure = cookie.getSecure();

    // etc...
});

Note that the HttpCookie class defines a matching algorithm to determine if a particular host is part of a particular domain, so it avoids replicating cookies over multiple URIs belonging to the same domain.

If you need the more recent standard, then it seems you either have to do some fairly dirty hacking on the non-public classes, or you need to implement your own cookie handler (which as you've observed, is not a trivial task).

James_D
  • 201,275
  • 16
  • 291
  • 322
1

There is no direct way in java as far as I know, but you can communicate with the javascript side of a webPage, meaning you could do something like this: (not tested!)

WebView wv; //assumed to be initialized
WebEngine we = wv.getEngine();
String cookie = (String)we.executeScript("return document.cookie"):

Yes this means you got the usual restrictions to cookie access as you normally have in javascript. But it might be enough in your case.

EDIT:

Apparently its also possible using the java.net library (e.g. java.net.CookieHandler.getDefault()). In this SO post you can find a bit more on how to use it, and here is the CookieManager documentation.

gouessej
  • 3,640
  • 3
  • 33
  • 67
n247s
  • 1,898
  • 1
  • 12
  • 30
  • I need to get all the cookies, not just the one for the current document. – Pablo Fernandez Aug 22 '17 at 22:17
  • @Pablo I updated my answer. I dont know how reliable the `java.net`'s coockie manager is, though you might be better of implementing your own. – n247s Aug 22 '17 at 22:34
  • JavaFXs webview doesn't use java.net's cookie manager, it uses com.sun.webkit's cookie manager. – Pablo Fernandez Aug 22 '17 at 22:37
  • Im currently not at a desktop, so its kind of hard to check the implementations etc.. I made the assumption based on the linked SO post (which is for javafx2, so it might have changed in the meantime indeed). In the SO post there is a small snippit which you could easily test. If that doesn't work, than I'm out of idea's unfortunatly. – n247s Aug 22 '17 at 22:49
  • @Pablo I think `com.sun.webkit.CookieManager` is the default implementation irrespective of whether you are using JavaFX or just plain old Java. – James_D Aug 22 '17 at 23:59
  • @James_D The default implementation is java.net.CookieManager which differs from com.sun.webkit.CookieManager. – dejuknow Aug 23 '17 at 00:43
  • @dejuknow Ah, OK. Got it. I thought that was abstract, for some reason. – James_D Aug 23 '17 at 00:46