Talk:Location hack: Difference between revisions

From GreaseSpot Wiki
Jump to navigationJump to search
Fromp (talk | contribs)
No edit summary
m Reverted edits by 46.17.96.204 (talk) to last revision by Arantius
 
(31 intermediate revisions by 12 users not shown)
Line 1: Line 1:
== GM_eval vs. eval(s, unsafeWindow) vs. LocationHack ==
== GM_eval vs. eval(s, unsafeWindow) vs. LocationHack ==


JavaScript in Mozilla/Gecko/Firefox is currently implemented by the [http://developer.mozilla.org/en/docs/SpiderMonkey SpiderMonkey engine written in C]. It implements an [http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:eval eval] function that takes an optional second argument to give the scope or context for the <code>eval</code>.
<code>GM_eval()</code> will not be implemented.
eval(string[, object])
It seems the two-argument <code>eval</code> can do all or more than the ''location-hack''.


The main question is how secure it is in terms of giving a web page control over a browser. (Note that there are problems with this issue but particulars are purposefully rarely mentioned).
# [https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Functions/eval#Cross-implementation_compatibility The second argument of eval was removed].
I can't see how a JS statement like the following could leak any GM access to a web page.
# Running <code>eval()</code> in chrome scope is a very dangerous thing to do.
eval(s, unsafeWindow);
# I'm not confident that it actually worked to solve the problems that the location hack does. (I'm confident that it does not, on FF 3.0.13 on Linux.  Perhaps it did in older versions, before it was removed.)
But then, the following might be unsafe, particularly if <code>r</code> is later stored somewhere or more particularly if <code>r</code> gets some methods invoked off of it:
  var r = eval(s, unsafeWindow);


The obvious solution to make <code>r</code> safe from potential terrors is to wrap it in a [http://developer.mozilla.org/en/docs/XPCNativeWrapper XPCNativeWrapper] just like GreaseMonkey does.
== Location Hack Broken ==
r = new XPCNativeWrapper(eval(s, unsafeWindow));


=== Objections to: eval(s, unsafeWindow) ===
-Removed-
* It is not as obfuscated as sending the string <code>'javascript:...'</code> to <code>location.href</code> and is thus not as '''cool'''.
* 2-argument <code>eval</code> is not standard JavaScript
** While the '''true GreaseMonkey''' is based on Mozilla code, there are several other implementations for different browsers with different script engines, and those might not implement 2-argument <code>eval</code>.
** Mozilla JavaScript is considering and may migrate to to standards such as [http://developer.mozilla.org/en/docs/E4X ECMAScript for XML (E4X)] and/or go to the open source [http://www.mozilla.org/projects/tamarin/ Tamerin engine] which Adobe wants. These new versions of JavaScript might not implement 2-argument eval (though I doubt it).
* Script writers who directly use <code>eval</code> may break security unless they take the extra step to wrap the result.


=== Advantages of: eval(s, unsafeWindow) ===
Fromp: You are confused about what the location hack is supposed to accomplish.
* It is less verbose than the location hack. You don't have to wrap strings in things like
It's for letting a user script call into a function (for example) defined in the page.
javascript:void(...)
Your example which I just removed was doing the opposite.
* It permits returning a value in a way that the target page cannot detect.
As written that example should never have worked.
* You don't have to worry whether you need to  [http://wiki.greasespot.net/index.php?title=Location_hack&action=submit#Percent_encoding_issue encode special characters] like with the ''location-hack''.
[[User:Arantius|Arantius]] 12:41, 16 September 2009 (EDT)


=== GM_eval ===
== Polling ==
To resolve this distinction I propose that the GM API include the function GM_eval. In Mozilla GreaseMonkey it is just defined as something like
function GM_eval (string) {return new XPCNativeWrapper(eval(string, unsafeWindow));
For other browsers/script-engines that don't have 2-argument eval one might want to make it a 2-argument function:
GM_eval(string, [boolean]);
that uses the ''location-hack'' and wraps and encodes the string appropriatly. If the second argument is ''true'' then that means the function should use some detectable means to record the result inside the target document and return it or just possibly complain with a security error.
==== Advantages: ====
* Using <code>GM_eval</code> semantically indicates that you are safely evaluating something on the target page  as opposed to going through some hack back door.
* You don't need to extra wrap or encode things like for the ''location-hack''.
* <code>GM_eval</code> would really simplify the documentation of GreaseMonkey. For example this page would disappear. There would be one page on <code>GM_eval</code> that explains what it means and how it can be imlemented through <code>eval</code> or the ''location-hack'', and most of the other stuff  on this page could go into "code snippits". [Though I have noticed some other references to ''location-hack'' that should be cleaned up]
==== Disadvantages ====
None seen so far other than increasing the API count by one.


== Location Hack Broken ==
[[User:Arantius|Arantius]] has removed the following passage that I had added:


The location hack seems to be broken on Firefox 3.0+. Use the following script as an example:
: Whoops, true that I missed adding a comment there.  But it was because I created [[Reading Content Globals]] to contain this topic (and added it to the "see also" section).  It works consistently, and well, and does not involve polling.  [[User:Arantius|Arantius]] 13:17, 27 September 2010 (UTC)
: It is possible that the location.href code is excuted (much) later than the readout code in the following lines. This breaks the location hack. (Observed in Firefox 3.6.8)


function test(j)
This happened with Firefox 3.6.8 and broke the Wikipedia editor [http://en.wikipedia.org/wiki/User:Cacycle/wikEd wikEd]. The only work around was to poll the location hack element after having set location.href. Independent of the poll delay time the results arrives usually with the 1st or 2nd poll. I have to apply the location hack during the page loading period before the load event fires. Event triggering instead of polling was not possible because during the page loading none of the tested events fired (e.g. click) (Arantius also reverted that remark [http://wiki.greasespot.net/index.php?title=Generate_Click_Events&curid=1943&diff=5592&oldid=5591]). Please see the current working code and testcase below. [[User:Cacycle|Cacycle]] 00:53, 5 September 2010 (UTC)
{
alert(j);
}
var j = document.createElement('div');
j.innerHTML = "Click me!";
j.setAttribute('onclick', 'javascript:location.href=void( test("sdf") )');


<pre>// ==UserScript==
// @name        location hack polling
// @include    *wiki*
// ==/UserScript==


document.getElementsByTagName('body')[0].insertBefore(j, document.getElementsByTagName('body')[0].firstChild);
window.Polling = function() {
</code>
  if (globalNode.value != '') {
    GM_log(globalNode.value);
  }
  else {
    GM_log('polling');
    setTimeout(Polling, 10);
  }
}


You should get "test is not defined".
window.globalNode = document.createElement('textarea');
globalNode.id = 'globalNode'
document.body.appendChild(globalNode);
location.href = 'javascript:document.getElementById(\'globalNode\').value = skin; void(0);';
Polling();
</pre>


== Alternate solution sketch for reading page scope variables ==


'''EDIT:'''
While I don't know why Arantius removed the location hack helper (even the tersest change message would be useful), I can guess that it wasn't a dependable solution.
 
This code works:
 
function test(j)
{
alert(j);
}
var j = document.createElement('div');
j.innerHTML = "Click me!";


j.addEventListener('click', function(e){ test('asdf'); }, false);
I think some asynchronous solution might be more backwards-and-forwards compatible and dependable, even though the API is less friendly to javascript beginners, and the code size explodes. Maybe something like this (untested):


document.getElementsByTagName('body')[0].insertBefore(j, document.getElementsByTagName('body')[0].firstChild);
// Query page javascript for the identifier "name", and call callback(value),
// when found, or undefined, if not found or some error occurred. This works
// only for values that can be JSON serialized -- numbers, strings, booleans,
// null, or nested structures like Arrays and Objects that only contain above
// mentioned types of data.
function queryContentVar(name, callback) {
  // makes a random 20-char lowercase id
  function random() {
    var rand = '';
    while (rand.length < 20)
      rand += String.fromCharCode(97 + Math.random() * 26 | 0);
    return rand;
  }
  // Creates a mostly anonymous <meta> tag in the <head> section, giving it a
  // secret id we'll use for messaging back and forth with content space, and
  // fires pageInit() in the page scope (to listen and respond to queries from
  // us about variables) and registers reply() for answers from it at this end.
  function privInit() {
    var node = document.createElement('meta'), secret,
        head = document.getElementsByTagName('head')[0];
    node.id = secret = random();
    node.addEventListener(id, reply, false);
    head.appendChild(node);
    location.href = 'javascript:('+ pageInit +')('+ JSON.stringify(secret) +')';
    return node;
  }
  // Grabs the meta tag, anonymizes it and listens for incoming queries from us.
  function pageInit(secret) {
    // Replaces given <meta content="variable.name"> with <meta content="value">
    // (after JSON encoding it) and then alerts the caller to grab the response.
    function reply() {
      var name = node.getAttribute('content'),
          mesg = document.createEvent('Events'),
          val;
      try {
        val = eval(node.getAttribute('content'));
        val = JSON.stringify(val);
      } catch(e) { val = ''; }
      node.setAttribute('content', val);
      mesg.initEvent(secret, true, false);
      node.dispatchEvent(mesg);
    }
    var node = document.getElementById(secret);
    node.addEventListener(secret + '?', reply, false);
    node.removeAttribute('id');
  }
  var self = queryContentVar,
      node = self.node = self.node || privInit(),
        id = self.secret = self.secret || node.id,
      mesg = document.createEvent('Events');
  mesg.initEvent(secret + '?', true, false);
  node.setAttribute('content', name);
  self.callback = callback;
  node.dispatchEvent(mesg);
  // Picks up the response from content space, decodes it, passes it on to given
  // response callback and resets everything (just in case) to handle new calls.
  function reply() {
    var cb = self.callback,
        is = node.getAttribute('content');
    if (cb) cb(is === '' ? undefined : JSON.parse(is));
    node.removeAttribute('name');
    delete self.callback;
  }
}


'''@Fromp''' Would you post your user agent for confirmation please? [[User:Marti|Marti]] 16:24, 3 May 2009 (EDT)


'''@Martin''' User agent: Firefox 3.0.9 Ubuntu Jaunty. [[User:Fromp|Fromp]] 23:51, 5 May 2009 (EDT)
--[[User:Ecmanaut|Ecmanaut]] 23:58, 26 September 2010 (UTC)

Latest revision as of 13:22, 22 March 2012

GM_eval vs. eval(s, unsafeWindow) vs. LocationHack

GM_eval() will not be implemented.

  1. The second argument of eval was removed.
  2. Running eval() in chrome scope is a very dangerous thing to do.
  3. I'm not confident that it actually worked to solve the problems that the location hack does. (I'm confident that it does not, on FF 3.0.13 on Linux. Perhaps it did in older versions, before it was removed.)

Location Hack Broken

-Removed-

Fromp: You are confused about what the location hack is supposed to accomplish. It's for letting a user script call into a function (for example) defined in the page. Your example which I just removed was doing the opposite. As written that example should never have worked. Arantius 12:41, 16 September 2009 (EDT)

Polling

Arantius has removed the following passage that I had added:

Whoops, true that I missed adding a comment there. But it was because I created Reading Content Globals to contain this topic (and added it to the "see also" section). It works consistently, and well, and does not involve polling. Arantius 13:17, 27 September 2010 (UTC)
It is possible that the location.href code is excuted (much) later than the readout code in the following lines. This breaks the location hack. (Observed in Firefox 3.6.8)

This happened with Firefox 3.6.8 and broke the Wikipedia editor wikEd. The only work around was to poll the location hack element after having set location.href. Independent of the poll delay time the results arrives usually with the 1st or 2nd poll. I have to apply the location hack during the page loading period before the load event fires. Event triggering instead of polling was not possible because during the page loading none of the tested events fired (e.g. click) (Arantius also reverted that remark [1]). Please see the current working code and testcase below. Cacycle 00:53, 5 September 2010 (UTC)

// ==UserScript==
// @name        location hack polling
// @include     *wiki*
// ==/UserScript==

window.Polling = function() {
  if (globalNode.value != '') {
    GM_log(globalNode.value);
  }
  else {
    GM_log('polling');
    setTimeout(Polling, 10);
  }
}

window.globalNode = document.createElement('textarea');
globalNode.id = 'globalNode'
document.body.appendChild(globalNode);
location.href = 'javascript:document.getElementById(\'globalNode\').value = skin; void(0);';
Polling();

Alternate solution sketch for reading page scope variables

While I don't know why Arantius removed the location hack helper (even the tersest change message would be useful), I can guess that it wasn't a dependable solution.

I think some asynchronous solution might be more backwards-and-forwards compatible and dependable, even though the API is less friendly to javascript beginners, and the code size explodes. Maybe something like this (untested):

// Query page javascript for the identifier "name", and call callback(value),
// when found, or undefined, if not found or some error occurred. This works
// only for values that can be JSON serialized -- numbers, strings, booleans,
// null, or nested structures like Arrays and Objects that only contain above
// mentioned types of data.
function queryContentVar(name, callback) {
  // makes a random 20-char lowercase id
  function random() {
    var rand = ;
    while (rand.length < 20)
      rand += String.fromCharCode(97 + Math.random() * 26 | 0);
    return rand;
  }

  // Creates a mostly anonymous <meta> tag in the <head> section, giving it a
  // secret id we'll use for messaging back and forth with content space, and
  // fires pageInit() in the page scope (to listen and respond to queries from
  // us about variables) and registers reply() for answers from it at this end.
  function privInit() {
    var node = document.createElement('meta'), secret,
        head = document.getElementsByTagName('head')[0];
    node.id = secret = random();
    node.addEventListener(id, reply, false);
    head.appendChild(node);
    location.href = 'javascript:('+ pageInit +')('+ JSON.stringify(secret) +')';
    return node;
  }

  // Grabs the meta tag, anonymizes it and listens for incoming queries from us.
  function pageInit(secret) {
    // Replaces given <meta content="variable.name"> with <meta content="value">
    // (after JSON encoding it) and then alerts the caller to grab the response.
    function reply() {
      var name = node.getAttribute('content'),
          mesg = document.createEvent('Events'),
          val;
      try {
        val = eval(node.getAttribute('content'));
        val = JSON.stringify(val);
      } catch(e) { val = ; }

      node.setAttribute('content', val);
      mesg.initEvent(secret, true, false);
      node.dispatchEvent(mesg);
    }

    var node = document.getElementById(secret);
    node.addEventListener(secret + '?', reply, false);
    node.removeAttribute('id');
  }

  var self = queryContentVar,
      node = self.node = self.node || privInit(),
        id = self.secret = self.secret || node.id,
      mesg = document.createEvent('Events');

  mesg.initEvent(secret + '?', true, false);
  node.setAttribute('content', name);
  self.callback = callback;
  node.dispatchEvent(mesg);

  // Picks up the response from content space, decodes it, passes it on to given
  // response callback and resets everything (just in case) to handle new calls.
  function reply() {
    var cb = self.callback,
        is = node.getAttribute('content');
    if (cb) cb(is ===  ? undefined : JSON.parse(is));
    node.removeAttribute('name');
    delete self.callback;
  }
}


--Ecmanaut 23:58, 26 September 2010 (UTC)