Skip navigation.

miscoded

the web is a hack

Posts tagged with "browserjs"

Site patching updates

, ,

There's a new browser.js on the servers (a ua.ini update too), and I'll give you a quick changelog..

  • Rewritten Likno AllWebMenus patch. The version detection is improved (so now it's impossible or much harder to shoot yourself in the menu with a variable).
  • This rewritten AllWebMenus patch also fixes the worst AWM problem: a certain outdated version will go into a neverending loop, making Opera use 100% CPU. Not anymore!
  • Dropped FoldOutMenu patching after re-testing all reported sites - all but one had changed

The coolest thing about these changes is removing the busiest and probably most CPU-demanding part of browser.js, an AfterScript event listener that ran for all SCRIPT elements everywhere. With that CPU drain out of the way I'm pretty sure there is scope for some more ambitious patching.. stay tuned :smile:

It's interesting how quickly the web changes, and to what extent giant-dynamic-menus have gone out of fashion. While testing the removed FoldOutMenu and updated AWM patches I went through maybe 40 bug reports dealing with those scripts. The vast majority of sites no longer used them. Some had gone Flashy, but many had traded them for a much simpler and clearer navigation structure and several sites actually used little or no JavaScript for important site functionality like navigation. I'm sure usability experts and SEO advisors will fight to take credit for this trend :wink:

More changes..


Credit goes to fearphage for writing the patches and forum users for testing them - all my work done already :smile:

It's hard to believe though that bank sites - of all sites - create solutions that require a less secure browser than Opera. That's right, the problem was the banks' scripts violating Opera's security policy!!

The issue is that we allow no cross-domain communication at all on https unless domains match. So if https://a.example.com tries to change the address of a frame from https://b.example.com Opera stops that script. Turns out we're the only ones to do so, but we'd rather keep doing it and hope the other browsers will follow eventually. It keeps you safer :wink:.



I really should spend some more time here. Sorry to be such an irregular contributor there, only so many working hours in a day.. But now and then the forum brings up regulars - sites users have complained about and we have complained about for literally YEARS, and I look at it and go - yes, that's a very old annoyance - but we can patch it right now!

So TDWaterhouse, CVS.com - what a PITA you've been during those years. Anyway, let's remember that a new year offers us the possibility for a new start, and it's time for reconciliation and working together towards creating good user experiences. We're excited to give users access to their savings and spendings and look forward to rendering so far unknown frontiers of your carefully developed web properties. In other words - gotcha! :D

patching Adobe's hole

, ,

At this point most readers of this blog will have noticed the latest Adobe Acrobat security issue (see links at the bottom of this post if you haven't). In brief, an Acrobat PDF plugin feature lets an attacker run JavaScript of their choice on any site that hosts PDFs.

To put that into perspective, I am aware of several online banks that have a "mailbox" feature where you can see official letters from the bank to you in a PDF format. A malicious attacker could gain complete control of your online banking interface, for example inject malicious code to perform money transfer, by making you click a link of the form

https://examplebank.com/mailbox/letter1.pdf#arg=javascript:some_javascript_here

Even if you are technical enough to check that protocol and hostname of that link are OK, it is easy to overlook the abnormal and dangerous bit at the end. And it works in any browser using the NSAPI plugin - for example Opera and Firefox. This exploit is very dangerous indeed!

The blame and responsibility for fixing it lies with Adobe. Fortunately, they've resolved the issue in Acrobat Reader 8 - but most users are as we know slow at updating their systems..

So, I'm happy to announce that most cases of this exploit no longer work in Opera.

Yesterday we published a browser.js update for all public versions of Opera greater than 8.01 which disables the exploit for all files that have a .pdf extension. Normally, this update will be downloaded by all Opera installations within a week from now - to get it faster just choose "Help > Check for updates". You may see a message saying no updates are available but the new browser.js will have been downloaded.

I will work a bit more on the patch to see if I can write a version that gives complete protection also for files without .pdf in the address, but you're already much safer with PDF security patch version 1.

It's the first time we've published a generic security issue fix in browser.js. Hopefully it won't happen often but it is interesting to see site patching applied in the security domain.

More information about the security issue and its consequences:

Official advisory:
http://www.adobe.com/support/security/advisories/apsa07-01.html

Other related links:
http://www.gnucitizen.org/blog/danger-danger-danger/
http://www.securityfocus.com/brief/401?ref=rss
http://www.symantec.com/enterprise/security_response/weblog/2007/01/when_pdfs_attack.html
http://www.gnucitizen.org/blog/universal-pdf-xss-after-party/

And, in case you're curious this is the patch itself (in a somewhat more readable format than in browser.js itself):

opera.addEventListener('BeforeJavaScriptURL', function( e ){
var pathname=unescape(self.location.pathname.toLowerCase());
var hash=unescape(self.location.hash.toLowerCase());
if( pathname.indexOf('.pdf')>-1 && hash  &&  hash.indexOf('javascript:')>-1   ) e.preventDefault();
}, false);

removing patches is fun

,

I just finished a largeish review of the browser.js patches and apparently we can now remove 14 out of 70-something fixes, where sites have changed and fixed their problems. Dan might be happy to learn that his failed attempts at getting to New York at least has made the US Visa site Opera-compatible!!

Well done to the Web Openers.

Launching OddPatch 0.1 Beta

, , , ...


Launching in browser.js tomorrow or so: OddPatch beta, solving many of the compatibility issues with the service formerly known as OddPost, now Yahoo!Mail beta.

It's a complex patch sorting out a daunting number of issues on both sides. When things get this complex, it's rarely only "their fault" or "our fault" - the testing has uncovered several bugs in Opera and several mistakes in their JavaScript. Here's a walk-through of the entire patch:

// browser sniffing workaround - walking in through the back door
if( location.href.indexOf( '/dc/system_requirements?browser=blocked' ) >-1){
location.href='/dc/launch?sysreq=ignore';
}


Y!Mail has three modes for browsers: supported, possibly working, or blocked. Opera is blocked, but luckily there is a backdoor that will bypass the sniffing.

The irony here is that when they block us, they are making their work on Opera-compatibility much harder than necessary. If we get access, we'll do our best to make things work: test, find bugs, even decide to support things we haven't supported previously (we'll have selectSingleNode soon because Y!Mail uses it heavily, and their code was also a very important reason why DOM2 Style support was prioritised for Opera 9.0!). Blocking us makes it much harder for us to make their life simpler.

if( top.location.href.indexOf('/dc/launch')>-1 ){ 
// Gecko compatibility library uses defineGetter and defineSetter. We need to fake them.
//* Patch below is required but causes trouble.. 
Object.prototype.__defineGetter__=      function(){}
Object.prototype.__defineSetter__=      function(){}


This is known as "fake it until you make it". We won't have getters and setters anytime soon, but things will work anyway if we pretend we do.

// IEism called loadXML, basically a DOMParser / DOMLS equivalent
// must handle XML fragments without root element!
Element.prototype.loadXML=function(s){  
try{
var d=new DOMParser().parseFromString(s, 'text/xml'); 


This, I think, is a bit of the strange world IE lets you into if you put an XML tag in a page. That tag takes on a life of its own and starts behaving in some contexts like a document, it aquires several methods and properties - and though I at first thought I could simply fake it with a DOMParser I had to think again because...
}catch(e){ // DOMParser could not parse fragment, probably because of missing single root element. Workaround time..
var d=document.implementation.createDocument('', this.tagName, null), el=d.createElement('el');
//?? why did I use this.tagName there?
el.innerHTML=s;
for(var i=0 ; i<el.childNodes.length;i++){
d.appendChild(el.childNodes[i].cloneNode(true));
}
}


Yes, they are not always playing with well-formed XML fragments. Oh well, we'll pull out good-old-tagsoup-parsing .innerHTML and eat their strings anyway. Then we move on and fill in some other required bits and pieces of IE's XML DOM. Right now I'm not sure if all the stuff in this block is required, but there it is.. I'll be the first to admit that both code and comments are evidence of the somewhat chaotic process of late-night patching..

// faking IE-style XML element DOM - separate documents with documentElement within the main doc's DOM
this.documentElement=d.documentElement||d.firstChild; 
//?? firstChild is probably leftover from earlier versions using documentFragment?
this.XMLDocument=d;
// address book loading checks .parseError.errorCode
this.XMLDocument.parseError={ 'errorCode':0 };
return d;
}


And then is a peculiar mystery, who would ever need a function called isSameNode when == would presumably do the job?

// some method called isSameNode is called. Not sure where it comes from but simple enough to fake..
Element.prototype.isSameNode = function(n){
return n===this;
}


Here we go, more delicacies from IE's internals: the handy XPath method selectSingleNode. I have quite some reservations against loadXML and the other XML DOM stuff above, but selectSingleNode should be written into a standard as soon as possible because document.evaluate needs too many arguments for lazy JS coders and the returned object is too fiddly too.

// selectSingleNode support
var realSelectSingleNode=function( expr, resolver ){
var result=(this.ownerDocument?this.ownerDocument:this).evaluate( expr+'[1]', this, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null );
return ( result && result.snapshotLength ) ? result.snapshotItem(0) : null;
}
Node.prototype.selectSingleNode = function (expr, resolver)
{ 
  if (!resolver)
    if (this.nodeType == Node.DOCUMENT_NODE){
      resolver = document.createNSResolver (this.documentElement);
    }else if(this.nodeType == Node.ELEMENT_NODE && this.ownerDocument && this.ownerDocument.documentElement ){
      resolver = document.createNSResolver (this.ownerDocument.documentElement);
    }else{
resolver = document.createNSResolver (this);
    }
  return realSelectSingleNode.apply (this, [expr, resolver]);
}


Now, up till now the patching has been quite ordered. A patch is a bit of a kludge anyway, but so far it's been a nice kludge. Here come the problems that were too ugly for a nice kludge. Certain issues just required search and replace operations on the script source code, nothing else to do about them - and because Yahoo source is compressed and variable names random, search and replace must take that into account and go for seriously complicated and ugly regular expressions.

opera.addEventListener('BeforeScript', function(e){
// This is the riskiest patch
// Fixing typo: missing ' after attribute value
e.element.text=e.element.text.replace( /\):\(">"\)\),/, "):(\"'>\"))," );
e.element.text=e.element.text.replace( / id='_test_add_folder>/, " id='_test_add_folder'>" );


Yep, twice in their source code they say things like <tag class="foo> omitting the closing quote. That caused Opera to parse it as a text node instead of an element, meaning source code would appear here and there in the interface. Oops.

// WebForms2 problem: button attribute "action" is a URL in WF2
e.element.text=e.element.text.replace( /\.(action\b)/g, ".js$1" );


Specs and implementations collide again: if you set input.action to a value like 'markAsSpam' the WF2 spec means it will be resolved as a URL, so when the script reads it again it will see 'http://mail.yahoo.com/dc/markAsSpam' which is not at all what it expected.

// send button not working - attribute nodes must be in the document they will be used
e.element.text=e.element.text.replace( /(\w)\.setAttributeNode\((\w)\)/, "$1.setAttributeNode($1.ownerDocument.importNode($2, true))" );


Hm, is Firefox sloppy with exceptions on cross-document node usage again?

// workaround for getting the documentElement.xml  markup
e.element.text=e.element.text.replace( /(([\w\.]*)documentElement).xml/g, "(document.implementation.createLSSerializer()).writeToString($1)" );


IE's XML DOM rides again. Elements over there have an .xml property which is basically the equivalent of .innerHTML for an HTML element, showing the inner serialised markup.

I didn't take a long and hard look at how Y!Mail used it but some of that seemed very weird. I had the impression that they read .xml of the contacts list only to pass it around as a string and use loadXML later on. Why would they serialize markup just to parse it right into a DOM tree again? Oh well, there is probably some complex reason..

// for...in on objects run into our faked __defineGetter__ and __defineSetter__
// we try to add an exception to any for...in loops
e.element.text=e.element.text.replace( /(for\((var |)(\w*) in \w*\)\{)/g, "$1if($3.match(/^__define/))continue;" );


It turns out "fake it until you make it" wasn't such a good idea after all. The site called our bluff with code like
function foo(obj){
for( p in obj )return false; return true
}

var bar = {}; if(!foo(bar)) return;

and what exactly they meant by that I don't know either, except to check that a newly created object really REALLY had no properties. Huh?


// To: / CC: autocomplete fails
// we support IE's TEXTAREA.createTextRange but unfortunately not its boundingLeft property. Improving object detection..
if(e.element.src&&e.element.src.match(/ac\.js$/))e.element.text=e.element.text.replace( /if \( editCtrl\.createTextRange \)/, "if ( editCtrl.createTextRange  && editCtrl.createTextRange().boundingLeft )" );


This is a typical trap of piecemeal implementation of something: we support whatever of IE's stuff was deemed important to get some plaintext formatting JavaScript to work with 8.x. .boundingLeft wasn't on the list back then. Sorry. Look a bit harder when you look for something.

// Preferences not read correctly from XML attributes
// IE has an attribute node .text property. .nodeValue will work in Opera..
e.element.text=e.element.text.replace( /\.selectNodes\((\w*)\);\s*\}return\((\w*)\.length\)\?(\w*)\[0\]\.text:/g, ".selectNodes($1);}return($2.length)?$2[0].nodeValue:" );


IE again. text alias nodeValue, enough said.

// We throw an unwanted exception if both arguments to insertBefore are the same node
e.element.text=e.element.text.replace( /var (\w*)=(\w*)\?(\w*)\.nextSibling:(\w*)\.firstChild;\s*(\w*)\.insertBefore\((\w*),(\w*)\);/, "var $1=$2?$2.nextSibling:$4.firstChild;if($6!=$1) $4.insertBefore($6,$1);" );


Now this is plainly a bug. The DOM spec says we should throw an error if the node you insert is a parent of the reference child, but we also did so if the inserted node was the reference child itself.

The code doesn't make sense, mind you... Why do you want to replace an element with itself?

// Opera 9.00 and 9.01 has a bug that means createContextualFragment on table elements is unreliable
// easily the worst patch.. but then it works around a really tricky bug..
if( navigator.userAgent.indexOf('9.01')>-1 || navigator.userAgent.indexOf('9.00')>-1 ){ 
// UA detection to target specific bug in specific version is OK
e.element.text=e.element.text.replace( /(\b(\w*)\.selectNodeContents\((\w*)\);\s*var (\w*)=(\w*).createContextualFragment\((\w*)\))/, "if($3.tagName=='TBODY'||$3.tagName=='TR'){ $2.createContextualFragment=function(s){var n=s.match(/<(\\w*)/)[1]; var e=document.createElement('div');e.innerHTML='<table><tbody>'+s+'</tbody></table>';return e.getElementsByTagName(n)[0];  } }$1" ); 


Just as ugly as it looks, just an attempt to make the code work in 9.01.
Imagine if Yahoo!Mail blocked us until they one day decided that it was necessarily to start working on Opera compatibility? If we never had gotten to test their system, they would probably have to add such an ugly workaround to their application to get around this bug. Developers everywhere: please, don't sniff, just leave more of the burden of compatibility on the UA's table (and listen to feedback!).

Hey, we're done with the replacements! It wasn't pretty, and I look forward to deleting one by one while things are fixed on either side. That will also give us a nice performance lift. Their scripts are huge. At some point I did some profiling of the above replace calls and found that up to half of the time it took Opera to load Y!Mail was spent applying the above patches.

}
}, false)

// No scrollbars appear for message list..
// uses an "overflow" CSS property to control scrollbars. e.overflow="-moz-scrollbars-vertical", and some odd clipping as well..
document.addEventListener( 'load', function(){ setTimeout( function(){ var divs=document.getElementsByTagName('div');for(var i=0,div;div=divs[i];i++)if(div.className&&div.className.indexOf('fakeScrollBar')>-1){div.style.overflow='auto';div.style.clip='auto';}},500);}, false );


Yes. That problem. It uses some CSS I still haven't fully understood.. I think they wanted to show an element with a scroll bar but clip the whole element away so only the scroll bar would be visible.

// redraw problem hides To: field in compose screen
document.addEventListener( 'load', 
function(){ if(top.document.frames['newmessage']){
setTimeout( function(){try{top.document.frames['newmessage'].document.body.className+=' ';}catch(e){}},1000);
}}, true);


This is another tricky one, it has to do with timing and I still haven't quite captured the sequence of events. Basically the "To" and "Subject" fields in the compose screen disappear until you click "Show BCC".

// sluggish performance due to unintended event capture
(function(ael){ 
window.addEventListener = function(type, func, capture){ ael.call(window, type, func, false); }
})(window.addEventListener);


..and just to top if off, they had to capture events by mistake. Of course. That's from the curriculum of "How to code Opera-incompatible websites 101".

opera.postError( 'Yahoo mail patched' );
}
}


Yippee! We did it!

Now, this patch is (repeat after me) in beta! There are problems that I'm aware of but haven't fixed, and there are problems that I'm not aware of and haven't fixed. And while I was working on this stuff, code changes in Yahoo mail would break things again every few days (and even break differently on the U.S. and the U.K. sites!). So, hurry up and try it while you have a chance! I'll try to keep the patch maintained, and we sure hope that we'll get all the issues sorted out from either side for a fast, friendly, responsive experience - somewhere in the future..

how to shoot yourself in the menu with a variable

, , , ...

Opera uses the Browser JavaScript feature to automatically fix older scripts that otherwise would not run correctly.

One of the interesting challenges of doing that is to work out how, exactly, browser.js can tell when Opera is about to run an "outdated" script that needs fixing? Because if we apply the fix to a modern script, we might break things instead of fixing them..

Until today I thought the AllWebMenus fix was one of the safest in the book. It takes advantage of the fact that every AWM script contains a "build number" variable showing what version the script is. This variable is not even part of the comments where webmasters might remove it, it is right there embedded in the script. It is also relatively unlikely (though not impossible) that other scripts would define a variable called awmBN with a value that would trigger the patch, and even more unlikely that the patch would break a script doing so.

OK, think again. Browser.js breaks the menu on Aquaria.info because the inline script says

var MenuLinkedBy='AllWebMenus [2]', awmBN='438';

while the script file the page links to actually reveals a much more recent build number:

//****** AllWebMenus Libraries Version # 626 ******

So the webmaster has changed the .js files to upgrade, but not bothered changing the script in the HTML file. Not such a safe patch after all..

Once I stop saying "d'oh" I'll have to figure out what to do about it.. :-(

Site patching works

, , , ...

Back then when we added the browser.js feature I heard some sceptical voices saying that fixing broken sites automatically was a bad idea because then the site had no incentive for fixing it themselves. The risk was that the web might become even more fragmented, with even worse examples of incompatible code because Opera would automagically fix things and cloak the faults of the webmasters.

Now call me an optimist, but we have about half a year's experience with browser.js and I'm seeing evidence of the opposite. Three good examples are allmusic.com, shockwave.com and atomfilms.com - they all had long-standing issues with Opera, they were patched successfully with browser.js and a few months after the patch, each site was fixed by the webmaster!

So perhaps, perhaps site patching does exactly what we hoped: increases Opera's ranking in site statistics by making previously unusable sites available to Opera users, thereby making webmasters more concerned about Opera compatibility (because such decisions are often based on browser stats) and eventually creating a more compatible web.

Of course it also helps that we always contact the website before or while we patch it.

It is no accident that browser.js is a simple text file written in readable and reasonably well commented JavaScript and that it always outputs some text in the JavaScript console when it does something. We could have done things differently, we considered pre-compiling the script somehow for performance - but in the end, it was most important to keep the whole feature as open for inspection as possible. And that pays off: we hear from web developers who sit down and read through the section of browser.js that is used on their website, for to-the-point, updated information about where in their site there are problems and scope for improvements. Thus browser.js itself becomes a way of communicating directly to the web developers we need to reach!

Hey, some of the fixes in browser.js can even be cut and pasted into the site to solve the problem :smile:

Yes, I think site patching works - and every patch I can remove from browser.js is a vote for that conclusion.

spot my browser.js bug

, ,

Usually I use this blog for opining on the quality of other people's and systems' code. Well, that's a bit one-way and biased, isn't it? Since I just found a bug in browser.js, written and maintained by yours truly, I'll sweep the street in front of my own door (as a Norwegian saying goes).

So, can you spot the broken logic in this browser.js excerpt?

if( hostname.indexOf('walla.co.il') ==hostname.length-11 ){
// Walla.co.il odd CSS styling causes display problems
// Bugs 184398, 184399
opera.addEventListener('BeforeEvent.load',
function(){ if(document.documentElement){(document.documentElement.appendChild(document.createElement('style'))).appendChild(document.createTextNode(' .btn-t,.btn{display:inline !important;} .wp-0-b{width:auto !important}'));
opera.removeEventListener('BeforeEvent.load', arguments.callee, false); 
opera.postError('Opera has modified the JavaScript on '+hostname+'. See browser.js for details');}}
,false);
}