title: Altering HTTP Responses in Firefox Extension
body: <p>How can I alter the HTTP response body in a Firefox extension? I have setup an http-on-examine-response observer and an nsIStreamListener object with the code below. After I get the data, parse it, and alter it, how do I push the altered response back to the firefox browser? For example, let's say I go to Google.com with my extension enabled, the extension should intercept the response and change every occurence of "google" to "goggle". So when the page is loaded, the user will see "goggle" everywhere.</p>
  
  <pre><code>function TmSteroidsObserver()
  {
    this.register();
  }
  
  
  TmSteroidsObserver.prototype = {
    observe: function(subject, topic, data) {
  
      if (topic == "http-on-examine-response") {
  
      }
      else if (topic == "http-on-modify-request") {
         var channel = subject.QueryInterface(Components.interfaces.nsIChannel);
         var listener = new StreamListener(channel);
      }
  
    },
  
    register: function() {
      var observerService = Components.classes["@mozilla.org/observer-service;1"]
                                  .getService(Components.interfaces.nsIObserverService);
      observerService.addObserver(listener, "http-on-modify-request", false);
      observerService.addObserver(listener, "http-on-examine-response", false);
    },
  
    unregister: function() {
      var observerService = Components.classes["@mozilla.org/observer-service;1"]
                              .getService(Components.interfaces.nsIObserverService);
      observerService.removeObserver(this, "http-on-modify-request");
      observerService.removeObserver(this, "http-on-examine-response");
    },
  
    QueryInterface : function(aIID) {
      if (aIID.equals(Components.interfaces.nsISupports) ||
          aIID.equals(Components.interfaces.nsIObserver))
        return this;
      throw Components.results.NS_NOINTERFACE;
    }
  
  }
  
  function StreamListener(channel) {
  
      channel.notificationCallbacks = listener;
      channel.asyncOpen(listener, null);
  
  }
  
  StreamListener.prototype = {
    mData: "",
    mChannel: null,
  
    // nsIStreamListener
    onStartRequest: function (aRequest, aContext) {
      this.mData = "";
    },
  
    onDataAvailable: function (aRequest, aContext, aStream, aSourceOffset, aLength) {
      var scriptableInputStream = 
        Components.classes["@mozilla.org/scriptableinputstream;1"]
          .createInstance(Components.interfaces.nsIScriptableInputStream);
      scriptableInputStream.init(aStream);
  
      this.mData += scriptableInputStream.read(aLength);
    },
  
    onStopRequest: function (aRequest, aContext, aStatus) {
      if (Components.isSuccessCode(aStatus)) {
        // request was successfull
        this.mCallbackFunc(this.mData);
      } else {
        // request failed
        this.mCallbackFunc(null);
      }
  
      this.mChannel = null;
    },
  
    // nsIChannelEventSink
    onChannelRedirect: function (aOldChannel, aNewChannel, aFlags) {
      // if redirecting, store the new channel
      this.mChannel = aNewChannel;
    },
  
    // nsIInterfaceRequestor
    getInterface: function (aIID) {
      try {
        return this.QueryInterface(aIID);
      } catch (e) {
        throw Components.results.NS_NOINTERFACE;
      }
    },
  
    // nsIProgressEventSink (not implementing will cause annoying exceptions)
    onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
    onStatus : function (aRequest, aContext, aStatus, aStatusArg) { },
  
    // nsIHttpEventSink (not implementing will cause annoying exceptions)
    onRedirect : function (aOldChannel, aNewChannel) { },
  
    // we are faking an XPCOM interface, so we need to implement QI
    QueryInterface : function(aIID) {
      if (aIID.equals(Components.interfaces.nsISupports) ||
          aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
          aIID.equals(Components.interfaces.nsIChannelEventSink) || 
          aIID.equals(Components.interfaces.nsIProgressEventSink) ||
          aIID.equals(Components.interfaces.nsIHttpEventSink) ||
          aIID.equals(Components.interfaces.nsIStreamListener))
        return this;
  
      throw Components.results.NS_NOINTERFACE;
    }
  };
  </code></pre>