Zmodyfikuj odpowiedzi HTTP z rozszerzenia Chrome

83

Czy można utworzyć rozszerzenie do Chrome, które modyfikuje treść odpowiedzi HTTP?

Szukałem w Chrome Extension API , ale nie znalazłem nic, aby to zrobić.

Kapitan Smok
źródło
1
Ogólnie nie. Zobacz https://code.google.com/p/chromium/issues/detail?id=104058 . W przypadku podzbioru przypadków użycia tak. Czy możesz wyjaśnić, dlaczego chcesz edytować treść odpowiedzi?
Rob W
ok dzięki. napisz to jako odpowiedź, a ja to zaakceptuję.
kapitan smok
Jeśli akceptujesz inne przeglądarki, Firefox obsługuje webRequest.filterResponseData(). Niestety jest to rozwiązanie tylko dla przeglądarki Firefox.
Franklin Yu

Odpowiedzi:

49

Ogólnie rzecz biorąc, nie można zmienić treści odpowiedzi żądania HTTP przy użyciu standardowych interfejsów API rozszerzeń Chrome.

Ta funkcja jest wymagana pod adresem 104058: WebRequest API: zezwól rozszerzeniu na edytowanie treści odpowiedzi . Oznacz problem gwiazdką, aby otrzymywać powiadomienia o aktualizacjach.

Jeśli chcesz edytować treść odpowiedzi dla znanego XMLHttpRequest, wstrzykuj kod za pomocą skryptu zawartości, aby zastąpić domyślny XMLHttpRequestkonstruktor niestandardowym (w pełni funkcjonalnym), który przepisuje odpowiedź przed wyzwoleniem rzeczywistego zdarzenia. Upewnij się, że Twój obiekt XMLHttpRequest jest w pełni zgodny z wbudowanym XMLHttpRequestobiektem Chrome, w przeciwnym razie strony z dużą ilością technologii AJAX ulegną awarii.

W innych przypadkach możesz użyć interfejsów API chrome.webRequestlub, chrome.declarativeWebRequestaby przekierować żądanie do data:-URI. W przeciwieństwie do podejścia XHR, nie otrzymasz oryginalnej treści żądania. W rzeczywistości żądanie nigdy nie dotrze do serwera, ponieważ przekierowanie można wykonać tylko przed wysłaniem rzeczywistego żądania. Jeśli przekierowujesz main_frameżądanie, użytkownik zobaczy data:-URI zamiast żądanego adresu URL.

Rob W
źródło
1
Nie sądzę, żeby dane: -Pomysł na Uri działa. Właśnie próbowałem to zrobić i wygląda na to, że CORS to blokuje. Strona wysyłająca pierwotne żądanie kończy się komunikatem: „Żądanie zostało przekierowane do 'data: text / json;, {...}', co jest niedozwolone w przypadku żądań między źródłami, które wymagają inspekcji wstępnej”.
Joe
@Joe Cannot reproduced in Chromium 39.0.2171.96
Rob W
1
@RobW, Jakie są sposoby lub rozwiązania, które uniemożliwiają zmianę adresu URL data:text...?
Pacerier,
1
@Pacerier Tak naprawdę nie ma satysfakcjonującego rozwiązania. W mojej odpowiedzi wspomniałem już o możliwości użycia skryptu zawartości do zastąpienia treści, ale poza tym nie można „modyfikować” odpowiedzi bez spowodowania zmiany adresu URL.
Rob W.
1
Wypróbowałem to rozwiązanie. Zauważ, że oprócz XMLHttpRequest możesz potrzebować nadpisania funkcji fetch (). ograniczeniem jest to, że żądania przeglądarki do js / images / css nie są przechwytywane.
user861746
27

Właśnie wydałem rozszerzenie Devtools, które właśnie to robi :)

Nazywa się tamper, jest oparty na mitmproxy i pozwala zobaczyć wszystkie żądania wysyłane przez bieżącą kartę, modyfikować je i obsługiwać zmodyfikowaną wersję przy następnym odświeżeniu.

To dość wczesna wersja, ale powinna być kompatybilna z OS X i Windows. Daj mi znać, jeśli to nie zadziała.

Możesz go pobrać tutaj http://dutzi.github.io/tamper/

Jak to działa

Jak skomentował @Xan poniżej, rozszerzenie komunikuje się poprzez Native Messaging ze skryptem Pythona, który rozszerza mitmproxy .

Rozszerzenie wyświetla listę wszystkich żądań używających chrome.devtools.network.onRequestFinished.

Po kliknięciu żądań pobiera swoją odpowiedź przy użyciu metody obiektu żądania getContent(), a następnie wysyła tę odpowiedź do skryptu Pythona, który zapisuje ją lokalnie.

Następnie otwiera plik w edytorze (używając calldla OSX lub subprocess.Popendla Windows).

Skrypt w Pythonie używa mitmproxy do nasłuchiwania całej komunikacji prowadzonej przez to proxy, jeśli wykryje żądanie dotyczące zapisanego pliku, zamiast tego obsługuje zapisany plik.

Użyłem API proxy Chrome (w szczególności chrome.proxy.settings.set()), aby ustawić PAC jako ustawienie proxy. Ten plik PAC przekierowuje całą komunikację do serwera proxy skryptu Python.

Jedną z największych zalet mitmproxy jest to, że może również modyfikować komunikację HTTPs. Więc to też masz :)

dutzi
źródło
Ciekawe rozwiązanie, choć wymaga modułu Native Host.
Xan
5
Nawiasem mówiąc, pomogłoby, gdybyś lepiej wyjaśnił zastosowaną tutaj technikę.
Xan
10
ciekawe rozszerzenie Devtools! Jednak wydaje się, że można modyfikować tylko odpowiedź nagłówki i nie reakcja organizmu z sabotaż.
Michael Trouw
3
Problem z instalacją.
Dmitry Pleshkov
1
Czy możemy za pomocą tego zmodyfikować treść odpowiedzi?
Kiran
17

Tak. Jest to możliwe dzięki chrome.debuggerinterfejsowi API, który zapewnia rozszerzeniu dostęp do protokołu Chrome DevTools , który obsługuje przechwytywanie i modyfikację HTTP za pośrednictwem sieciowego interfejsu API .

To rozwiązanie zostało zasugerowane w komentarzu do Chrome Issue 487422 :

Dla każdego, kto chce alternatywy, która jest obecnie możliwa, możesz użyć na stronie chrome.debuggerw tle / wydarzenia, aby dołączyć do określonej karty, której chcesz słuchać (lub dołączyć do wszystkich kart, jeśli to możliwe, nie przetestowałem osobiście wszystkich kart) , a następnie użyj sieciowego API protokołu debugowania.

Jedynym problemem jest to, że u góry widoku karty będzie widoczny zwykły żółty pasek, chyba że użytkownik wyłączy go w chrome://flags.

Najpierw dołącz debugger do celu:

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

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

Następnie wyślij Network.setRequestInterceptionEnabledpolecenie, które umożliwi przechwytywanie żądań sieciowych:

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 zacznie teraz wysyłać Network.requestInterceptedwydarzenia. Dodaj dla nich słuchacza:

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

W odbiorniku params.requestbędzie odpowiedni Requestobiekt.

Wyślij odpowiedź z Network.continueInterceptedRequest:

  • Przekaż kodowanie base64 żądanej nieprzetworzonej odpowiedzi HTTP (w tym wiersz stanu HTTP, nagłówki itp. ) Jako rawResponse.
  • Podaj params.interceptionIdjako interceptionId.

Zwróć uwagę, że w ogóle tego nie testowałem.

MultiplyByZer0
źródło
Wygląda bardzo obiecująco, chociaż próbuję go teraz (Chrome 60) i albo coś mi brakuje, albo nadal nie jest to możliwe; setRequestInterceptionEnabledmetoda wydaje się nie być zawarte w protokole DevTools v1.2, a ja nie mogę znaleźć sposób, aby dołączyć go do najnowszej (tip-of-tree) zamiast wersji.
Aioros
1
Wypróbowałem to rozwiązanie i do pewnego stopnia zadziałało. Jeśli chcesz zmodyfikować żądanie, to rozwiązanie jest dobre. Jeśli chcesz zmodyfikować odpowiedź na podstawie odpowiedzi zwróconej przez serwer, nie ma sposobu. w tym momencie nie ma odpowiedzi. z coz możesz nadpisać pole nieprzetworzonej odpowiedzi, jak powiedział autor.
user861746
1
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });nie powiodło się, a „Network.setRequestInterceptionEnabled” nie został znaleziony ”
Pacerier
1
@ MultiplyByZer0, Ok udało się go uruchomić. i.stack.imgur.com/n0Gff.png Jednak potrzeba usunięcia „topbar”, czyli konieczność ustawienia flagi przeglądarki, po której następuje restart przeglądarki, oznacza, że ​​potrzebne jest alternatywne, rzeczywiste rozwiązanie.
Pacerier
@Pacerier Masz rację, że to rozwiązanie nie jest idealne, ale nie znam lepszego. Ponadto, jak zauważyłeś, ta odpowiedź jest niekompletna i wymaga aktualizacji. Mam nadzieję, że wkrótce to zrobię.
MultiplyByZer0
13

Jak powiedział @Rob w, nadpisałem XMLHttpRequesti jest to wynikiem modyfikacji wszelkich żądań XHR w dowolnych witrynach (działa jak przezroczysty proxy modyfikacji):

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

na przykład ten kod może być z powodzeniem używany przez Tampermonkey do wprowadzania jakichkolwiek modyfikacji na dowolnych stronach :)

MixerOID
źródło
1
Użyłem twojego kodu i zarejestrowałem przechwycony na konsoli, ale nie zmieniło to odpowiedzi, którą otrzymała moja aplikacja (Angular).
André Roggeri Campos,
2
@ AndréRoggeriCampos Wpadłem na to samo. Angular używa responseraczej nowszego niż responseText, więc wszystko, co musisz zrobić, to zmienić Object.defineProperty, aby użyć responsezamiast tego
Jonathan Gawrych
Dziękuję Ci bardzo ! działa na moim rozszerzeniu do Chrome!
Lancer.Yan