1

I need to call a cross domain GM_xmlhttpRequest which would be triggered by an event in the webpage.

How do I access the functions within the greasemonkey sandbox, from the webpage?

Rob W
  • 341,306
  • 83
  • 791
  • 678
hoshiKuzu
  • 866
  • 1
  • 12
  • 30
  • Yeah i don't think my use is "legitimate"! I was trying to look up google dictionary if a word is double clicked, and display it in a tooltip. But non-google sites wouldnt allow this. Such an extension exists for Chrome, but not for firefox, and since i have never made an extension before (nor do i know how to), i decided to do it with userscript. If u think this is legitimate, then im sorry :) – hoshiKuzu Mar 23 '12 at 22:14
  • You do not need to enable cross-domain XHR to do that. Your GM script would merely add a click handler and then parse mouse position or word selection as desired. No need to hack around sandbox security. – Brock Adams Mar 23 '12 at 22:33
  • but i dont want to open a new window. i want to diplay the definition in a tooltip – hoshiKuzu Mar 24 '12 at 11:25
  • No one mentioned anything about a new window. You can add "tooltips" all day without busting sandbox security. – Brock Adams Mar 24 '12 at 12:31

1 Answers1

1

To allow pages to use GM_xmlhttpRequest, you have to:

  1. Overwrite unsafeWindow.XMLHttpRequest with a custom method.
  2. Circumvent the security by detecting any desired requests via a poller, e.g. setInterval.

I have previously written such an User script for personal use, at the local protocol. Do not abuse it, and create very strict matching patterns to prevent exposing cross-site XHR to arbitrary websites.

The code does the following:

  • Move the original XMLHttpRequest object to unsafeWindow.GM_XHR.original.
  • To restore the original object, invoke GM_XHR.restore().
  • The XMLHttpRequest object is overwritten by a custom method, which adds any requests to an array queue.
  • A poller periodically checks the content of the queue, and performs all requests.

Cross-site XMLHttpRequest, greasemonkey userscript

// ==UserScript==
// @name           Cross-site XMLHttpRequest
// @namespace      Rob W
// @description    Enables cross-site XHR
// @include        file:///home/*
// ==/UserScript==

/*
 * http://www.w3.org/TR/XMLHttpRequest
 * http://wiki.greasespot.net/GM_xmlhttpRequest
 * https://developer.mozilla.org/en/XMLHttpRequest
 * 
 * https://developer.mozilla.org/En/nsIDOMProgressEvent
 * https://developer.mozilla.org/en/nsIChannel
 */

// Configuration. Allow paths via a RegExp:
var enabled = [
    "^file:///home/rob/Documenten/etc/",
    "^file:///home/rob/Documenten/test\.html$"
];

var XHR = {
    allowed: new RegExp(enabled.join("|")),
    allowed_atm: function(url){
        return XHR.allowed.test(url || location.protocol + "//" + location.pathname)
    },
    original: unsafeWindow.XMLHttpRequest,
    restore: function(){
        unsafeWindow.XMLHttpRequest = XHR.original;
    }
};
if (XHR.allowed_atm()) {
    // Request queue
    var reqs = [];

    // Inititate the actual gm_xmlhttpRequest.
    // Also define the `.abort()` method
    var do_xmlHttpRequest = function(details) {
        details.abort = GM_xmlhttpRequest(details).abort;
    }

    // Set poller, used to circumvent the security policy
    window.setInterval(function(){
        while(reqs.length) {
            do_xmlHttpRequest(reqs.shift());
        }
    }, 50);

    // unsafeWindow.XMLHttpRequest will be overwritten by:
    var xmlhttprequest = function() {
        var o = {headers: {}},
            t = this,
            sent = false,
            currentHeaders = "";
        
        t.channel = {
            name: ""
        };
        
        o.onprogress = function(r){
            if(r.lengthComputable) {
                t.channel.contentLength = r.total;
                currentHeaders = "Content-Length: " + r.total;
            }
            t.status = r.status;
            t.statusText = r.statusText;
            t.channel.name = r.finalUrl;
        };
        
        t.abort = function() {
            if(typeof o.abort == "function") o.abort();
            else t.onreadystatechange = null;
            sent = false;
        };
        t.getAllResponseHeaders = function() {
            return t.responseHeaders ? t.responseHeaders + (/(^|\n)Content-Length:\s?\d/i.test(t.responseHeaders)?"":"\n" + currentHeaders) : currentHeaders;
        };
        t.getResponseHeader = function(header) {
            console_log("Method not supported. getResponseHeader: " + header);
            return "";
        };
        t.open = function(method, url, async, user, password) {
            o.method = method;
            o.url = url;
            t.channel.name = url; //finalUrl?
            //o.synchronous = !async; //Not implemented for safety reasons
            if (typeof user != "undefined") o.user = user;
            if (typeof password != "undefined") o.password = password;
        };
        t.overrideMimeType = function(mimetype) {
            o.overrideMimeType = mimetype;
        };
        t.send = function(data){
            var readyState4reached = false;
            if (typeof t.onabort == "function") r.onabort = t.onabort;
            if (typeof t.onerror == "function") r.onerror = t.onerror;
            o.onload = function(r){
                o.onreadystatechange(r);
                if (typeof t.onload == "function") t.onload();
            };
            o.data = data;
            o.onreadystatechange = function(r) {
                t.channel.name = r.finalUrl;
                if (t.responseHeaders = r.responseHeaders) {
                    var tmp;
                    if(tmp = t.responseHeaders.match(/Content-Length:\s?(\d+)/i)) {
                        t.channel.contentLength = tmp[1];
                    }
                }
                t.readyState = r.readyState;
                t.responseText = r.responseText;
                t.status = r.status;
                t.statusText = r.statusText;
                if(readyState4reached) return;
                if(!readyState4reached && t.readyState == 4) readyState4reached = true;
                typeof t.onreadystatechange == "function" && t.onreadystatechange();
            }
            if(!sent) reqs.push(o);
            sent = true;
        };
        t.setRequestHeader = function(name, value) {
            o.headers[name] = value;
        };
    }
    /* Event binding */
    unsafeWindow.XMLHttpRequest = xmlhttprequest;
    unsafeWindow.GM_XHR = {original: XHR.original, restore: XHR.restore};

    // Log the exposed method in the console, to not forget about it:
    console.log("GM cross-site XHR activated at: " + location.href);
};
Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • What legitimate use does this have? Why not just have the GM script handle the cross-site requests without using such attempts to circumvent security? – Brock Adams Mar 23 '12 at 20:50
  • @BrockAdams The code here is the stripped version of my actual code. I used to use many home-made applications, which fetch cross-site XHRs. Since Firefox 4, it's not possible to use the `netscape.privilegeManager` to request permissions for cross-site XHR. That's why I created this user script: It enables cross-site XHR on any page, with a simple pattern. – Rob W Mar 23 '12 at 21:02
  • Yes, but why not just have the GM script do that, rather than creating a security hack that does not seem to be needed? I'm not questioning that this code works. It just appears to be a very convoluted way for a legitimate developer to do XSS. While being a nice recipe for black hats to compromise unsuspecting users. – Brock Adams Mar 23 '12 at 21:18
  • @BrockAdams I had several pages which were broken when I upgraded from FF 3.6. To enable cross-site XHR on the `file:` protocol, I had two choices: **1)** Create specific methods for *every* page, or **2)** Create a single, general-purpose, portable script, which enables cross-site XHR on the fly. The matching rules are defined in a RegExp instead of `@include`, because GreaseMonkey does not automatically update the inclusion rules when the source code is edited. – Rob W Mar 23 '12 at 21:24