How to handle onBeforeSendHeaders in Chrome v3 - javascript

I have some published Chrome Extension. I am trying to move it from mv2 to mv3 format. Inside background.js , I am intercepting the header with below code.
chrome.webRequest.onBeforeSendHeaders.addListener(
onBeforeSendHeadersHandler, {
urls: <url list obtained using websocket from machine>,
types: ['main_frame']
},
['blocking', 'requestHeaders']
);
var onBeforeSendHeadersHandler = function(details) {
if (details.requestHeaders[idx].name === 'Purpose' &&details.requestHeaders[idx].name === 'prefetch') {
return {};
}
var condition = <some code>;
chrome.tabs.update(params);
return {cancel: condition ? true : false };
}
Based upon the input in "details" onBeforeSendHeadersHandler will run some logic and return true or false. Which will decide whether Extension will handle the url or not.
With v3 how can I intercept the header at runtime and return some values.
I tried "declarative_net_request" in manifest.json and tried updateDynamicRules , it's not working. But even though I can fix the issue, my query is how can I define some finction() to be executed inside onBeforeSendHeadersHandler() , as I can not write all the conditions inside rules.json file or update it dynamically.
V3 has depricated "blocking" call , which is a must for my requirement. Is there any alternative for this ?

you can use declarativeNetRequest permission to block request
chrome.declarativeNetRequest.updateDynamicRules({
addRules:[{
"id":1,
"action": { "type": "block" },
"condition": {
"urlFilter": 'url-part',
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
}],
removeRuleIds: [1]
});

Related

JavaScript url in json returns 501 but string url does not

So I have the following problem:
I try to pull data from iCloud using CloudKit (HTTP requests)
The iCloud entity contains a CKAsset and I need the download URL which a POST request to https://api.apple-cloudkit.com/database/1/... returns. However, if I try to download the data from this URL, it returns a 501 error but if I print the URL to the console and paste that into the browser, the browser downloads the file.
It gets even weirder because if I implement the URL I printed to the console directly into the code everything works! I am converting the URL in the JSON response to a string so IDK what's wrong.
CloudKit response:
{ "records" : [ { "recordName" : "xxxxxxxxxxxx", "recordType" : "xxxxxxx", "fields" : { "file" : { "value" : { "fileChecksum" : "AZJ1FbmpL7caqaksfwrFm3586o5+", "size" : 303, "downloadURL" : "https://cvws.icloud-content.com/B/AZJ1Fbmpxxxaxxxxxxx/${f}?xxxxxxxx..." }, "type" : "ASSETID" },},} ] }
I shorted the response so that it only contains the relevant stuff.
I tried to get the URL with the following code: var url = data["records"][0]["fields"]["file"]["value"]["downloadURL"];
Already tried with .toString() and var url == "" + data["records"]....
It works if I do var url = "https://cvws.icloud-content.com/B/AZJ1Fbmpxxxaxxxxxxx/${f}?xxxxxxxx..." but obviously this is no real solution.
Help is really appreciated!
EDIT:
Here is the code that downloads the file from downloadURL. I'm using a library called zip.js because the file is a zip file (with a different file extension):
zip.createReader(
new zip.HttpReader(url),
function (reader) {
reader.getEntries(async function (entries) {
if (entries.length) {
entries[0].getData(
new zip.TextWriter(),
async function (text) {
reader.close(function () {
// onclose callback
});
},
function (current, total) {
// onprogress callback
}
);
} else {
}
});
},
function (error) {
// onerror callback
}
);
EDIT 2:
I found out something that might be interesting: If I paste the URL directly into the code, status code 200 returns from disk cache. I tried loading the website in incognito mode and I had to reload once to get it working. Because I receive a new download ID on every refresh, it can't cache a status code.
This works if the URL is valid
You need to look in the network tab to see what is the matter. For example the ${f} looks suspicious
const data = {
"records": [{
"recordName": "xxxxxxxxxxxx",
"recordType": "xxxxxxx",
"fields": {
"file": {
"value": {
"fileChecksum": "AZJ1FbmpL7caqaksfwrFm3586o5+",
"size": 303,
"downloadURL": "https://cvws.icloud-content.com/B/AZJ1Fbmpxxxaxxxxxxx/${f}?xxxxxxxx..."
},
"type": "ASSETID"
},
},
}]
}
location = data.records[0].fields.file.value.downloadURL;
The code that you have above does work to pull the URL out of the object (run code below). Are you sure that data holds the information in the format you're expecting and that the URL is correct?
var data = { "records" : [ { "recordName" : "xxxxxxxxxxxx", "recordType" : "xxxxxxx", "fields" : { "file" : { "value" : { "fileChecksum" : "AZJ1FbmpL7caqaksfwrFm3586o5+", "size" : 303, "downloadURL" : "https://cvws.icloud-content.com/B/AZJ1Fbmpxxxaxxxxxxx/${f}?xxxxxxxx..." }, "type" : "ASSETID" },},} ] };
// Original
var url = data["records"][0]["fields"]["file"]["value"]["downloadURL"];
// Object notation
var url2 = data.records[0].fields.file.value.downloadURL;
console.log(url);
console.log(url2);
Ok so I found a solution. This has nothing to do with the actual URL, it is fine, but rather the way how zip.js downloads data. It first makes a HEAD request and because the iCloud servers do not support these, it returns 501. The static domains worked because it somehow cached 200 OK, even though 501 returned.
Thank you to everyone for their help!

opening tabs/windows without displaying them [duplicate]

I'm the author of Intab, a Chrome extension that lets you view a link inline as opposed to a new tab. There's not much fancy stuff going on behind the scenes, it's just an iframe that loads the URL the user clicked on.
It works great except for sites that set the X-Frame-Options header to DENY or SAMEORIGIN. Some really big sites like Google and Facebook both use it which makes for a slightly janky experience.
Is there any way to get around this? Since I'm using a Chrome extension, is there any browser level stuff I can access that might help? Looking for any ideas or help!
Chrome offers the webRequest API to intercept and modify HTTP requests. You can remove the X-Frame-Options header to allow inlining pages within an iframe.
chrome.webRequest.onHeadersReceived.addListener(
function(info) {
var headers = info.responseHeaders;
for (var i=headers.length-1; i>=0; --i) {
var header = headers[i].name.toLowerCase();
if (header == 'x-frame-options' || header == 'frame-options') {
headers.splice(i, 1); // Remove header
}
}
return {responseHeaders: headers};
}, {
urls: [
'*://*/*', // Pattern to match all http(s) pages
// '*://*.example.org/*', // Pattern to match one http(s) site
],
types: [ 'sub_frame' ]
}, [
'blocking',
'responseHeaders',
// Modern Chrome needs 'extraHeaders' to see and change this header,
// so the following code evaluates to 'extraHeaders' only in modern Chrome.
chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
].filter(Boolean)
);
In the manifest, you need to specify the webRequest and webRequestBlocking permissions, plus the URLs patterns you're intending to intercept i.e. "*://*/*" or "*://www.example.org/*" for the example above.
ManifestV3 example using declarativeNetRequest
See also the warning at the end of this answer!
manifest.json for Chrome 96 and newer,
doesn't show a separate permission for "Block page content" during installation
"minimum_chrome_version": "96",
"permissions": ["declarativeNetRequestWithHostAccess"],
"host_permissions": ["*://*.example.com/"],
"background": {"service_worker": "bg.js"},
bg.js for Chrome 101 and newer using initiatorDomains and requestDomains
(don't forget to add "minimum_chrome_version": "101" in manifest.json)
const iframeHosts = [
'example.com',
];
chrome.runtime.onInstalled.addListener(() => {
const RULE = {
id: 1,
condition: {
initiatorDomains: [chrome.runtime.id],
requestDomains: iframeHosts,
resourceTypes: ['sub_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
};
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [RULE.id],
addRules: [RULE],
});
});
Old Chrome 84-100
Use the following instead, if your extension should be compatible with these old versions.
manifest.json for Chrome 84 and newer,
shows a separate permission for "Block page content" during installation
"permissions": ["declarativeNetRequest"],
"host_permissions": ["*://*.example.com/"],
"background": {"service_worker": "bg.js"},
bg.js for Chrome 84 and newer using the now deprecated domains
const iframeHosts = [
'example.com',
];
chrome.runtime.onInstalled.addListener(() => {
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: iframeHosts.map((h, i) => i + 1),
addRules: iframeHosts.map((h, i) => ({
id: i + 1,
condition: {
domains: [chrome.runtime.id],
urlFilter: `||${h}/`,
resourceTypes: ['sub_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
})),
});
});
Warning: beware of site's service worker
You may have to remove the service worker of the site(s) and clear its cache before adding the iframe or before opening the extension page because many modern sites use the service worker to create the page without making a network request thus ignoring our header-stripping rule.
Add "browsingData" to "permissions" in manifest.json
Clear the SW:
function removeSW(url) {
return chrome.browsingData.remove({
origins: [new URL(url).origin],
}, {
cacheStorage: true,
serviceWorkers: true,
});
}
// If you add an iframe element in DOM:
async function addIframe(url, parent = document.body) {
await removeSW(url);
const el = document.createElement('iframe');
parent.appendChild(el);
el.src = url;
return el;
}
// If you open an extension page with an <iframe> element in its HTML:
async function openPage(url) {
await removeSW('https://example.com/');
return chrome.tabs.create({url});
}
You can try the Frame extension that lets the user drop X-Frame-Options and Content-Security-Policy HTTP response headers, allowing pages to be iframed.
The code is available on github
It's based on ManifestV3 and working perfectly with Google & Facebook.

Edge extension requesting JSON doesn't work

I'm working on an Edge extension, and I need to request JSON from an external website. The following example works fine on stackoverflow.com, but fails on steamcommunity.com/id/ where I need it.
Here are the files I have:
jQuery (from local file)
manifest.json:
{
"author": "me",
"name": "Get JSON",
"description": "get json",
"version": "0.1",
"manifest_version": 2,
"content_scripts": [
{
"matches": [
"*://steamcommunity.com/id/*",
"*://stackoverflow.com/*"
],
"js": ["jquery.min.js", "myscript.js"]
}
]
}
myscript.js:
var url = 'https://raw.githubusercontent.com/bahamas10/css-color-names/master/css-color-names.json';
$.getJSON(url, function(data) {
console.log(data);
});
As I've said, this example works fine on Stackoverflow, but fails on Steam.
This is the error I receive on the Steam website:
CSP14312: Resource violated directive 'connect-src 'self' http://steamcommunity.com https://steamcommunity.com https://api.steampowered.com/ http://localhost:27060 http://store.steampowered.com/ https://store.steampowered.com/' in Content-Security-Policy: https://raw.githubusercontent.com/bahamas10/css-color-names/master/css-color-names.json. Resource will be blocked.
EDIT: I should also add, that this exact example works fine when using a Chrome extension in Google Chrome.
Any help is appreciated.
Microsoft Edge, differently from Chrome and Firefox, doesn't ignore the website CSP rules for extensions.
See https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11320214/ for an open issue.
The only viable workaround I know, which I also tested myself, is to change the CSP response headers on the fly by using browser.webRequest.onHeadersReceived, which luckily is well supported in Edge.
NOTE: this code was adapted from this great article, which also explains the problem in more detail: https://transitory.technology/browser-extensions-and-csp-headers/
The code adds your extension to the allowed urls for each CSP header and directive, it should be executed in the background page, which should be set as persistent.
Make sure to add the webRequest and webRequestBlocking permissions to your manifest.json.
const cspHeaderNames = [
'content-security-policy',
'content-security-policy-report-only',
'x-webkit-csp'
];
// #see https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
const cspSources = [
'connect-src',
'default-src',
'font-src',
'frame-src',
'img-src',
'media-src',
'object-src',
'script-src',
'style-src',
'child-src',
'form-action',
];
const url = browser.extension.getURL("").slice(0, -1); // (remove trailing "/")
browser.webRequest.onHeadersReceived.addListener(details => {
details.responseHeaders.forEach(header => {
const isCspHeader = cspHeaderNames.indexOf(header.name.toLowerCase()) >= 0;
if (isCspHeader) {
let newValue = header.value;
cspSources.forEach(source => {
newValue = newValue.replace(source, source + ' ' + url);
});
header.value = newValue;
}
});
return {
responseHeaders: details.responseHeaders
};
}, {
urls: ['<all_urls>'],
types: ['main_frame', "sub_frame"],
}, ["blocking", "responseHeaders"]);

What is the proper way to change http headers in a chrome extension? [duplicate]

I'm the author of Intab, a Chrome extension that lets you view a link inline as opposed to a new tab. There's not much fancy stuff going on behind the scenes, it's just an iframe that loads the URL the user clicked on.
It works great except for sites that set the X-Frame-Options header to DENY or SAMEORIGIN. Some really big sites like Google and Facebook both use it which makes for a slightly janky experience.
Is there any way to get around this? Since I'm using a Chrome extension, is there any browser level stuff I can access that might help? Looking for any ideas or help!
Chrome offers the webRequest API to intercept and modify HTTP requests. You can remove the X-Frame-Options header to allow inlining pages within an iframe.
chrome.webRequest.onHeadersReceived.addListener(
function(info) {
var headers = info.responseHeaders;
for (var i=headers.length-1; i>=0; --i) {
var header = headers[i].name.toLowerCase();
if (header == 'x-frame-options' || header == 'frame-options') {
headers.splice(i, 1); // Remove header
}
}
return {responseHeaders: headers};
}, {
urls: [
'*://*/*', // Pattern to match all http(s) pages
// '*://*.example.org/*', // Pattern to match one http(s) site
],
types: [ 'sub_frame' ]
}, [
'blocking',
'responseHeaders',
// Modern Chrome needs 'extraHeaders' to see and change this header,
// so the following code evaluates to 'extraHeaders' only in modern Chrome.
chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
].filter(Boolean)
);
In the manifest, you need to specify the webRequest and webRequestBlocking permissions, plus the URLs patterns you're intending to intercept i.e. "*://*/*" or "*://www.example.org/*" for the example above.
ManifestV3 example using declarativeNetRequest
See also the warning at the end of this answer!
manifest.json for Chrome 96 and newer,
doesn't show a separate permission for "Block page content" during installation
"minimum_chrome_version": "96",
"permissions": ["declarativeNetRequestWithHostAccess"],
"host_permissions": ["*://*.example.com/"],
"background": {"service_worker": "bg.js"},
bg.js for Chrome 101 and newer using initiatorDomains and requestDomains
(don't forget to add "minimum_chrome_version": "101" in manifest.json)
const iframeHosts = [
'example.com',
];
chrome.runtime.onInstalled.addListener(() => {
const RULE = {
id: 1,
condition: {
initiatorDomains: [chrome.runtime.id],
requestDomains: iframeHosts,
resourceTypes: ['sub_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
};
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [RULE.id],
addRules: [RULE],
});
});
Old Chrome 84-100
Use the following instead, if your extension should be compatible with these old versions.
manifest.json for Chrome 84 and newer,
shows a separate permission for "Block page content" during installation
"permissions": ["declarativeNetRequest"],
"host_permissions": ["*://*.example.com/"],
"background": {"service_worker": "bg.js"},
bg.js for Chrome 84 and newer using the now deprecated domains
const iframeHosts = [
'example.com',
];
chrome.runtime.onInstalled.addListener(() => {
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: iframeHosts.map((h, i) => i + 1),
addRules: iframeHosts.map((h, i) => ({
id: i + 1,
condition: {
domains: [chrome.runtime.id],
urlFilter: `||${h}/`,
resourceTypes: ['sub_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
})),
});
});
Warning: beware of site's service worker
You may have to remove the service worker of the site(s) and clear its cache before adding the iframe or before opening the extension page because many modern sites use the service worker to create the page without making a network request thus ignoring our header-stripping rule.
Add "browsingData" to "permissions" in manifest.json
Clear the SW:
function removeSW(url) {
return chrome.browsingData.remove({
origins: [new URL(url).origin],
}, {
cacheStorage: true,
serviceWorkers: true,
});
}
// If you add an iframe element in DOM:
async function addIframe(url, parent = document.body) {
await removeSW(url);
const el = document.createElement('iframe');
parent.appendChild(el);
el.src = url;
return el;
}
// If you open an extension page with an <iframe> element in its HTML:
async function openPage(url) {
await removeSW('https://example.com/');
return chrome.tabs.create({url});
}
You can try the Frame extension that lets the user drop X-Frame-Options and Content-Security-Policy HTTP response headers, allowing pages to be iframed.
The code is available on github
It's based on ManifestV3 and working perfectly with Google & Facebook.

Google Chrome - Screen capture failing when iframe is used, same script works without iframe

When i use this following script it works with normal browser. But when iframe is used then its showing me this error:
Does anyone know what is causing this and can be resolved?
ERROR:
channel message Object {type: "getScreenPending", id: 24504, request: 6} content.js:4
channel message Object {type: "gotScreen", id: 24504, request: 6} content.js:4
>>> ShareScreen: if any err NavigatorUserMediaError {constraintName: "", message: "", name: "InvalidStateError"} test.js:1616
manifest.json:
{
"name": "Screen sharing",
"description": "Screensharing utility",
"version": "0.0.2",
"manifest_version": 2,
"minimum_chrome_version": "34",
"icons": {
"48" : "icon.png"
},
"permissions": [
"desktopCapture"
],
"background": {
"scripts": ["background.js"]
},
"content_scripts": [ {
"js": [ "content.js" ],
"all_frames": true,
"run_at": "document_start",
"matches": ["*://*.a.com/*", "*://*.b.com/*"]
}],
"web_accessible_resources": [
"icon.png"
]
}
background.js:
/* background page, responsible for actually choosing media */
chrome.runtime.onConnect.addListener(function (channel) {
channel.onMessage.addListener(function (message) {
switch(message.type) {
case 'getScreen':
var pending = chrome.desktopCapture.chooseDesktopMedia(message.options || ['screen', 'window'],
channel.sender.tab, function (streamid) {
// communicate this string to the app so it can call getUserMedia with it
message.type = 'gotScreen';
message.sourceId = streamid;
channel.postMessage(message);
});
// let the app know that it can cancel the timeout
message.type = 'getScreenPending';
message.request = pending;
channel.postMessage(message);
break;
case 'cancelGetScreen':
chrome.desktopCapture.cancelChooseDesktopMedia(message.request);
message.type = 'canceledGetScreen';
channel.postMessage(message);
break;
}
});
});
content.js:
/* the chrome content script which can listen to the page dom events */
var channel = chrome.runtime.connect();
channel.onMessage.addListener(function (message) {
console.log('channel message', message);
window.postMessage(message, '*');
});
window.addEventListener('message', function (event) {
if (event.source != window)
return;
if (!event.data && (event.data.type == 'getScreen' || event.data.type == 'cancelGetScreen'))
return;
channel.postMessage(event.data);
});
This is caused by the fact that the a stream can only be used by frames whose URL match the origin of the tab. Starting with Chrome 40, you can use the stream in frames as well if you set tab.url to a URL whose origin matches the frame (crbug.com/425344).
The stream is only valid for ten seconds, so you have to follow the following flow:
Load the iframe that contains the page that should handle the stream. This page must be served from a secure scheme, e.g. https: or chrome-extension:.
Send the frame's origin (location.origin) to the background page.
Request the desktop stream using the tab info, with tab.url set to the frame's URL or origin.
Send the streamId back to the frame and use it (within ten seconds).
Example (based on the code in the question):
var tab = channel.sender.tab;
// NEW (Chrome 40+)
tab.url = message.url; // Your custom message, e.g. {url: location.origin}
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], tab,
function (streamid) {
// ... see question for the rest of the code
});
1 - this is not a code problem, browser problem
2 - this is not working because i am launching the extension from HTTP (http://www.maindomain.com) using iframe a HTTPS (https://subdomain.maindomain.com) link which is using the browser extension
So to fix it. I needed to use HTTPS (https://www.maindomain.com) opening HTTPS iframe links (https://subdomain.maindomain.com) . Since then it works now.
Hope this help others.
NOTE: problem occurred: when i run the iframe from same subdomain subdomain.maindomain.com/test.php (iframe src=subdomain.maindomain.com/core.php) then it works. But when i am running it as maindomain.com/otherpages (iframe src=subdomain.maindomain.com/core.php) then this is not working. Was very confusing.
EDIT: This still did not resolved the problem. screen share dialog box opens but when i press share screen then it gives same error and fails.

Categories

Resources