I can't see an answer to this in the Developer's Guide, though maybe I'm not looking in the right place.
I want to intercept HTTP requests with a Chrome Extension, and then forward it on, potentially with new/different HTTP headers - how can I do that?
PS: I am the author of Requestly - Chrome/Firefox extension to modify HTTP requests & responses.
It was certainly not possible when OP asked the question but now you can use WebRequest API with Manifest V2 and DeclarativeNetRequest API with Manifest V3 to write your own extension to modify Request & Response Headers.
Manifest V2 code
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
for (var i = 0; i < details.requestHeaders.length; ++i) {
if (details.requestHeaders[i].name === 'User-Agent') {
details.requestHeaders.splice(i, 1);
break;
}
}
return { requestHeaders: details.requestHeaders };
},
{urls: ['<all_urls>']},
['blocking', 'requestHeaders' /* , 'extraHeaders' */]
// uncomment 'extraHeaders' above in case of special headers since Chrome 72
// see https://developer.chrome.com/extensions/webRequest#life_cycle_footnote
);
Google Chrome is deprecating webRequest Blocking APIs in the Manifest V3. As per the official statement from Google on 28th Sep 2022, all extensions with Manifest v2 won't run on Chrome from June 2023 onwards. Here's an approach to Modify Request & Response headers with Manifest v3 - https://github.com/requestly/modify-headers-manifest-v3
Manifest V3 Code:
rules.ts
const allResourceTypes =
Object.values(chrome.declarativeNetRequest.ResourceType);
export default [
{
id: 1,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'x-test-request-header',
value: 'test-value',
},
]
},
condition: {
urlFilter: '/returnHeaders',
resourceTypes: allResourceTypes,
}
},
{
id: 2,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
responseHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'x-test-response-header',
value: 'test-value',
},
]
},
condition: {
urlFilter: 'https://testheaders.com/exampleAPI',
resourceTypes: allResourceTypes,
}
},
];
background.ts
import rules from './rules';
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: rules.map((rule) => rule.id), // remove existing rules
addRules: rules
});
The complete source code is available in the GitHub repo - https://github.com/requestly/modify-headers-manifest-v3
If you want to use an existing Chrome/Firefox/Edge Extension, you can use Requestly which allows you to modify request and response headers. Have a look at this snapshot:
Modifying request headers ( https://developer.chrome.com/extensions/webRequest ) is supported in chrome 17.
You are looking at the right place, but intercepting HTTP requests does not exist yet, but the extension team is aware that it's a popular request and would like to get to it sometime in the near future.
Keep in mind that starting from chrome 72, some headers are not allowed unless you add extraHeaders in opt_extraInfoSpec
So the above example in #sachinjain024's answer will look something like this:
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
for (var i = 0; i < details.requestHeaders.length; ++i) {
if (details.requestHeaders[i].name === 'User-Agent') {
details.requestHeaders.splice(i, 1);
break;
}
}
return { requestHeaders: details.requestHeaders };
},
{urls: ['<all_urls>']},
[ 'blocking', 'requestHeaders', 'extraHeaders']
);
For more info, check the documentation Screenshot from the documentation https://developer.chrome.com/extensions/webRequest#life_cycle_footnote
For extensions using manifest version 3, you can no longer use chrome.webRequest.onBeforeSendHeaders.*. The alternative is chrome.declarativeNetRequest
Make the following changes in your manifest.json:
{
...
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"host_permissions": ["<all_urls>"],
"permissions": [
"declarativeNetRequest"
],
...
}
💡 "<all_urls>" is for modifying all outgoing urls's headers. Restrict this for your scope of your work
Make the following changes in your background.js:
// ...
const MY_CUSTOM_RULE_ID = 1
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [MY_CUSTOM_RULE_ID],
addRules: [
{
id: MY_CUSTOM_RULE_ID,
priority: 1,
action: {
type: "modifyHeaders",
requestHeaders: [
{
operation: "set",
header: "my-custom-header",
value: "my custom header value"
}
]
},
condition: {
"resourceTypes": ["main_frame", "sub_frame"]
},
}
],
});
Result
Read the docs https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/
You could install ModHeader extension and add headers:
You can use WebRequest API which is now deprecated it allows you to modify request/response headers.
You can upgrade your extension to Manifest V3 to be able to use DeclativeNetRequest which also supports modifying request/response headers.
Or you can install Inssman chrome extension.
It allows you to modify HTTP(S) request/response headers, reqirect and block request, return custom data like HTML/CSS/JS/JSON and more.
And it is open source project
Related
As described into the documentation I'm trying to update declarativeNetRequest rules of a chrome extension. From the docs:
updateDynamicRules
chrome.declarativeNetRequest.updateDynamicRules(array of integer ruleIdsToRemove, array of Rule rulesToAdd, function callback)
Modify the current set of dynamic rules for the extension. The rules with IDs listed in ruleIdsToRemove are first removed, and then the rules given in rulesToAdd are added. This update happens as a single atomic operation: either all specified rules are added and removed, or an error is returned. These rules are persisted across browser sessions and across extension updates. Any ids in ruleIdsToRemove that are not present will be ignored. Note that static rules specified as part of the extension package can not be removed using this function. Note: MAX_NUMBER_OF_DYNAMIC_RULES is the maximum number of dynamic rules an extension can add.
It's not clear what is intended for dynamic rule. This api will rely on a static rule set that can't be modified until an extension is updated and the method described into the documentation says that the rules shipped with the extension will not be removed from the function.
My question is, how I really can update the rules after I get new rules from an online api? The declarativeNetRequest api didn't provide any method to do this, all the methods provided will not be useful for this scope.
After I spent sometime to figure out how chrome.declarativeNetRequest.updateDynamicRules() api works, I ended up this solution, and it works fine.
blockUrls is an array variable which holds all block websites/urls.
blockUrls.forEach((domain, index) => {
let id = index + 1;
chrome.declarativeNetRequest.updateDynamicRules(
{addRules:[{
"id": id,
"priority": 1,
"action": { "type": "block" },
"condition": {"urlFilter": domain, "resourceTypes": ["main_frame"] }}
],
removeRuleIds: [id]
},
)
})
Create an array with all your new rules. Get the old rules and replace them all with the new rules.
const newRules = [];
blockUrls.forEach((domain, index) => {
newRules.push({
"id": index + 1,
"priority": 1,
"action": { "type": "block" },
"condition": { "urlFilter": domain, "resourceTypes": ["main_frame"] }
});
});
chrome.declarativeNetRequest.getDynamicRules(previousRules => {
const previousRuleIds = previousRules.map(rule => rule.id);
chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds: previousRuleIds, addRules: newRules});
});
I have a problem with Patch in FeathersJS.
I want to update the data with axios.patch
but the message that appears is always No auth token
{"name":"NotAuthenticated","message":"No auth token","code":401,"className":"not-authenticated","data":{},"errors":{}}
This my axios :
Aktifasi() {
axios.patch(process.env.ROOT_API+'/ek_user?id_user=2',
qs.stringify({
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
'Content-Type': 'application/json',
},
active_user: 1
}))
.then(request => this.AktifasiSuccesSend(request))
.catch((error) => this.AktifasiFailedSend(error))
},
AktifasiSuccesSend (request) {
console.log('Yay');
},
AktifasiFailedSend (error) {
console.log('Oh Fail');
}
And this Hook on FeathersJS :
before: {
all: [],
find: [ authenticate('jwt') ],
get: [ authenticate('jwt') ],
create: [ hashPassword() ],
update: [ hashPassword(), authenticate('jwt') ],
patch: [ hashPassword(), authenticate('jwt') ],
remove: [ authenticate('jwt') ]
},
As the Axios configuration documentation shows, headers are passed as a separate option not as a stringified query string (which shouldn't be necessary at all):
const data = {
active_user: 1
};
const config = {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
'Content-Type': 'application/json',
}
};
axios.patch(process.env.ROOT_API + '/ek_user?id_user=2', data, config);
I recommend becoming very good at using a proper Node debugger. Visual Studio Code has a great debugger. I even wrote an article about it on the Feathers blog: https://blog.feathersjs.com/debugging-feathers-with-visual-studio-code-406e6adf2882
I will give you some pointers to get you on your way, but you will be required to answer your own question by using a debugger.
The "No auth token" message that you are getting is coming from the authenticate('jwt') hook. Here are some typical steps you'd use to solve your own problem:
If you open that hook in your node_modules folder and put break points in it before the message, you'll be able to see where it's looking for a jwt token.
If you create a hook before all other hooks in the patch hooks, you'll be able to put a break point in it and inspect the hook context object to see if the request contains the jwt, or not (in the same location that the authenticate hook expects it.
If the jwt token is not in the place where the authenticate hook expects to find it, you may be missing a middleware function registration in your authentication.js setup file. You would check the feathers docs to make sure you've properly registered the authentication plugins.
I according to the chrome.webRequest I want to change data-urls responseheaders.But I can't capture data request in chrome.webRequest.onHeadersReceived.
Am I wrong ?
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
const url = details.url
if(url == 'http://www.example.com/api/getUsers') {
return {
redirectUrl: 'data:application/json; charset=utf-8,' +
JSON.stringify({"a":1, "b": 2})
}
}
return {cancel: false}
},
{urls: ["<all_urls>"]},
["blocking"]
)
chrome.webRequest.onHeadersReceived.addListener(
function (details) {
console.log(details) // can't capture data-urls
return {responseHeaders:details.responseHeaders};
},
{urls: ["<all_urls>"]},
["responseHeaders","blocking"]
)
You can't capture data: URLs with webRequest API, since data: is not a supported scheme for host permissions.
As such, even with <all_urls> permission/filter you won't get events for data:
Documentation quote:
The webRequest API only exposes requests that the extension has permission to see, given its host permissions. Moreover, only the following schemes are accessible: http://, https://, ftp://, file://, ws:// (since Chrome 58), wss:// (since Chrome 58), or chrome-extension://.
I'm pretty sure there's already a feature request for this, but I can't currently find it.
If JSON arrays are used in a SenseNet settings object, they are not accessible via the OData API.
For example, consider the following SenseNet settings object, which comes installed at Root/System/Settings/Portal.settings by default:
{
ClientCacheHeaders: [
{ ContentType: "PreviewImage", MaxAge: 1 },
{ Extension: "jpeg", MaxAge: 604800 },
{ Extension: "gif", MaxAge: 604800 },
{ Extension: "jpg", MaxAge: 604800 },
{ Extension: "png", MaxAge: 604800 },
{ Extension: "swf", MaxAge: 604800 },
{ Extension: "css", MaxAge: 600 },
{ Extension: "js", MaxAge: 600 }
],
UploadFileExtensions: {
"jpg": "Image",
"jpeg": "Image",
"gif": "Image",
"png": "Image",
"bmp": "Image",
"svg": "Image",
"svgz": "Image",
"tif": "Image",
"tiff": "Image",
"xaml": "WorkflowDefinition",
"DefaultContentType": "File"
},
BinaryHandlerClientCacheMaxAge: 600,
PermittedAppsWithoutOpenPermission: "Details"
}
When viewing this object through the OData API, the ClientCacheHeaders field is not included:
{
"d": {
"UploadFileExtensions.jpg": "Image",
"UploadFileExtensions.jpeg": "Image",
"UploadFileExtensions.gif": "Image",
"UploadFileExtensions.png": "Image",
"UploadFileExtensions.bmp": "Image",
"UploadFileExtensions.svg": "Image",
"UploadFileExtensions.svgz": "Image",
"UploadFileExtensions.tif": "Image",
"UploadFileExtensions.tiff": "Image",
"UploadFileExtensions.xaml": "WorkflowDefinition",
"UploadFileExtensions.DefaultContentType": "File",
"BinaryHandlerClientCacheMaxAge": 600,
"PermittedAppsWithoutOpenPermission": "Details",
}
}
If you search specifically for the ClientCacheHeaders field using the following query:
Odata.svc/Root/System/Settings('Portal.settings')?&metadata=no&$select=ClientCacheHeaders
the API returns null:
{
"d": {
"ClientCacheHeaders": null
}
}
I know that JSON arrays are allowed in settings files because the above example is referenced in the SenseNet wiki page describing settings usage.
Am I performing my OData query incorrectly, or is this some sort of parsing bug in the SenseNet API?
Here's an implementation of the custom OData function suggested by Miklos. Once this is done, you have to register the OData call as described here.
public static class OData
{
[ODataFunction]
public static string GetMySettings(Content content)
{
var retstr = "";
try
{
var settingsFile = Settings.GetSettingsByName<Settings>("MySettings", content.Path);
var node = Node.LoadNode(settingsFile.Path) as Settings;
var bindata = node.GetBinary("Binary");
using (var sr = bindata.GetStream())
using (var tr = new System.IO.StreamReader(sr))
retstr = tr.ReadToEnd();
}
catch (Exception e)
{
SnLog.WriteException(e);
}
return retstr;
}
}
This is a limitation of the current dynamic json field conversion behind the odata api. It actually converts these setting json properties to sensenet fields, so what you see in the odata response is not the actual settings json, but only the fragments that can be converted to sensenet fields (for the curious: it happens in the JsonDynamicFieldHelper class, BuildDynamicFieldMetadata method).
And unfortunately there is no built-in field type in sensenet for handling json arrays, it is not possible to convert a json array to a field value, this is why the system skips it.
Workaround 1
Get the raw settings json in javascript in two steps. The following request gives you the binary field's direct url:
/odata.svc/Root/System/Settings('Portal.settings')?&metadata=no&$select=Binary
...something like this:
/binaryhandler.ashx?nodeid=1084&propertyname=Binary&checksum=1344168
...and if you load that, you'll get the full raw json, including the array.
Please note: Settings are not accessible to visitors by
default, for a reason: they may contain sensitive information. So if
you want to let your users access settings directly (the way you tried
or the way described in the first workaround above), you'll have to
give open permission for the necessary user groups on those setting
files. This is not the case with the second workaround.
Workaround 2
Create a custom odata action that returns settings in a format of your choice from the server. This is a better solution, because this way you control which parts of a setting file is actually accessible to the client.
I follow the tutorial here https://developer.chrome.com/apps/app_identity and use the api here https://developer.chrome.com/apps/identity but with no luck. Could anyone point out is anything wrong in this code?
function onGoogleLibraryLoaded() {
var redirect_uri = chrome.identity.getRedirectURL("http://qqibrow.github.io");
var full_url = "https://stackexchange.com/oauth/dialog?client_id=4716&redirect_uri=" + redirect_uri;
console.log(redirect_uri);
console.log(full_url);
chrome.identity.launchWebAuthFlow({
'url': full_url,
'interactive': true
}, authorizationCallback);
}
var authorizationCallback = function (data) {
// should print out redirect_uri with auth_token if succeed.
console.log(data);
};
// manifest.json
// ...
"permissions": [
"activeTab",
"identity",
"https://ajax.googleapis.com/",
"https://stackexchange.com/*",
"https://stackexchange.com/oauth/*",
"http://qqibrow.github.io/*"
],
"web_accessible_resources": [
"http://qqibrow.github.io/*",
"https://stackexchange.com/*",
],
// ...
If i try https://stackexchange.com/oauth/dialog?client_id=4716&redirect_uri=http://qqibrow.github.io it does work. But with above code, I always got a error page from stackexchange, saying that:
Application Login Failure error description: Cannot return to provided redirect_uri.
This is a creative usage of chrome.identity.getRedirectURL().
It does not allow you to redirect to an arbitrary domain; you can provide a path, but the domain for chrome.identity will be https://<app-id>.chromiumapp.org.
So, your call returns https://<app-id>.chromiumapp.org/http://qqibrow.github.io which is not a valid URL, and your auth fails.
I recommend re-reading the launchWebAuthFlow documentation.