Search code examples
javascriptbrowsercontextmenufirefox-addon-webextensions

Javascript Browser WebExtension API - Get all headers and other parameters of the active tab


I'm developing an easy addon for the browser for exercise since I'm new on javascript and I'm reading documentation related to the topic by https://developer.mozilla.org/en-US/docs/Web/API

My purpose is able to replicate the "Copy as cURL" of the Network tab inside the Browser devtools inside a context menu that I'm creating.

For example if I consider a "Copy as cURL" command by https://stackoverflow.com (by using Firefox 91.5.0esr (64-bit)):

curl 'https://stackoverflow.com/' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: https://www.google.com/' -H 'Connection: keep-alive' -H 'Cookie: prov=d03ecfc2-207d-cda2-bb6b-38b282cd5b84; _ga=GA1.2.1551734572.1648346036; _gid=GA1.2.1710305938.1648346036; _gat=1' -H 'Upgrade-Insecure-Requests: 1' -H 'Sec-Fetch-Dest: document' -H 'Sec-Fetch-Mode: navigate' -H 'Sec-Fetch-Site: cross-site' -H 'Sec-Fetch-User: ?1' -H 'Cache-Control: max-age=0' -H 'TE: trailers'

I'm able to get the Cookie values but not the other headers or any other parameters (i.e., POST data parameters). Until now my code is the following:

script.js

/*
Called when the item has been created, or when creation failed due to an error.
We'll just log success/failure here.
*/
function onCreated() {
  if (browser.runtime.lastError) {
    console.log(`Error: ${browser.runtime.lastError}`);
  } else {
    console.log("Item created successfully");
  }
}

/*
Called when the item has been removed.
We'll just log success here.
*/
function onRemoved() {
  console.log("Item removed successfully");
}

/*
Called when there was an error.
We'll just log the error here.
*/
function onError(error) {
  console.log(`Error: ${error}`);
}

/*
Create all the context menu items.
*/

browser.menus.create({
  id: "tools-copy",
  title: browser.i18n.getMessage("menuItemToolsCopy"),
  contexts: ["all"],
}, onCreated);

browser.menus.create({
  id: "tools-copy-as-cURL-example",
  parentId: "tools-copy",
  type: "radio",
  title: browser.i18n.getMessage("menuItemToolsCopyAsCURL"),
  contexts: ["all"],
  checked: false
}, onCreated);

/*
functions to impl
*/

function updateClipboard(newClip) {
  navigator.clipboard.writeText(newClip).then(function() {
    /* clipboard successfully set */
  }, function() {
    /* clipboard write failed */
  });
}

//get active tab to run an callback function.
//it sends to our callback an array of tab objects
function getActiveTab() {
  return browser.tabs.query({currentWindow: true, active: true});
}

function showCookiesForTab(tabs) {
  //get the first tab object in the array
  let tab = tabs.pop();

  //get all cookies in the domain
  var gettingAllCookies = browser.cookies.getAll({url: tab.url});
  var str_cookies = "";

  gettingAllCookies.then((cookies) => {
    if (cookies.length > 0) {
      str_cookies = "-H 'Cookie: ";
      for (let cookie of cookies) {
        str_cookies = str_cookies.concat(cookie.name + "="+ cookie.value+"; ");
      }
      str_cookies = str_cookies.replace(/.{0,2}$/,"'");
      console.log(str_cookies);
    }
  });
}

/*
The click event listener, where we perform the appropriate action given the
ID of the menu item that was clicked.
*/
browser.menus.onClicked.addListener((info, tab) => {
  switch (info.menuItemId) {    
    case "tools-copy-as-cURL-example":
      getActiveTab().then(showCookiesForTab);
      break;
  }
});

message.json

{  
  "extensionName": {
    "message": "Copy as cURL demo",
    "description": "Name of the extension."
  },

  "extensionDescription": {
    "message": "Demonstrates the menus API for copying cURL.",
    "description": "Description of the add-on."
  },

  "menuItemToolsCopy": {
    "message": "Copy",
    "description": "Title of tools copy item."
  },

  "menuItemToolsCopyAsCURL": {
    "message": "Copy as cURL",
    "description": "Title of cURL copy item."
  }
}

manifest.json

{
  "manifest_version": 2,
  "name": "__MSG_extensionName__",
  "description": "__MSG_extensionDescription__",
  "version": "1.0",
  "default_locale": "en",
  "browser_specific_settings": {
    "gecko": {
      "strict_min_version": "56.0a1"
    }
  },

  "background": {
    "scripts": ["script.js"]
  },
  
  "permissions": [
    "menus",
    "activeTab",
    "cookies",
    "webRequest",
    "<all_urls>",
    "tabs",
    "clipboardWrite"
  ]
}

According to the documentation, I tried to use this object array https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/HttpHeaders and also the browser.webRequest.onHeadersReceived.addListener() but with no luck.

My purpose is only to read the headers of the current active tab of the browser. I don't need to edit them.

How can I get all the headers and any data parameters (in case of POST requests)? Sorry for these easy requests, I'm new on this topic.

Thank you in advance.


Solution

  • There's no way to get this info retroactively for a tab, so you'll have to observe the network constantly and store the data in a global object per each tab id and frame id to support frames.

    const tabData = {};
    const getProp = (obj, key) => (obj[key] || (obj[key] = {}));
    const encodeBody = body => 'implement it yourself';
    
    const FILTER = {
      types: ['main_frame', 'sub_frame'],
      urls: ['<all_urls>'],
    };
    
    browser.webRequest.onBeforeRequest.addListener(e => {
      getProp(getProp(tabData, e.tabId), e.frameId).body = e.requestBody;
    }, FILTER, ['requestBody']);
    
    browser.webRequest.onSendHeaders.addListener(e => {
      getProp(getProp(tabData, e.tabId), e.frameId).headers = e.requestHeaders;
    }, FILTER, ['requestHeaders']);
    
    browser.tabs.onRemoved.addListener(tabId => delete tabData[tabId]);
    
    browser.tabs.onReplaced.addListener((addId, delId) => delete tabData[delId]);
    
    browser.menus.onClicked.addListener((info, tab) => {
      if (info.menuItemId === 'tools-copy-as-cURL-example') {
        const data = tabData[tab.id]?.[info.frameId || 0] || {};
        navigator.clipboard.writeText(`curl '${info.frameUrl || tab.url}'` +
          (data.headers?.map(h => ` -H '${h.name}: ${h.value}'`).join('') || '') +
          (data.body ? ' ' + encodeBody(data.body) : ''));
      }
    });