Skip to content
  • Stefano Crocco's avatar
    Synchronize cookies between QWebEngine and KCookieServer · b10baef5
    Stefano Crocco authored
    Summary:
    KHTML part and KWebKitPart both use KCookieServer to manage cookies,
    while WebEnginePart doesn't. This has at least two consequences:
    - cookie settings made using the ""Cookie" page in Konqueror settings dialog or
      in SystemSettings aren't honoured
    - you can't use KIO to download files from many sites (in particular, sites
      requiring authentication) because the cookies received by the browser and
      those used by KIO are different.
    
    Qt WebEngine provide a class, `QWebEngineCookieStore` to allow synchronizing
    cookies between Qt WebEngine and other systems. Unfortunately, the API provided
    by this class is quite different from the API used by KCookieServer.
    
    I added a class, `WebEnginePartCookieJar` to manage the synchronization of cookies
    between Qt WebEngine and `KCookieServer`, then added a static variable of this
    class to `WebEnginePart`. This static variable is filled by `WebEnginePart`'s
    constructor the first time it's created.
    
    `WebEnginePartCookieJar` does the following:
    - disables persistent cookies in `QWebEngineProfile`. This way, cookies will
      only be stored on disk by `KCookieStore`
    - on creation, loads cookies from `KCookieServer` and adds them to
      `QWebEngineCookieStore`
    - in response to the `QWebEngineCookieStore::cookiesAdded` signal, it adds the
      cookie to `KCookieServer` according to the Cookies KCM settings
    - in response to the `QWebEngineCookieStore::cookiesRemoved` signal, it removes
      the cookie from `KCookieServer`
    - in response to the `QApplication::lastWindowClosed` signal, it calls
      `KCookieServer::deleteSessionCookies` for each window (having store each
      window's id earlier).
    
    Some functionality is missing, however, because of `KCookieServer`'s API:
    - there's no way to know when a cookie is added to `KCookieServer` or removed
      from it, so (aside from loading the coockies from `KCookieServer` when the
      `WebEnginePartCookieJar` is created) the synchronization is only one-way: from
      the `QWebEngineCookieStore` to `KCookieServer`. This should not be an issue
      most of the times, but it also means that, if the user deletes a cookie from
      the Cookies KCM while Konqueror is running, this change won't be propagated to
      `QWebEngineCookieStore` until Konqueror is restarted
    - when the cookie policy is set to "Ask", `KCookieServer` doesn't provide a way
      to find out what the user chose. As a workaround, `WebEnginePartCookieJar`
      checks whether the cookie exists in `KCookieStore` and removes it from the
      `QWebEngineCookieStore` if it doesn't. However, there's no way to distinguish
      between an "Accept" and "AcceptUntilSession" answer.
    
    Some tricks have been needed to make all this work. In particular:
    - `QWebEngineCookieStore` only provides the origin URL to the function set as
      cookie filter; however, `QWebEngineCookieStore::setCookieFilter` only exists
      since Qt 5.11, so on earlier Qt versions we can't determine whether a cookie
      is a cross origin cookie or not and honour the corresponding setting in the
      Cookie KCM
    - `KCookieServer::addCookies` requires an URL as parameter, but
      `QWebEngineCookieStore::cookiesAdded` doesn't provide one (I thought the URL
      passed to the cookie filter could be used, but there's no warranty that the
      order the requests are passed to the filter is the same in which cookies are
      added). Looking at the source code for `KCookieStore` and `KCookieJar`,
      however, it seems that they use this parameter mainly to determine the domain
      if it isn't specified in the cookie. Since the `QNetworkCookie` given by
      `KCookieServer::addCookies` seems to always have a non-empty domain, the URL
      is created using that domain as host
    - since `QWebEngineCookieStore` doesn't provide a way to find out which page
      made the request resulting in a cookie, there's no way to be sure of the
      window ID to pass to `KCookieServer::addCookies`. `WebEnginePartCookieJar`
      assumes that the window is the active one (this, of course, is only a problem
      when Konqueror has more than one window open)
    - there's an issue with expiration times. `KCookieJar` reads expiration times
      using `QDateTime::fromString` which, it seems, always interpret that time as
      local time even if expiration times in cookies is in GMT; this can lead to
      cookies to be considered expired when they should not. For example, if my
      local time is GMT +1 and I receive a cookie at 14:00 (local time) which
      expires in half an hour, it's expiration field will be something like
      "13:30:00 GMT"; however `KCookieJar` seems to interpret it as 13:30 local time
      and, since in local time, the time is 14:00, it considers the cookie expired.
      I worked around this issue by manually setting the time zone in the
      `QNetworkCookie` to GMT, but I can't understand why this happens (it also
      happens when calling `KCookieServer::addCookie` from the command line using
      `qdbus`, but it doesn't happen when using KHTMLPart).
    
    Test Plan: Open the "Cookies" page in SystemSettings, remove all cookies, use Konqueror to navigate web pages using cookies and note which cookies have appeared in the list; repeat the operation using KHTML and check that the cookies are the same.
    
    Reviewers: dfaure
    
    Reviewed By: dfaure
    
    Differential Revision: https://phabricator.kde.org/D14379
    b10baef5