98

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.

Sachin
  • 20,754
  • 28
  • 93
  • 162
captain dragon
  • 1,099
  • 1
  • 8
  • 8
  • If you accept other browsers, then Firefox supports [`webRequest.filterResponseData()`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/filterResponseData). Unfortunately this is a Firefox-only solution. – Franklin Yu Oct 17 '19 at 07:16

7 Answers7

58

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.

Rudie
  • 49,688
  • 40
  • 128
  • 172
Rob W
  • 328,606
  • 78
  • 779
  • 666
  • 1
    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." – Joe Dec 30 '14 at 18:47
  • @Joe Cannot reproduce in Chromium 39.0.2171.96 – Rob W Dec 30 '14 at 18:58
  • 1
    @RobW, What are some hacks or **solutions** to stop the URL from changing into `data:text...`? – Pacerier Feb 22 '17 at 16:53
  • 1
    @Pacerier 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. – Rob W Feb 23 '17 at 00:29
  • 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](https://www.eff.org/deeplinks/2016/04/save-firefox) running atop Chrome. – Pacerier Jul 15 '17 at 15:49
  • 1
    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. – user861746 May 10 '18 at 07:29
  • 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. – Damn Vegetables Feb 22 '19 at 16:03
  • @DamnVegetables, can't get your meaning. Use an example – Pacerier May 26 '19 at 18:55
  • 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. – Damn Vegetables Jun 06 '19 at 19:47
  • You have to modify not only `XMLHttpRequest`, but also `fetch()` API – Eugene Mala Feb 22 '21 at 23:59
  • `chrome.declarativeWebRequest` is now deprecated. Use [`chrome.declarativeNetRequest`](https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/) instead. – Maytha8 Jun 04 '22 at 11:16
30

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 :)

dutzi
  • 1,714
  • 1
  • 16
  • 22
28

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 :)

mixalbl4
  • 2,857
  • 1
  • 24
  • 41
24

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.

Community
  • 1
  • 1
MultiplyByZer0
  • 5,209
  • 3
  • 29
  • 46
  • 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. – Aioros Jul 27 '17 at 22:03
  • 1
    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. – user861746 May 10 '18 at 07:26
  • 1
    `chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });` fails with 'Network.setRequestInterceptionEnabled' wasn't found' – Pacerier May 26 '19 at 19:36
  • 1
    @MultiplyByZer0, Ok managed to get it to work. https://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.** – Pacerier May 26 '19 at 21:38
  • @Pacerier 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. – MultiplyByZer0 May 26 '19 at 23:32
  • @Infinity It was really required for me and used your inspiration here is the extension https://chrome.google.com/webstore/detail/kbipbobgpnhgghikihmodppmfbkbmgfj/publish-accepted?authuser=0&hl=en https://github.com/Pasupathi-Rajamanickam/chrome-response-override – Pasupathi Rajamanickam Feb 22 '20 at 07:25
  • `Network.setRequestInterceptionEnabled` is obsoleted and now should use this new API: https://chromedevtools.github.io/devtools-protocol/tot/Fetch – macabeus Mar 08 '21 at 01:50
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.

Stephen Rudolph
  • 1,345
  • 15
  • 20
2

Chrome doesn't provide any APIs to modify the response body. This is neither possible with Chrome WebRequest API nor DeclarativeNetRequest API.

Using Desktop App - Modify Response for any request type

Requestly Desktop App offers this capability to create Modify HTTP Response Rule and then you can use that in any browser. Here's the demo video. Here are the steps to do it

  1. Install Requestly Desktop App & Launch your browser from Connected Apps
  2. Go to HTTP Rules and Create a Modify HTTP Response Rule
  3. Define your URL Pattern and define your custom response

You can define your custom response in 3 ways

  1. Static Content - Fixed Response you need
  2. Progamatic Override - If you need to override something in the existing server response
  3. Load from file - Define your response in the local file and map it here.

Not only this, but You can also change the HTTP Status code to 400 or 500 using this approach.

Here's a screenshot - An example of overriding existing response using JS Code

Override HTTP Response Programatically

Using Requestly Chrome Extension - Modify Response for only XHR/fetch requests

One can also use Requestly Chrome Extension to modify the response of HTTP Requests triggered by XHR/fetch only. Here's the demo video using Chrome Extension

Since Chrome doesn't provide any API, Requestly overrides the XHR and fetch prototype object to hook the custom implementation and you'd get the response you have defined in the Requestly response modification rule. You can read more about it in the docs.

PS: I built Requestly

Sachin
  • 20,754
  • 28
  • 93
  • 162
0

If I want to quickly mock a response, usually because my local data doesn't have a live response I need for instance... use chrome extension Tweak! It's free for up to twelve URL's to intercept:

https://tweak-extension.com/

Put in your URL and specify the Get/Post and the paste in your response. Perfect for quick bug recreations etc.

enter image description here

MagicLuckyCat
  • 187
  • 2
  • 3
  • 17