Ändern Sie HTTP-Antworten von einer Chrome-Erweiterung

83

Ist es möglich, eine Chrome-Erweiterung zu erstellen, die HTTP-Antwortkörper ändert?

Ich habe in den Chrome-Erweiterungs-APIs nachgesehen , aber nichts dafür gefunden.

Kapitän Drache
quelle
1
Im Allgemeinen nein. Siehe https://code.google.com/p/chromium/issues/detail?id=104058 . Für eine Teilmenge von Anwendungsfällen ja. Können Sie klarstellen, warum Sie Antwortkörper bearbeiten möchten?
Rob W
OK danke. Bitte schreibe es als Antwort und ich werde das akzeptieren.
Kapitän Drache
Wenn Sie andere Browser akzeptieren, unterstützt Firefox diese webRequest.filterResponseData(). Leider ist dies eine reine Firefox-Lösung.
Franklin Yu

Antworten:

49

Im Allgemeinen Sie können nicht die Antwort Körper einer HTTP - Anforderung ändern , um die Standard - Chrome - Erweiterung APIs.

Diese Funktion wird unter 104058 angefordert : WebRequest-API: Erweiterung zum Bearbeiten des Antworttextes zulassen . Markieren Sie das Problem, um über Updates informiert zu werden.

Wenn Sie den Antworttext für einen bekannten bearbeiten möchten XMLHttpRequest, fügen Sie Code über ein Inhaltsskript ein , um den Standardkonstruktor XMLHttpRequestmit einem benutzerdefinierten (voll ausgestatteten) Konstruktor zu überschreiben, der die Antwort neu schreibt, bevor das reale Ereignis ausgelöst wird. XMLHttpRequestStellen Sie sicher, dass Ihr XMLHttpRequest-Objekt vollständig mit dem in Chrome integrierten Objekt kompatibel ist. Andernfalls werden AJAX-lastige Websites beschädigt.

In anderen Fällen können Sie die APIs chrome.webRequestoder verwenden chrome.declarativeWebRequest, um die Anforderung an eine data:-URI umzuleiten. Im Gegensatz zum XHR-Ansatz erhalten Sie nicht den ursprünglichen Inhalt der Anfrage. Tatsächlich wird die Anfrage niemals den Server erreichen, da die Umleitung nur erfolgen kann, bevor die eigentliche Anfrage gesendet wird. Wenn Sie eine main_frameAnfrage umleiten , wird dem Benutzer die data:-URI anstelle der angeforderten URL angezeigt.

Rob W.
quelle
1
Ich glaube nicht, dass die Daten: -URI Idee funktioniert. Ich habe gerade versucht, dies zu tun, und es scheint, dass CORS es blockiert. Auf der Seite, auf der die ursprüngliche Anfrage gestellt wurde, heißt es schließlich: "Die Anfrage wurde an 'data: text / json ;, {...}' umgeleitet, was für Cross-Origin-Anfragen, die Preflight erfordern, nicht zulässig ist."
Joe
@ Joe Kann nicht in Chrom 39.0.2171.96 reproduzieren
Rob W
1
@RobW, Was sind einige Hacks oder Lösungen , um zu verhindern, dass sich die URL ändert data:text...?
Pacerier
1
@ Pacerier Es gibt keine wirklich zufriedenstellende Lösung. In meiner Antwort habe ich bereits die Option erwähnt, ein Inhaltsskript zu verwenden, um den Inhalt zu ersetzen. Ansonsten können Sie die Antwort nicht "ändern", ohne dass sich die URL ändert.
Rob W
1
Ich habe diese Lösung ausprobiert. Beachten Sie, dass Sie möglicherweise neben XMLHttpRequest auch fetch () überschreiben müssen. Die Einschränkung besteht darin, dass die Anforderungen des Browsers an js / images / css nicht abgefangen werden.
user861746
27

Ich habe gerade eine Devtools-Erweiterung veröffentlicht, die genau das tut :)

Es heißt Tamper, basiert auf Mitmproxy und ermöglicht es Ihnen, alle von der aktuellen Registerkarte gestellten Anforderungen anzuzeigen , zu ändern und die geänderte Version beim nächsten Aktualisieren bereitzustellen.

Es ist eine ziemlich frühe Version, sollte aber mit OS X und Windows kompatibel sein. Lassen Sie mich wissen, wenn es bei Ihnen nicht funktioniert.

Sie können es hier bekommen http://dutzi.github.io/tamper/

Wie das funktioniert

Wie @Xan weiter unten kommentiert, kommuniziert die Erweiterung über Native Messaging mit einem Python-Skript, das Mitmproxy erweitert .

Die Erweiterung listet alle Anforderungen mit auf chrome.devtools.network.onRequestFinished.

Wenn Sie auf eine der Anforderungen klicken, wird die Antwort mithilfe der getContent()Methode des Anforderungsobjekts heruntergeladen und anschließend an das Python-Skript gesendet, das sie lokal speichert.

Anschließend wird die Datei in einem Editor geöffnet (unter callOSX oder subprocess.PopenWindows).

Das Python-Skript verwendet Mitmproxy, um die gesamte Kommunikation über diesen Proxy abzuhören. Wenn es eine Anforderung für eine gespeicherte Datei erkennt, wird die stattdessen gespeicherte Datei bereitgestellt.

Ich habe die Proxy-API von Chrome (speziell chrome.proxy.settings.set()) verwendet, um einen PAC als Proxy-Einstellung festzulegen. Diese PAC-Datei leitet die gesamte Kommunikation an den Proxy des Python-Skripts weiter.

Eines der größten Dinge bei Mitmproxy ist, dass es auch die HTTP-Kommunikation ändern kann. Also hast du das auch :)

Dutzi
quelle
Interessante Lösung, obwohl ein Native Host-Modul erforderlich ist.
Xan
5
Es wäre übrigens hilfreich, wenn Sie die hier verwendete Technik besser erklären würden.
Xan
9
interessante Devtools-Erweiterung! Es scheint jedoch kann man nur die Antwort ändern Header und nicht die Antwort Körper mit Tamper.
Michael Trouw
3
Problem mit der Installation.
Dmitry Pleshkov
1
Können wir den Antworttext damit ändern?
Kiran
17

Ja. Dies ist mit der chrome.debuggerAPI möglich, die Erweiterungszugriff auf das Chrome DevTools-Protokoll gewährt , das das Abfangen und Ändern von HTTP über die Netzwerk-API unterstützt .

Diese Lösung wurde durch einen Kommentar zu Chrome Issue 487422 vorgeschlagen :

Für alle, die eine Alternative suchen, die derzeit machbar ist, können Sie sie chrome.debuggerauf einer Hintergrund- / Ereignisseite verwenden, um sie an die bestimmte Registerkarte anzuhängen, die Sie anhören möchten (oder wenn möglich an alle Registerkarten anhängen, nicht alle Registerkarten persönlich getestet haben). Verwenden Sie dann die Netzwerk-API des Debugging-Protokolls.

Das einzige Problem dabei ist, dass sich oben im Ansichtsfenster der Registerkarte der übliche gelbe Balken befindet, es sei denn, der Benutzer schaltet ihn aus chrome://flags.

Fügen Sie zunächst einen Debugger an das Ziel an:

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

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

Senden Sie als Nächstes den Network.setRequestInterceptionEnabledBefehl, der das Abfangen von Netzwerkanforderungen ermöglicht:

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 beginnt nun mit dem Senden von Network.requestInterceptedEreignissen. Fügen Sie einen Listener für sie hinzu:

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

Im Listener params.requestwird das entsprechende RequestObjekt sein.

Senden Sie die Antwort mit Network.continueInterceptedRequest:

  • Übergeben Sie eine Base64-Codierung Ihrer gewünschten HTTP-Raw-Antwort ( einschließlich HTTP-Statuszeile, Header usw.! ) Als rawResponse.
  • Pass params.interceptionIdas interceptionId.

Beachten Sie, dass ich nichts davon getestet habe.

MultiplyByZer0
quelle
Sieht sehr vielversprechend aus, obwohl ich es jetzt versuche (Chrome 60) und entweder etwas fehlt oder es immer noch nicht möglich ist. Die setRequestInterceptionEnabledMethode scheint nicht im DevTools-Protokoll v1.2 enthalten zu sein, und ich kann keine Möglichkeit finden, sie stattdessen mit der neuesten Version (Tip-of-Tree) zu verknüpfen.
Aioros
1
Ich habe diese Lösung ausprobiert und sie hat bis zu einem gewissen Grad funktioniert. Wenn Sie die Anforderung ändern möchten, ist diese Lösung gut. Wenn Sie die Antwort basierend auf der vom Server zurückgegebenen Antwort ändern möchten, gibt es keine Möglichkeit. Zu diesem Zeitpunkt erfolgt keine Antwort. von coz können Sie das Rawresponse-Feld überschreiben, wie der Autor sagte.
user861746
1
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });schlägt fehl mit 'Network.setRequestInterceptionEnabled' wurde nicht gefunden '
Pacerier
1
@ MultiplyByZer0, Ok hat es geschafft, es zum Laufen zu bringen. i.stack.imgur.com/n0Gff.png Die Notwendigkeit, die 'obere Leiste' zu entfernen, dh das Browser-Flag zu setzen, gefolgt von einem Neustart des Browsers, bedeutet jedoch, dass eine alternative echte Lösung erforderlich ist.
Pacerier
@Pacerier Sie haben Recht, dass diese Lösung nicht ideal ist, aber ich kenne keine besseren. Wie Sie bemerkt haben, ist diese Antwort unvollständig und muss aktualisiert werden. Ich werde das hoffentlich bald tun.
MultiplyByZer0
12

Wie @Rob w sagte, habe ich überschrieben XMLHttpRequestund dies ist ein Ergebnis für die Änderung von XHR-Anforderungen auf beliebigen Websites (funktioniert wie ein transparenter Änderungs-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);
};

Zum Beispiel kann dieser Code von Tampermonkey erfolgreich verwendet werden, um Änderungen an Websites vorzunehmen :)

MixerOID
quelle
1
Ich habe Ihren Code verwendet und er hat den auf der Konsole gefangenen Code protokolliert, aber die Antwort meiner Anwendung (Angular) hat sich nicht geändert.
André Roggeri Campos
2
@ AndréRoggeriCampos Ich bin auf das Gleiche gestoßen. Angular verwendet die neuere responseanstelle von responseText, also müssen Sie nur die Object.defineProperty ändern, um responsestattdessen zu verwenden
Jonathan Gawrych
Vielen Dank ! es funktioniert auf meiner Chrome-Erweiterung!
Lancer.Yan