Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to modify http response in Firefox extension

Hey i have been able to write an nsIStreamListener listener to listen on responses and get the response text following tutorials at nsitraceablechannel-intercept-http-traffic .But i am unable to modify the response sent to browser.Actually if i return the reponse and sent back to chain it reflects in firebug but not in browser.

What i am guessing is we will have to replace default listener rather than listening in the chain.I cant get any docs anywhere which explains how to do this.

Could anyone give me some insight into this.This is mainly for education purposes.

Thanks in advance

Edit : As of now i have arrived at a little solutions i am able to do this

var old;

function TracingListener() {}

TracingListener.prototype = {
    originalListener: null,
    receivedData: null, //will be an array for incoming data.

//For the listener this is step 1.
onStartRequest: function (request, context) {
    this.receivedData = []; //initialize the array

    //Pass on the onStartRequest call to the next listener in the chain -- VERY         IMPORTANT
    //old.onStartRequest(request, context); 
},

//This is step 2. This gets called every time additional data is available
onDataAvailable: function (request, context, inputStream, offset, count) {
    var binaryInputStream = CCIN("@mozilla.org/binaryinputstream;1",
        "nsIBinaryInputStream");
    binaryInputStream.setInputStream(inputStream);

    var storageStream = CCIN("@mozilla.org/storagestream;1",
        "nsIStorageStream");
    //8192 is the segment size in bytes, count is the maximum size of the stream in     bytes
    storageStream.init(8192, count, null);

    var binaryOutputStream = CCIN("@mozilla.org/binaryoutputstream;1",
        "nsIBinaryOutputStream");
    binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));

    // Copy received data as they come.
    var data = binaryInputStream.readBytes(count);

    this.receivedData.push(data);

    binaryOutputStream.writeBytes(data, count);



    //Pass it on down the chain
    //old.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count); 
},
onStopRequest: function (request, context, statusCode) {
    try {
        //QueryInterface into HttpChannel to access originalURI and requestMethod properties
        request.QueryInterface(Ci.nsIHttpChannel);


        //Combine the response into a single string
        var responseSource = this.receivedData.join('');


        //edit data as needed
        responseSource = "test";
        console.log(responseSource);

    } catch (e) {
        //standard function to dump a formatted version of the error to console
        dumpError(e);
    }

    var stream = Cc["@mozilla.org/io/string-input-stream;1"]
        .createInstance(Ci.nsIStringInputStream);
    stream.setData(responseSource, -1);

    //Pass it to the original listener
    //old.originalListener=null;
    old.onStartRequest(channel, context);
    old.onDataAvailable(channel, context, stream, 0, stream.available());
    old.onStopRequest(channel, context, statusCode);
},
QueryInterface: function (aIID) {
    if (aIID.equals(Ci.nsIStreamListener) ||
        aIID.equals(Ci.nsISupports)) {
        return this;
    }
    throw components.results.NS_NOINTERFACE;
},
readPostTextFromRequest: function (request, context) {
    try {
        var is = request.QueryInterface(Ci.nsIUploadChannel).uploadStream;
        if (is) {
            var ss = is.QueryInterface(Ci.nsISeekableStream);
            var prevOffset;
            if (ss) {
                prevOffset = ss.tell();
                ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
            }

            // Read data from the stream..
            var charset = "UTF-8";
            var text = this.readFromStream(is, charset, true);

            if (ss && prevOffset == 0)
                ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);

            return text;
        } else {
            dump("Failed to Query Interface for upload stream.\n");
        }
    } catch (exc) {
        dumpError(exc);
    }

    return null;
},
readFromStream: function (stream, charset, noClose) {

    var sis = CCSV("@mozilla.org/binaryinputstream;1",
        "nsIBinaryInputStream");
    sis.setInputStream(stream);

    var segments = [];
    for (var count = stream.available(); count; count = stream.available())
        segments.push(sis.readBytes(count));

    if (!noClose)
        sis.close();

   var text = segments.join("");
    return text;
}

}

httpRequestObserver = {

observe: function (request, aTopic, aData) {
    if (typeof Cc == "undefined") {
        var Cc = components.classes;
    }
    if (typeof Ci == "undefined") {
        var Ci = components.interfaces;
    }
    if (aTopic == "http-on-examine-response") {
        request.QueryInterface(Ci.nsIHttpChannel);

        console.log(request.statusCode);

        var newListener = new TracingListener();
        request.QueryInterface(Ci.nsITraceableChannel);

        channel = request;
        //newListener.originalListener 
        //add new listener as default and save old one 
        old = request.setNewListener(newListener);
        old.originalListener = null;

        var threadManager = Cc["@mozilla.org/thread-manager;1"]
            .getService(Ci.nsIThreadManager);
        threadManager.currentThread.dispatch(newListener,     Ci.nsIEventTarget.DISPATCH_NORMAL);


    }
},

QueryInterface: function (aIID) {
    if (typeof Cc == "undefined") {
        var Cc = components.classes;
    }
    if (typeof Ci == "undefined") {
        var Ci = components.interfaces;
    }
    if (aIID.equals(Ci.nsIObserver) ||
        aIID.equals(Ci.nsISupports)) {
        return this;
    }

    throw components.results.NS_NOINTERFACE;

},
};

var observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);

observerService.addObserver(httpRequestObserver,
"http-on-examine-response", false);
like image 606
Jiby Jose Avatar asked Apr 17 '13 19:04

Jiby Jose


1 Answers

This example works for me on Firefox 34 (current nightly): https://github.com/Noitidart/demo-nsITraceableChannel

I downloaded the xpi, edited bootstrap.js to modify the stream:

132         // Copy received data as they come.
133         var data = binaryInputStream.readBytes(count);
134         data = data.replace(/GitHub/g, "TEST");
135         this.receivedData.push(data);

installed the XPI then reloaded the github page. It read "TEST" in the footer.

The version of code you posted doesn't actually pass the results back to the old listener, so that's the first thing that ought to be changed.

It also may have interacted with Firebug or another extension badly. It's a good idea to try reproducing the problem in a clean profile (with only your extension installed).

like image 106
Nickolay Avatar answered Oct 15 '22 12:10

Nickolay