kiwibrowser / src.next

Source-code for Kiwi Next, a Kiwi Browser auto-rebased with latest Chromium
BSD 3-Clause "New" or "Revised" License
2.21k stars 285 forks source link

The "chrome.cookies" category of WebExtension APIs appears to be unsupported #731

Open DavidJCobb opened 1 year ago

DavidJCobb commented 1 year ago

Describe the bug The chrome.cookies group of WebExtension APIs appears to be totally unsupported, but they fail silently, returning empty results instead of throwing an error. Neither Google searches nor searches in the issue tracker here turned up anything to suggest that this is a known or deliberate situation (e.g. the API being unimplemented/stubbed and on the to-do list).

To Reproduce

  1. Download the "Cookie Editor" WebExtension.
  2. Go to example.com.
  3. Open the browser developer tools for this tab via the main menu.
  4. Execute this line of JavaScript to set a new cookie: document.cookie = "test=123;"
  5. Execute this line of JavaScript to verify that your cookie has been set: document.cookie.
  6. Go back to the example.com tab.
  7. In the browser menu, open Cookie Editor for this tab.

Expected behavior Step 5 should show test=123, confirming that the cookie has been set. Step 7 should show the cookie.

Actual behavior Step 5 behaves as expected, but in step 7, Cookie Editor claims that no cookies exist for the site.

Smartphone (please complete the following information):

Additional context In WebExtensions I'm developing for private use, I've found that chrome.cookies.getAllCookieStores() always returns an empty array, and APIs like chrome.cookies.getAll() return empty results as well. A call to the chrome.permissions.contains API confirms that cookie permissions are granted for the domains I'm trying to target, as expected; the cookie APIs simply don't work in Kiwi. Chrome on desktop returns a list of cookie stores as expected when tested with my private-use extension under the same conditions.

The lack of chrome.cookies support is a nasty problem. The only way for WebExtension background pages/workers to send HTTP requests with credentials is by manually reading the user's cookies and jamming them into the outgoing request. This bug breaks that workaround, and the fact that the APIs are failing silently (coupled with Chrome's lackluster documentation for them) made this a hard problem to track down.

Possible duplicates from the old repo:

DavidJCobb commented 1 year ago

For WebExtension developers, a workaround is possible: find a tab that's already open to the target site (chrome.tabs.query), use chrome.scripting.executeScript to execute a function in that tab which returns document.cookie. Example:

async function findCookieAccessibleTab(incognito) {
   let tabs = await chrome.tabs.query({
      discarded: false,
      status:    "complete",
      url:       "*://*.example.com/*",
   });
   if (incognito !== void 0)
      tabs = Array.from(tabs).filter(function(e) {
         return e.incognito == incognito;
      });
   if (tabs.length) {
      for(let tab of tabs)
         if (tab.active)
            return tab.id;
      //
      // Pick a random tab:
      //
      let i   = Math.floor(Math.random() * tabs.length);
      let tab = tabs[i];
      if (!tab || !tab.id)
         tab = tabs[0];
      return tab.id;
   }
   return Promise.reject("no-worker-tab-available");
}

function _getCookiesImpl() {
   return document.cookie;
}
async function getCookies(tabId, incognito) {
   let cookie = "";
   if (!tabId || isNaN(tabId)) {
      tabId = await findCookieAccessibleTab(incognito);
   }
   let results = await chrome.scripting.executeScript({
      func:   _getCookiesImpl,
      target: { tabId: tabId },
      injectImmediately: true,
   });
   if (!results) {
      throw Promise.reject("unknown");
   }
   return results[0].result;
}
DavidJCobb commented 1 year ago

A more reliable approach is to intercept Cookie headers on outgoing requests, and Set-Cookie headers on incoming responses, and use these to maintain a cache of cookie states in the background page. This should allow you to get even HttpOnly cookies. Then, use declarativeNetRequests or other means to forcibly set the cookies you want to use for background page fetch requests, background page IFRAMEs, and similar.

The nice thing about this is that you never need to send the cookie across from a content script to the background page; the data stays in the background page/process.