Override HTTP responses from a Chrome extension - google-chrome

I'm making an extension that will be able to modify request responses. I did some research, found this answer https://stackoverflow.com/a/51594799/16661157 and implemented it into my extension.
When debugging this script, it detects the majority of requests but not all, unfortunately the most important for me are not. So my question is: what could be causing this?
manifest.json
{
"manifest_version": 3,
"permissions": [
"tabs"
],
"web_accessible_resources": [{
"resources": ["script.js"],
"matches": [ "<all_urls>" ]
}],
"action": {
"default_popup": "index.html"
},
"content_scripts": [
{
"matches": [ "<all_urls>" ],
"js": ["jquery-3.6.0.min.js", "contentscript.js"],
"run_at": "document_start"
}
]
}
contentscript.js
var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
script.js
var _open = XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, URL) {
var _onreadystatechange = this.onreadystatechange,
_this = this;
_this.onreadystatechange = function () {
// catch only completed 'api/search/universal' requests
try {
console.log('Caught! :)', method, URL/*, _this.responseText*/);
} catch (e) {}
if (_this.readyState === 4 && _this.status === 200 && ~URL.indexOf('api/search/universal')) {
try {
//////////////////////////////////////
// THIS IS ACTIONS FOR YOUR REQUEST //
// EXAMPLE: //
//////////////////////////////////////
var data = JSON.parse(_this.responseText); // {"fields": ["a","b"]}
if (data.fields) {
data.fields.push('c','d');
}
// rewrite responseText
Object.defineProperty(_this, 'responseText', {value: JSON.stringify(data)});
/////////////// END //////////////////
} catch (e) {}
}
// call original callback
if (_onreadystatechange) _onreadystatechange.apply(this, arguments);
};
// detect any onreadystatechange changing
Object.defineProperty(this, "onreadystatechange", {
get: function () {
return _onreadystatechange;
},
set: function (value) {
_onreadystatechange = value;
}
});
return _open.apply(_this, arguments);
};

Related

MV3 declarativeNetRequest and X-Frame-Options DENY

I have a MV2 extension with chrome.webRequest that works perfectly but fail on MV3 declarativeNetRequest getting around iframes.
The extension is like a multi-messenger that opens multiple iframes for various sites to merge in a single extension all popular messengers.
So I have a domain "example.com" and there I open multiple iframes, for example open an iframe with Twitter.com or Telegram.org.
Since twitter.com or telegram.org set the X-Frame-Options to DENY those iframes don't show anything.
With MV2 we could run chrome.webRequest and remove those headers:
chrome.webRequest.onHeadersReceived.addListener(
function (details)
{
if (details.tabId && (details.tabId === tabId || details.tabId === -1 || tabMultiId.includes(details.tabId))) {
var b = details.responseHeaders.filter((details) => !['x-frame-options', 'content-security-policy', 'x-content-security-policy', 'strict-transport-security', 'frame-ancestors'].includes(details.name.toLowerCase()));
b.forEach(function(e){
"set-cookie" === e.name && -1 !== e.value.indexOf("Secure") && (-1 !== e.value.indexOf("SameSite=Strict") ?
(e.value = e.value.replace(/SameSite=Strict/g, "SameSite=None"))
: -1 !== e.value.indexOf("SameSite=Lax")
? (e.value = e.value.replace(/SameSite=Lax/g, "SameSite=None"))
: (e.value = e.value.replace(/; Secure/g, "; SameSite=None; Secure")));
});
return {
responseHeaders: b
}
}
},
{
urls: [ "<all_urls>" ],
tabId: tabId
},
["blocking", "responseHeaders", "extraHeaders"]
);
I have tried to do exactly the same with MV3 but keep failing.
My 2 attemps:
async function NetRequest() {
var blockUrls = ["*://*.twitter.com/*","*://*.telegram.org/*"];
var tabId = await getObjectFromLocalStorage('tabId');
var tabMultiId = [];
tabMultiId = JSON.parse(await getObjectFromLocalStorage('tabMultiId'));
tabMultiId.push(tabId);
blockUrls.forEach((domain, index) => {
let id = index + 1;
chrome.declarativeNetRequest.updateSessionRules({
addRules:[
{
"id": id,
"priority": 1,
"action": { "type": "modifyHeaders",
"responseHeaders": [
{ "header": "X-Frame-Options", "operation": "remove" },
{ "header": "Frame-Options", "operation": "remove" },
{ "header": "content-security-policy", "operation": "remove" },
{ "header": "content-security-policy-report-only", "operation": "remove" },
{ "header": "x-content-security-policy", "operation": "remove" },
{ "header": "strict-transport-security", "operation": "remove" },
{ "header": "frame-ancestors", "operation": "remove" },
{ "header": "set-cookie", "operation": "set", "value": "SameSite=None; Secure" }
]
},
"condition": {"urlFilter": domain, "resourceTypes": ["image","media","main_frame","sub_frame","stylesheet","script","font","xmlhttprequest","ping","websocket","other"],
"tabIds" : tabMultiId }
}
],
removeRuleIds: [id]
});
});
}
async function launchWindow(newURL, windowDimensions, urlWindow, isIncognitoWindow, windowType) {
chrome.windows.create({ url: newURL, type: windowType, incognito: isIncognitoWindow, width: windowDimensions.width, height: windowDimensions.height, left: windowDimensions.left, top: windowDimensions.top },
async function (chromeWindow) {
if (urlWindow != "install" || urlWindow != "update") {
chrome.storage.local.set({ 'extensionWindowId': chromeWindow.id }, function () { });
chrome.storage.local.set({ 'tabId': chromeWindow.tabs[0].id }, function () { });
NetRequest();
}
});
}
Also tried:
const iframeHosts = [
'twitter.com', 'telegram.org'
];
const RULE = {
id: 1,
condition: {
initiatorDomains: ['example.com'],
requestDomains: iframeHosts,
resourceTypes: ['sub_frame', 'main_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
};
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [RULE.id],
addRules: [RULE],
});
Permissions:
"permissions": [
"system.display",
"scripting",
"activeTab",
"notifications",
"contextMenus",
"unlimitedStorage",
"storage",
"declarativeNetRequestWithHostAccess",
"webNavigation",
"alarms"
],
"host_permissions": [
"<all_urls>"
],
Any of this attempts worked.
Greetings and thank you very much for anyone that try to help.
You need to unregister service worker for the site and clear its cache using chrome.browsingData API.
Syntax for urlFilter is different, so your "*://*.twitter.com/*" is incorrect and should be "||twitter.com/", however a better solution is to use requestDomains because it allows specifying multiple sites in just one rule.
// manifest.json
"permissions": ["browsingData", "declarativeNetRequest"],
"host_permissions": ["*://*.twitter.com/", "*://*.telegram.org/"],
// extension script
async function configureNetRequest(tabId) {
const domains = [
'twitter.com',
'telegram.org',
];
const headers = [
'X-Frame-Options',
'Frame-Options',
];
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [1],
addRules: [{
id: 1,
action: {
type: 'modifyHeaders',
responseHeaders: headers.map(h => ({ header: h, operation: 'remove'})),
},
condition: {
requestDomains: domains,
resourceTypes: ['sub_frame'],
tabIds: [tabId],
},
}],
});
await chrome.browsingData.remove({
origins: domains.map(d => `https://${d}`),
}, {
cacheStorage: true,
serviceWorkers: true,
});
}
// Usage
chrome.windows.create({ url: 'about:blank' }, async w => {
await configureNetRequest(w.tabs[0].id);
await chrome.tabs.update(w.tabs[0].id, { url: 'https://some.real.url/' });
});

How do you get the current tab in a popup window created with chrome.windows.create [duplicate]

I am going to create a chrome extension that will pop up a new window, by doing this
chrome.windows.create({
url: chrome.runtime.getURL("popup.html"),
type: "popup",
});
my problem is from "pop up window" I want to access the active Tab from the "main window", for example I want to change the background of the DOM from the active tab from where I clicked the extension that shows the popup window. I hope the question is not confusing. :)
manifest.json
{
"name": "test",
"description": "test",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting", "tabs"],
"action": {
"default_icon": {
"16": "Icon-16.png",
"32": "Icon-32.png",
"48": "Icon-48.png",
"128": "Icon-128.png"
}
},
"icons": {
"16": "Icon-16.png",
"32": "Icon-32.png",
"48": "Icon-48.png",
"128": "Icon-128.png"
}
}
on the pop up window, i have this onclick function
const onClick = async (e) => {
if (e && e.preventDefault) e.preventDefault();
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: () => {
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
},
});
};
Get the active tab before creating the window and pass the id as a URL parameter.
extension script:
async function openPopup() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.windows.create({
url: `popup.html?${new URLSearchParams({
tabId: tab.id,
})}` ,
type: 'popup',
});
}
popup.html:
<script src="popup.js"></script>
popup.js:
const activeTabId = Number(new URLSearchParams(location.search).get('tabId'));
chrome.scripting.executeScript({ target: { tabId: activeTabId }, function: foo });
function foo() {
console.log('injected foo');
}
async function getCurrentTab() {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
const tab = getCurrentTab();
you can use this from documentation, returning promises object
https://developer.chrome.com/docs/extensions/reference/tabs/

We made a listener in background.js in chrome extension but it does not run

I am creating a simple chrome extension that prevents me from connecting to the Google domain. But in the background, javascript kept working and created the background.js and the on-updated listener. But it doesn't work. Why? When I clicked on the icon in Main.js, it was OK.
manifest.json
{
"manifest_version": 2,
"name": "불법 토토사이트 감지기",
"description": "불법 토토사이트를 감지해주는 사이트입니다.",
"version": "1.0",
"content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'",
"background": {
"scripts": ["background.js"],
"persistent" : false
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab"
]
}
background.js
chrome.tabs.onUpdated.addListener(function () {
var domain = document.URL;
var google = 'https://www.google.co.kr/';
if (domain == google) {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
// $.ajax({
// type: 'POST',
// url: 'http://207.148.88.110:3000/',
// data: {
// url: domain
// },
// dataType: 'json',
// success: function(Object){
// if (Object.success == '통과') {
// document.getElementById('url').innerHTML = Object.success;
// }
// else {
// chrome.tabs.executeScript({
// code: 'alert("불법 사이트입니다"); history.go(-1);'
// })
// }
// },
// error: function(error) {
// console.log(error);
// }
// })
})
main.js
chrome.tabs.executeScript({
code: 'document.URL;'
}, function (domain) {
var google = 'https://www.google.co.kr/';
if (domain == google) {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
$.ajax({
type: 'POST',
url: 'http://207.148.88.110:3000/',
data: {
url: domain
},
dataType: 'json',
success: function(Object){
if (Object.success == '통과') {
document.getElementById('url').innerHTML = Object.success;
}
else {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
},
error: function(error) {
console.log(error);
}
})
})
chrome.tabs.onUpdated.addListener(function () {
var domain = document.URL;
/* ... */
At this point in your code, the document is the background page. So the URL will be something like chrome-extension://your-extension-id/_generated_background_page.html and will never match.
You need to check the details that are provided to onUpdated listeners:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
var domain = changeInfo.url; // Or tab.url
/* ... */

Google App script - Telegram Bot - Inline Keyboard callback_query

This here is my function that hears for anything incoming from Telegram. It does work well with any message and files or whatever but it doesn't work when i press a but of an inline_keyboard, Why?
How can i create a function that hears for a inline_keyboard button pressed?
function doPost(e) {
var data = JSON.parse(e.postData.contents);
sendText('personal_chat_id', JSON.stringify(data));
}
In case you need it here under is my function that can send a message with an inline_keyboard.
function lol(){
var keyboard ={
inline_keyboard: [
[
{
text: "A",
callback_data: 123
},
{
text: "B",
callback_data: 234
}
],
[
{
text: "C",
callback_data: 345
},
{
text: "D",
callback_data: 456
}
]
]
}
sendText('personal_chat_id', "lol", keyboard)
}
function sendText(chatId,text,keyBoard){
keyBoard = keyBoard || 0;
if(keyBoard.inline_keyboard || keyBoard.keyboard){
var data = {
method: "post",
payload: {
method: "sendMessage",
chat_id: String(chatId),
text: text,
parse_mode: "HTML",
reply_markup: JSON.stringify(keyBoard)
}
}
}
var response = UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', data);
return response.getContentText()
}
You need to do it inside the doPost(e) function. Take the "e" parameter and verify what kind of type it is. If it is "contents.callback_query" is coming from the inline keyboard response and if it is "contents.message" it's text typed by the user.
And dont forget declare sendText() and sendKeyboard() functions.
function doPost(e) {
var contents = JSON.parse(e.postData.contents);
var keyboard = {
"inline_keyboard": [
[{
"text": "Players",
"callback_data": "player"
}],
[{
"text": "Match",
"callback_data": "match"
}],
[{
"text": "Results",
"callback_data": "result"
}]
]}
if (contents.callback_query) {
var id = contents.callback_query.message.chat.id;
var text = contents.callback_query.data;
switch (text) {
case 'Player':
var answer = 'Ingresa tu nombre y apellido';
sendText(id, answer);
break;
case 'match':
var answer = 'Ingresá tu nombre y apellido';
sendText(id, answer);
break;
case 'result':
var keyboard = {
'inline_keyboard': [
[{
'text': 'Youtube',
'url': 'https://youtube.com'
},{
'text': 'Google',
'url': 'https://google.com'
}]
]
};
sendTextWithButtons(id, 'Algunos enlaces', JSON.stringify(keyboard));
break;
default:
var answer = 'Default answer';
sendText(id, answer);
break;
}
var chat_id = contents.callback_query.from.id;
var user = contents.callback_query.message.chat.first_name;
var cb_data = contents.callback_query.data;
sendText(chat_id, CariDataDariIDSheet(cb_data));
}else if (contents.message) {
var chat_id = contents.message.from.id;
var user = contents.message.chat.first_name;
var answer = "your answer";
sendKeyboard(chat_id, answer, keyboard);
}
}

Getting the source HTML of the current page from chrome extension

I have a chrome extension. I need to analyse from the HTML source of the current page. I found here all kinds of solutions with background pages and content scripts but none helped me. here is what I have so far:
manifest.json
{
"name": "Extension",
"version": "1.0",
"description": "Extension",
"browser_action": {
"default_icon": "bmarkred.ico",
"popup": "Test.html"
},
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["content.js"]
}
],
"background": {
"page": "backgroundPage.html"
},
"permissions": [
"cookies",
"tabs",
"http://*/*",
"https://*/*"
]
}
background.html
<html>
<head>
<script type="text/javascript">
try {
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.sendRequest(tab.id, {action: "getSource"}, function(source) {
alert(source);
});
});
}
catch (ex) {
alert(ex);
}
</script>
</head>
</html>
content.js
chrome.extension.onRequest.addListener(function(request, sender, callback) {
if (request.action == "getSource") {
callback(document.getElementsByTagName('html')[0].innerHTML);
}
});
The alert always alerts undefined. even if i change in the content.js file the callback function to:
callback('hello');
still the same result. What am I doing wrong? maybe I'm going at this the wrong way. What I really need is this: When the user opens the extension popup (and only then), I need HTML of the current page so I can analyse it.
Inject a script into the page you want to get the source from and message it back to the popup....
manifest.json
{
"name": "Get pages source",
"version": "1.1",
"manifest_version": 3,
"description": "Get active tabs or element on that pages source from a popup",
"action": {
"default_title": "Get pages source",
"default_popup": "popup.html"
},
"permissions": [
"scripting",
"activeTab"
]
}
popup.html
function onWindowLoad() {
var message = document.querySelector('#message');
chrome.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
var activeTab = tabs[0];
var activeTabId = activeTab.id;
return chrome.scripting.executeScript({
target: { tabId: activeTabId },
// injectImmediately: true, // uncomment this to make it execute straight away, other wise it will wait for document_idle
func: DOMtoString,
// args: ['body'] // you can use this to target what element to get the html for
});
}).then(function (results) {
message.innerText = results[0].result;
}).catch(function (error) {
message.innerText = 'There was an error injecting script : \n' + error.message;
});
}
window.onload = onWindowLoad;
function DOMtoString(selector) {
if (selector) {
selector = document.querySelector(selector);
if (!selector) return "ERROR: querySelector failed to find node"
} else {
selector = document.documentElement;
}
return selector.outerHTML;
}
Here is my solution:
chrome.runtime.onMessage.addListener(function(request, sender) {
if (request.action == "getSource") {
this.pageSource = request.source;
var title = this.pageSource.match(/<title[^>]*>([^<]+)<\/title>/)[1];
alert(title)
}
});
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
chrome.tabs.executeScript(
tabs[0].id,
{ code: 'var s = document.documentElement.outerHTML; chrome.runtime.sendMessage({action: "getSource", source: s});' }
);
});