4

I'd like to create a Firefox add-on that rewrites certain URLs. For example, if the page attempts to load a file http://my.com/foo.js, I want to rewrite the URL so that it loads from http://my-other.org/bar.js. And I don't want it to look like a 302 redirect, because the request might have originated from an XHR call, and I don't want the browser to deny the request on a redirect on the grounds that it's cross-domain.

I've looked in the TamperData and HttpFox source code to learn how to solve this. I've tried creating an observer that listens for http-on-modify-request, and within the listener, attempts to rewrite the URL. The problem is that the nsiHttpChannel object seems to let me do just about everything - override headers, cancel the request, capture the response, etc - except override the URL. Simply changing the content of the originalURI and URI attributes of nsiHttpChannel doesn't seem to work either.

The only promising pointer I could find was to the HTTPS-everywhere addon, but the code there is rather intricate, and it feels like there ought to be a simpler way to achieve this.

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
M. A. Sridhar
  • 61
  • 2
  • 4
  • Greasemonkey and JavaScript, Greasemonkey also supports cross-domain AJAX calls. – Anders Sep 29 '11 at 12:37
  • @Anders - Perhaps I wasn't clear in my question. I would like to intercept **all** network requests, because I need to rewrite all requests to the URLs in question. My understanding is that if, for example, a page loads a JS file which in turn makes an Ajax call, that call cannot be caught by Greasemonkey - that's why I did not consider it. Am I mistaken? – M. A. Sridhar Sep 29 '11 at 14:53
  • The question http://stackoverflow.com/questions/3122854/how-to-replace-a-javascript-file-request-with-contents-from-another-file-in-a-ff seems to answer the same question... – Sai Prasad Jun 28 '12 at 15:51

2 Answers2

2

gliptak's answer is correct but now out of date.

I wrote a very simple Firefox addon that does what you describe. You may use its source as a reference.

// intercept and occasionally modify HTTP/HTTPS requests...
let httpObserver = {
    observe: function (aSubject, aTopic, aData) {
        aSubject.QueryInterface(Ci.nsIHttpChannel);
        let url = aSubject.URI.spec;
        let modUrl = modifyUrlIfNeeded(url);
        if (modUrl){
            // replace URL and referrer in request...
            let urlObj = ioService.newURI(modUrl, aSubject.URI.originCharset, null);
            aSubject.redirectTo(urlObj);
        }
    }
};
observerService.addObserver(httpObserver, "http-on-modify-request", false);

https://github.com/tristanisme/gahmm/blob/master/index.js

WoodenKitty
  • 6,521
  • 8
  • 53
  • 73
1

We can do this my overriding the nsiHttpChannel with a new one, doing this is slightly complicated but luckily the add-on https-everywhere implements this to force a https connection.

https-everywhere's source code is available here

Most of the code needed for this is in the files

IO Util.js ChannelReplacement.js (no longer in the source tree)

We can work with the above files alone provided we have the basic variables like Cc,Ci set up and the function xpcom_generateQI defined.

var httpRequestObserver =
{ 
  observe: function(subject, topic, data) {
    if (topic == "http-on-modify-request") {

        var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);     
        var requestURL = subject.URI.spec;

        if(isToBeReplaced(requestURL))  {

            var newURL = getURL(requestURL);        
             ChannelReplacement.runWhenPending(subject, function() {
                    var cr = new ChannelReplacement(subject, ch);
                    cr.replace(true,null);
                    cr.open();
                });
        }
    }

  },

  get observerService() {
    return Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  },

  register: function() {
    this.observerService.addObserver(this, "http-on-modify-request", false);

  },

  unregister: function() {
    this.observerService.removeObserver(this, "http-on-modify-request");

  }
};


httpRequestObserver.register();

The code will replace the request not redirect.

While I have tested the above code well enough, I am not sure about its implementation. As far I can make out, it copies all the attributes of the requested channel and sets them to the channel to be overridden. After which somehow the output requested by original request is supplied using the new channel.

P.S. I had seen a SO post in which this approach was suggested.

gliptak
  • 3,592
  • 2
  • 29
  • 61
mdprasadeng
  • 143
  • 1
  • 8