From ae73342bf2b0586524ec1fbbfedb55f52648d2d4 Mon Sep 17 00:00:00 2001 From: Mike Conrad Date: Thu, 8 Aug 2024 07:58:33 -0400 Subject: [PATCH] Working POC --- manifest.json | 12 +++++ popup.html | 29 ++++++++++ popup.js | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 manifest.json create mode 100644 popup.html create mode 100644 popup.js diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..c0ba02a --- /dev/null +++ b/manifest.json @@ -0,0 +1,12 @@ + +{ + "name": "Cookie Monster", + "manifest_version": 3, + "version": "1.0", + "description": "Uses the chrome.cookies API by to allow for bulk edit/export/import/deletion of cookies", + "permissions": ["cookies"], + "host_permissions": [""], + "action": { + "default_popup": "popup.html" + } +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..7671c5b --- /dev/null +++ b/popup.html @@ -0,0 +1,29 @@ + + + + + + + +
+ + +
+
+ + + + +

+    
+  
+
diff --git a/popup.js b/popup.js
new file mode 100644
index 0000000..d0796ca
--- /dev/null
+++ b/popup.js
@@ -0,0 +1,145 @@
+const form = document.getElementById('control-row');
+const input = document.getElementById('input');
+const message = document.getElementById('message');
+const getCookieButton = document.getElementById('getCookiesButton');
+const setCookieButton = document.getElementById('saveCookiesButton');
+const cookieTextArea = document.getElementById('cookieInput');
+const cookieDisplayArea = document.getElementById('cookieJson');
+// The async IIFE is necessary because Chrome <89 does not support top level await.
+(async function initPopupWindow() {
+  let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
+
+  if (tab?.url) {
+    try {
+      let url = new URL(tab.url);
+      input.value = url.hostname;
+    } catch {
+      // ignore
+    }
+  }
+
+  input.focus();
+})();
+
+setCookieButton.addEventListener('click', async () => {
+  const cookieData = JSON.parse(cookieTextArea.value)
+    cookieData.map(async (cookie) => {
+      try {
+        let url = stringToUrl(input.value);
+
+        cookie.domain = url.hostname
+        cookie.url = `https://${url.hostname}${url.pathname}`
+        console.log('cookie', cookie)
+        delete cookie.hostOnly
+        delete cookie.session
+        await chrome.cookies.set(cookie)
+
+      } catch (e) {
+        console.log('e', e)
+      }
+      console.log('set this thing')
+    })
+
+})
+getCookieButton.addEventListener('click', async () => {
+  try {
+    let url = stringToUrl(input.value);
+
+    const cookies = await chrome.cookies.getAll({ domain: url.hostname });
+
+    if (cookies.length === 0) {
+      return 'No cookies found';
+    }
+    cookieDisplayArea.innerHTML = JSON.stringify(cookies, undefined, 2);
+
+  } catch (error) {
+    return `Unexpected error: ${error.message}`;
+  }
+})
+form.addEventListener('submit', handleFormSubmit);
+
+async function handleFormSubmit(event) {
+  event.preventDefault();
+
+  clearMessage();
+
+  let url = stringToUrl(input.value);
+  if (!url) {
+    setMessage('Invalid URL');
+    return;
+  }
+
+  let message = await deleteDomainCookies(url.hostname);
+  setMessage(message);
+}
+
+function stringToUrl(input) {
+  // Start with treating the provided value as a URL
+  try {
+    return new URL(input);
+  } catch {
+    // ignore
+  }
+  // If that fails, try assuming the provided input is an HTTP host
+  try {
+    return new URL('http://' + input);
+  } catch {
+    // ignore
+  }
+  // If that fails ¯\_(ツ)_/¯
+  return null;
+}
+
+async function deleteDomainCookies(domain) {
+  let cookiesDeleted = 0;
+  try {
+    const cookies = await chrome.cookies.getAll({ domain });
+
+    if (cookies.length === 0) {
+      return 'No cookies found';
+    }
+
+    let pending = cookies.map(deleteCookie);
+    await Promise.all(pending);
+
+    cookiesDeleted = pending.length;
+  } catch (error) {
+    return `Unexpected error: ${error.message}`;
+  }
+
+  return `Deleted ${cookiesDeleted} cookie(s).`;
+}
+
+function deleteCookie(cookie) {
+  // Cookie deletion is largely modeled off of how deleting cookies works when using HTTP headers.
+  // Specific flags on the cookie object like `secure` or `hostOnly` are not exposed for deletion
+  // purposes. Instead, cookies are deleted by URL, name, and storeId. Unlike HTTP headers, though,
+  // we don't have to delete cookies by setting Max-Age=0; we have a method for that ;)
+  //
+  // To remove cookies set with a Secure attribute, we must provide the correct protocol in the
+  // details object's `url` property.
+  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure
+  const protocol = cookie.secure ? 'https:' : 'http:';
+
+  // Note that the final URL may not be valid. The domain value for a standard cookie is prefixed
+  // with a period (invalid) while cookies that are set to `cookie.hostOnly == true` do not have
+  // this prefix (valid).
+  // https://developer.chrome.com/docs/extensions/reference/cookies/#type-Cookie
+  const cookieUrl = `${protocol}//${cookie.domain}${cookie.path}`;
+
+  return chrome.cookies.remove({
+    url: cookieUrl,
+    name: cookie.name,
+    storeId: cookie.storeId
+  });
+}
+
+function setMessage(str) {
+  message.textContent = str;
+  message.hidden = false;
+}
+
+function clearMessage() {
+  message.hidden = true;
+  message.textContent = '';
+}