Modificar las respuestas HTTP desde una extensión de Chrome

83

¿Es posible crear una extensión de Chrome que modifique los cuerpos de respuesta HTTP?

He buscado en las API de extensión de Chrome , pero no he encontrado nada para hacer esto.

capitán dragón
fuente
1
En general, no. Consulte https://code.google.com/p/chromium/issues/detail?id=104058 . Para un subconjunto de casos de uso, sí. ¿Puede aclarar por qué desea editar los cuerpos de respuesta?
Rob W
OK gracias. por favor escríbalo como respuesta y lo aceptaré.
capitán dragón
Si acepta otros navegadores, Firefox admite webRequest.filterResponseData(). Desafortunadamente, esta es una solución solo para Firefox.
Franklin Yu

Respuestas:

49

En general, no puede cambiar el cuerpo de respuesta de una solicitud HTTP utilizando las API de extensión estándar de Chrome.

Esta función se solicita en 104058: API WebRequest: permitir que la extensión edite el cuerpo de la respuesta . Destaca el problema para recibir notificaciones de actualizaciones.

Si desea editar el cuerpo de la respuesta para un conocido XMLHttpRequest, inyecte código a través de una secuencia de comandos de contenido para anular el XMLHttpRequestconstructor predeterminado con uno personalizado (con todas las funciones) que reescribe la respuesta antes de activar el evento real. Asegúrese de que su objeto XMLHttpRequest sea totalmente compatible con el XMLHttpRequestobjeto integrado de Chrome , o los sitios con mucho AJAX se romperán.

En otros casos, puede utilizar las API chrome.webRequesto chrome.declarativeWebRequestpara redirigir la solicitud a data:-URI. A diferencia del enfoque XHR, no obtendrá el contenido original de la solicitud. En realidad, la solicitud nunca llegará al servidor porque la redirección solo se puede realizar antes de que se envíe la solicitud real. Y si redirige una main_framesolicitud, el usuario verá la data:-URI en lugar de la URL solicitada.

Rob W
fuente
1
No creo que los datos: -La idea de URI funciona. Intenté hacer esto y parece que CORS lo bloquea. La página que realiza la solicitud original termina diciendo: "La solicitud se redirigió a 'data: text / json;, {...}', que no está permitida para solicitudes de origen cruzado que requieren verificación previa".
Joe
@Joe No se puede reproducir en Chromium 39.0.2171.96
Rob W
1
@RobW, ¿Cuáles son algunos trucos o soluciones para evitar que cambie la URL data:text...?
Pacerier
1
@Pacerier Realmente no hay una solución satisfactoria. En mi respuesta, ya mencioné la opción de usar un script de contenido para reemplazar el contenido, pero aparte de eso, no puede "modificar" la respuesta sin que la URL cambie.
Rob W
1
Probé esta solución. Tenga en cuenta que es posible que necesite anular fetch () además de XMLHttpRequest. la limitación es que las solicitudes del navegador a js / images / css no se interceptan.
user861746
27

Acabo de lanzar una extensión de Devtools que hace precisamente eso :)

Se llama tamper, se basa en mitmproxy y le permite ver todas las solicitudes realizadas por la pestaña actual, modificarlas y entregar la versión modificada la próxima vez que actualice.

Es una versión bastante temprana, pero debería ser compatible con OS X y Windows. Avísame si no te funciona.

Puedes conseguirlo aquí http://dutzi.github.io/tamper/

Como funciona esto

Como @Xan comentó a continuación, la extensión se comunica a través de la mensajería nativa con un script de Python que extiende mitmproxy .

La extensión enumera todas las solicitudes que utilizan chrome.devtools.network.onRequestFinished.

Cuando haces clic en una de las solicitudes, descarga su respuesta utilizando el getContent()método del objeto de solicitud y luego envía esa respuesta al script de Python que la guarda localmente.

Luego abre el archivo en un editor (usando callpara OSX o subprocess.Popenpara Windows).

La secuencia de comandos de Python usa mitmproxy para escuchar toda la comunicación realizada a través de ese proxy, si detecta una solicitud de un archivo que se guardó, sirve el archivo que se guardó en su lugar.

Usé la API de proxy de Chrome (específicamente chrome.proxy.settings.set()) para configurar un PAC como configuración de proxy. Ese archivo PAC redirige toda la comunicación al proxy del script de Python.

Una de las mejores cosas de mitmproxy es que también puede modificar la comunicación HTTP. Entonces tienes eso también :)

dutzi
fuente
Solución interesante, aunque requiere un módulo de host nativo.
Xan
5
Por cierto, ayudaría si explicaras mejor la técnica utilizada aquí.
Xan
9
interesante extensión Devtools! Sin embargo, parece que solo se pueden modificar los encabezados de respuesta y no el cuerpo de respuesta con Tamper.
Michael Trouw
3
Problema con la instalación.
Dmitry Pleshkov
1
¿Podemos modificar el cuerpo de respuesta con esto?
Kiran
17

Si. Es posible con la chrome.debuggerAPI, que otorga acceso de extensión al Protocolo Chrome DevTools , que admite la interceptación y modificación HTTP a través de su API de red .

Esta solución fue sugerida por un comentario sobre el problema de Chrome 487422 :

Para cualquiera que desee una alternativa que sea factible en este momento, puede usar chrome.debuggeren una página de fondo / evento para adjuntar a la pestaña específica que desea escuchar (o adjuntar a todas las pestañas si es posible, no he probado todas las pestañas personalmente) , luego use la API de red del protocolo de depuración.

El único problema con esto es que habrá la barra amarilla habitual en la parte superior de la ventana gráfica de la pestaña, a menos que el usuario la apague chrome://flags.

Primero, adjunte un depurador al destino:

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

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

A continuación, envíe el Network.setRequestInterceptionEnabledcomando, que permitirá la interceptación de solicitudes de red:

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 ahora comenzará a enviar Network.requestInterceptedeventos. Agrega un oyente para ellos:

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

En el oyente, params.requestestará el Requestobjeto correspondiente .

Envía la respuesta con Network.continueInterceptedRequest:

  • Pase una codificación base64 de su respuesta HTTP sin procesar deseada ( incluida la línea de estado HTTP, encabezados, etc. ) como rawResponse.
  • Pasa params.interceptionIdcomo interceptionId.

Tenga en cuenta que no he probado nada de esto, en absoluto.

MultiplyByZer0
fuente
Parece muy prometedor, aunque lo estoy probando ahora (Chrome 60) y me falta algo o todavía no es posible; el setRequestInterceptionEnabledmétodo parece no estar incluido en el protocolo DevTools v1.2, y no puedo encontrar una manera de adjuntarlo con la última versión (punta del árbol) en su lugar.
Aioros
1
Probé esta solución y funcionó hasta cierto punto. Si desea modificar la solicitud, esta solución es buena. Si desea modificar la respuesta en función de la respuesta devuelta por el servidor, no hay forma. no hay respuesta en ese momento. de porque puede sobrescribir el campo rawresponse como dijo el autor.
user861746
1
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });falla con 'Network.setRequestInterceptionEnabled' no se encontró '
Pacerier
1
@ MultiplyByZer0, Ok logró que funcione. i.stack.imgur.com/n0Gff.png Sin embargo, la necesidad de eliminar la 'barra superior', es decir, la necesidad de configurar el indicador del navegador seguido de un reinicio del navegador, significa que se necesita una solución real alternativa.
Pacerier
@Pacerier Tienes razón en que esta solución no es ideal, pero no conozco ninguna mejor. Además, como habrá notado, esta respuesta está incompleta y debe actualizarse. Lo haré pronto, con suerte.
MultiplyByZer0
12

Como dijo @Rob w, he anulado XMLHttpRequesty este es el resultado de la modificación de cualquier solicitud XHR en cualquier sitio (que funciona como un proxy de modificación transparente):

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

por ejemplo, este código puede ser utilizado con éxito por Tampermonkey para realizar modificaciones en cualquier sitio :)

MixerOID
fuente
1
Usé su código y registró el capturado en la consola, pero no cambió la respuesta que obtuvo mi aplicación (Angular).
André Roggeri Campos
2
@ AndréRoggeriCampos Me encontré con lo mismo. Angular usa el responsemás nuevo en lugar de responseText, por lo que todo lo que tiene que hacer es cambiar Object.defineProperty para usar responseen su lugar
Jonathan Gawrych
muchas gracias ! ¡Funciona en mi extensión de Chrome!
Lancer.Yan