Modify HTTP responses from a Chrome extension
Asked Answered
S

9

123

Is it possible to create a Chrome extension that modifies HTTP response bodies?

I have looked in the Chrome Extension APIs, but I haven't found anything to do this.

Sculpturesque answered 19/8, 2013 at 9:26 Comment(1)
If you accept other browsers, then Firefox supports webRequest.filterResponseData(). Unfortunately this is a Firefox-only solution.Peplos
M
67

In general, you cannot change the response body of a HTTP request using the standard Chrome extension APIs.

This feature is being requested at 104058: WebRequest API: allow extension to edit response body. Star the issue to get notified of updates.

If you want to edit the response body for a known XMLHttpRequest, inject code via a content script to override the default XMLHttpRequest constructor with a custom (full-featured) one that rewrites the response before triggering the real event. Make sure that your XMLHttpRequest object is fully compliant with Chrome's built-in XMLHttpRequest object, or AJAX-heavy sites will break.

In other cases, you can use the chrome.webRequest or chrome.declarativeWebRequest APIs to redirect the request to a data:-URI. Unlike the XHR-approach, you won't get the original contents of the request. Actually, the request will never hit the server because redirection can only be done before the actual request is sent. And if you redirect a main_frame request, the user will see the data:-URI instead of the requested URL.

Mechanize answered 19/8, 2013 at 14:16 Comment(14)
I don't think the data:-URI idea works. I just tried to do this and it seems CORS blocks it. The page making the original request ends up saying: "The request was redirected to 'data:text/json;,{...}', which is disallowed for cross-origin requests that require preflight."Gass
@Gass Cannot reproduce in Chromium 39.0.2171.96Mechanize
@RobW, What are some hacks or solutions to stop the URL from changing into data:text...?Andersonandert
@Andersonandert There is not really a satisfactory solution. In my answer I already mentioned the option of using a content script to replace the content, but other than that you cannot "modify" the response without causing the URL to change.Mechanize
I swear even if we wait till 2050, the evil of Google will never allow developers to change server responses. This is so that they can have a monopoly on web browsers, because once they would have implement it, it would cost almost nothing for someone to create an alternative browser running atop Chrome.Andersonandert
I tried this solution. Note that you may need override fetch() besides XMLHttpRequest. the limitation is that the browser's requests to js/images/css are not intercepted.Creosol
Some websites, which I have to use, are misusing lots of alert()s for trivial things, which is very annoying. I can see where they are defined: in a ".js" script file. I want to remove alert()s in the script. As of February 2019, there is no way to modify a ".js" file with a Chrome extension without enabling some debugging flags. Did I get it correctly? I want to publish the extension so that others can benefit from it, but I cannot really expect them to enable debugging flags for it.Agnella
@DamnVegetables, can't get your meaning. Use an exampleAndersonandert
For example, in a shopping cart page, if you remove an item, it shows "X has been removed [OK]". If you remove two items, you get two such alerts. Another example is in the preference page. When you click the [Save] button, it shows "Your changes are saved [OK]" before going back to the previous page. I think this type of alerts are completely useless; I can already see that the items is gone with my eyes.Agnella
You have to modify not only XMLHttpRequest, but also fetch() APIPenitentiary
chrome.declarativeWebRequest is now deprecated. Use chrome.declarativeNetRequest instead.Deandra
Is this answer outdated based on https://mcmap.net/q/180520/-modify-http-responses-from-a-chrome-extension ?Leopold
@RobW, re "redirection can only be done before the actual request is sent", what's with this limitation?Andersonandert
@TJ, to avoid the overwhelming-UI warning bar, you need a browser flag for that no?Andersonandert
L
34

Like @Rob w said, I've override XMLHttpRequest and this is a result for modification any XHR requests in any sites (working like transparent modification proxy):

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
        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) {}

            console.log('Caught! :)', method, URL/*, _this.responseText*/);
        }
        // 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);
};

for example this code can be used successfully by Tampermonkey for making any modifications on any sites :)

Lagrange answered 30/7, 2018 at 13:12 Comment(5)
I've used you code, and it logged the caught on the console, but it did not change the response that my application got (Angular).Correspond
@AndréRoggeriCampos I ran into the same thing. Angular uses the newer response rather than responseText, so all you have to do is change the Object.defineProperty to use response insteadBracteate
This seems to work well! If I need to prevent this in my production site, is there a way?Grappa
Cannot redefine property: onreadystatechangeHomespun
It works if you define the responseText to response, like this: Object.defineProperty(_this, 'response', {value: JSON.stringify(data)});Luxembourg
Q
33

I just released a Devtools extension that does just that :)

It's called tamper, it's based on mitmproxy and it allows you to see all requests made by the current tab, modify them and serve the modified version next time you refresh.

It's a pretty early version but it should be compatible with OS X and Windows. Let me know if it doesn't work for you.

You can get it here http://dutzi.github.io/tamper/

How this works

As @Xan commented below, the extension communicates through Native Messaging with a python script that extends mitmproxy.

The extension lists all requests using chrome.devtools.network.onRequestFinished.

When you click on of the requests it downloads its response using the request object's getContent() method, and then sends that response to the python script which saves it locally.

It then opens file in an editor (using call for OSX or subprocess.Popen for windows).

The python script uses mitmproxy to listen to all communication made through that proxy, if it detects a request for a file that was saved it serves the file that was saved instead.

I used Chrome's proxy API (specifically chrome.proxy.settings.set()) to set a PAC as the proxy setting. That PAC file redirect all communication to the python script's proxy.

One of the greatest things about mitmproxy is that it can also modify HTTPs communication. So you have that also :)

Quirk answered 6/10, 2014 at 21:59 Comment(13)
Interesting solution, though it requires a Native Host module.Unhouse
It would help, by the way, if you better explain the technique used here.Unhouse
interesting Devtools extension! However it seems one can only modify the response headers and not the response body with Tamper.Salish
Extension is not installing in chrome Version 63Beffrey
Issue with installation.Abrupt
Can we modify response body with this?Grappa
For linux I had to modify the manifest to install tamper "commands": { "toggle-tamper": { "suggested_key": { "windows": "Ctrl+Shift+P", "linux": "Ctrl+Shift+P", "mac": "Command+Shift+P" }, "description": "Toggle Tamper" } }, but I still get error starting proxySchenk
@dutzi, It doesn't require devtools isn't it? It only needs chrome.proxy and a proxy server installedAndersonandert
@Andersonandert it does, but I stopped maintaining it and I'm not sure it works anymore. sorry.Quirk
doesn't install :/ need a new solutionParturifacient
It's Python 2 based, one of the reasons why it does not work anymore.Gnu
mitmproxy is quite a good replacement for fiddler, thanks for sharing :)Backstop
@dutzi, if you need debugger anyways, then that sort of defeats the purpose. It seems like debugger could do it out-of-the-box alr.Andersonandert
O
26

Yes. It is possible with the chrome.debugger API, which grants extension access to the Chrome DevTools Protocol, which supports HTTP interception and modification through its Network API.

This solution was suggested by a comment on Chrome Issue 487422:

For anyone wanting an alternative which is doable at the moment, you can use chrome.debugger in a background/event page to attach to the specific tab you want to listen to (or attach to all tabs if that's possible, haven't tested all tabs personally), then use the network API of the debugging protocol.

The only problem with this is that there will be the usual yellow bar at the top of the tab's viewport, unless the user turns it off in chrome://flags.

First, attach a debugger to the target:

chrome.debugger.getTargets((targets) => {
    let target = /* Find the target. */;
    let debuggee = { targetId: target.id };

    chrome.debugger.attach(debuggee, "1.2", () => {
        // TODO
    });
});

Next, send the Network.setRequestInterceptionEnabled command, which will enable interception of network requests:

chrome.debugger.getTargets((targets) => {
    let target = /* Find the target. */;
    let debuggee = { targetId: target.id };

    chrome.debugger.attach(debuggee, "1.2", () => {
        chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
    });
});

Chrome will now begin sending Network.requestIntercepted events. Add a listener for them:

chrome.debugger.getTargets((targets) => {
    let target = /* Find the target. */;
    let debuggee = { targetId: target.id };

    chrome.debugger.attach(debuggee, "1.2", () => {
        chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
    });

    chrome.debugger.onEvent.addListener((source, method, params) => {
        if(source.targetId === target.id && method === "Network.requestIntercepted") {
            // TODO
        }
    });
});

In the listener, params.request will be the corresponding Request object.

Send the response with Network.continueInterceptedRequest:

  • Pass a base64 encoding of your desired HTTP raw response (including HTTP status line, headers, etc!) as rawResponse.
  • Pass params.interceptionId as interceptionId.

Note that I have not tested any of this, at all.

Octoroon answered 20/7, 2017 at 16:59 Comment(7)
Looks very promising, though I'm trying it now (Chrome 60) and either I'm missing something or it's still not possible; the setRequestInterceptionEnabled method seems to not be included in the DevTools protocol v1.2, and I can't find a way to attach it with the latest (tip-of-tree) version instead.Thais
I tried this solution and it worked to some degree. If you want to modify request, this solution is good. If you want to modify response based on the server returned response, there is no way. there is no response at that point. of coz you can overwrite the rawresponse field as the author said.Creosol
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true }); fails with 'Network.setRequestInterceptionEnabled' wasn't found'Andersonandert
@MultiplyByZer0, Ok managed to get it to work. i.stack.imgur.com/n0Gff.png However, the need to remove the 'topbar', ie the need to set the browser flag followed by a browser restart, means that an alternative real solution is needed.Andersonandert
@Andersonandert You're right that this solution isn't ideal, but I don't know of any better ones. Also, as you've noticed, this answer is incomplete and in need of updating. I'll do that soon, hopefully.Octoroon
@Infinity It was really required for me and used your inspiration here is the extension chrome.google.com/webstore/detail/… github.com/Pasupathi-Rajamanickam/chrome-response-overrideDrivein
Network.setRequestInterceptionEnabled is obsoleted and now should use this new API: chromedevtools.github.io/devtools-protocol/tot/FetchCorbicula
S
4

Yes, you can modify HTTP response in a Chrome extension. I built ModResponse (https://modheader.com/modresponse) that does that. It can record and replay your HTTP response, modify it, add delay, and even use the HTTP response from a different server (like from your localhost)

The way it works is to use the chrome.debugger API (https://developer.chrome.com/docs/extensions/reference/debugger/), which gives you access to Chrome DevTools Protocol (https://chromedevtools.github.io/devtools-protocol/). You can then intercept the request and response using the Fetch Domain API (https://chromedevtools.github.io/devtools-protocol/tot/Fetch/), then override the response you want. (You can also use the Network Domain, though it is deprecated in favor of the Fetch Domain)

The nice thing about this approach is that it will just work out of box. No desktop app installation required. No extra proxy setup. However, it will show a debugging banner in Chrome (which you can add an argument to Chrome to hide), and it is significantly more complicated to setup than other APIs.

For examples on how to use the debugger API, take a look at the chrome-extensions-samples: https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/_archive/mv2/api/debugger/live-headers

Stepmother answered 28/12, 2022 at 18:1 Comment(0)
D
3

While Safari has this feature built-in, the best workaround I've found for Chrome so far is to use Cypress's intercept functionality. It cleanly allows me to stub HTTP responses in Chrome. I call cy.intercept then cy.visit(<URL>) and it intercepts and provides a stubbed response for a specific request the visited page makes. Here's an example:

cy.intercept('GET', '/myapiendpoint', {
  statusCode: 200,
  body: {
    myexamplefield: 'Example value',
  },
})
cy.visit('http://localhost:8080/mytestpage')

Note: You may also need to configure Cypress to disable some Chrome-specific security settings.

Dorey answered 21/6, 2021 at 17:39 Comment(1)
Switching over to Safari to work on my issue really helped me out, thanks!Gass
G
2

Brand new in Chrome Devtools (may 2023 is the momment I am writing that answer). https://developer.chrome.com/docs/devtools/overrides/ Override files and HTTP response headers locally It comes by default in Chrome Version 113.0...

Gorgonian answered 8/5, 2023 at 13:37 Comment(2)
the question is about modyfing body not headers thoughBarquisimeto
and this would still have debugger bar UI no?Andersonandert
U
1

The original question was about Chrome extensions, but I notice that it has branched out into different methods, going by the upvotes on answers that have non-Chrome-extension methods.

Here's a way to kind of achieve this with Puppeteer. Note the caveat mentioned on the originalContent line - the fetched response may be different to the original response in some circumstances.

With Node.js:

npm install puppeteer [email protected]

Create this main.js:

const puppeteer = require("puppeteer");
const fetch = require("node-fetch");

(async function() {

  const browser = await puppeteer.launch({headless:false});
  const page = await browser.newPage();
  await page.setRequestInterception(true);

  page.on('request', async (request) => {
    let url = request.url().replace(/\/$/g, ""); // remove trailing slash from urls
    console.log("REQUEST:", url);

    let originalContent = await fetch(url).then(r => r.text()); // TODO: Pass request headers here for more accurate response (still not perfect, but more likely to be the same as the "actual" response)

    if(url === "https://example.com") {
      request.respond({
        status: 200,
        contentType: 'text/html; charset=utf-8',    // For JS files: 'application/javascript; charset=utf-8'
        body: originalContent.replace(/example/gi, "TESTING123"),
      });
    } else {
      request.continue();
    }
  });

  await page.goto("https://example.com");
})();

Run it:

node main.js

With Deno:

Install Deno:

curl -fsSL https://deno.land/install.sh | sh # linux, mac
irm https://deno.land/install.ps1 | iex      # windows powershell

Download Chrome for Puppeteer:

PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/[email protected]/install.ts

Create this main.js:

import puppeteer from "https://deno.land/x/[email protected]/mod.ts";
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();
await page.setRequestInterception(true);

page.on('request', async (request) => {
  let url = request.url().replace(/\/$/g, ""); // remove trailing slash from urls
  console.log("REQUEST:", url);

  let originalContent = await fetch(url).then(r => r.text()); // TODO: Pass request headers here for more accurate response (still not perfect, but more likely to be the same as the "actual" response)

  if(url === "https://example.com") {
    request.respond({
      status: 200,
      contentType: 'text/html; charset=utf-8',    // For JS files: 'application/javascript; charset=utf-8'
      body: originalContent.replace(/example/gi, "TESTING123"),
    });
  } else {
    request.continue();
  }
});

await page.goto("https://example.com");

Run it:

deno run -A --unstable main.js

(I'm currently running into a TimeoutError with this that will hopefully be resolved soon: https://github.com/lucacasonato/deno-puppeteer/issues/65)

Umbrian answered 15/10, 2022 at 12:57 Comment(1)
and code for bun.js? :DErmina
B
-2

I've just found this extension and it does a lot of other things but modifying api responses in the browser works really well: https://requestly.io/

Follow these steps to get it working:

  1. Install the extension

  2. Go to HttpRules

  3. Add a new rule and add a url and a response

  4. Enable the rule with the radio button

  5. Go to Chrome and you should see the response is modified

You can have multiple rules with different responses and enable/disable as required. I've not found out how you can have a different response per request though if the url is the same unfortunately.

Bejewel answered 10/10, 2022 at 12:43 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.