«Консультант» и IE
Вчера на «Хабре» люди в очередной раз подняли тему сайта «Консультанта» и Internet Explorer — первый, своей интересной частью, работает только на втором. Как я и ожидал, код завязан на особенности Internet Explorer, браузер богатый на возможности, на сайте использовано их немало. Впрочем, всё, если вдуматься, переводится и на более стандартные рельсы.
У меня давно руки чесались разобраться поподробнее как устроены userjs в «Опере» и посмотреть на отличия реализации XSLT-процессинга в IE и остальных браузерах. Userjs в «Опере» устроены исключительно удобно (даже предусмотрели патчи к выполняемому коду), а с XSLT в IE нестандартно, зато много проще.
По результатам появился userjs, который позволяет увидеть две первые страницы базы «Консультанта» в «Опере», если кому-нибудь хочется, допилите его дальше, я это делал исключительно из интереса научиться полезному, сама база мне без надобности.
// ==Userscript==
// @name Make Consultant base works
// @namespace /
// @description Make Consultant base works in non-IE browsers.
// @include http://base.consultant.ru/*
// @version 1.0
// @licence MIT
// ==/UserScript==
(function(){
/* TODO
saveCard — это behavior userData, сделать эмуляцию
VBArray — одна из переменных использует этот специфический массив
isBaseVisible, ForEachRow — убрать заглушку, сделать нормальную функцию
showModalDialog — дописать с передачей параметров (например, через window.name)
*/
document.addEventListener('load', function (e) {
showModalDialog = window.open;
return true;
}, false);
DOMParser.prototype.loadXML = function (text) {
return this.parseFromString(text,"text/xml")
}
opera.addEventListener('BeforeScript',
function(e){
if (e.element.getAttribute('for')) {
var script = e.element.text
var id = e.element.getAttribute('for')
var ev = e.element.getAttribute('event')
e.element.text = ''
document.addEventListener('load', new Function('',
'document.getElementById("' + id +
'").attachEvent("' + ev +
'", function () {'+ script + '})'
))
}
var repl = {
'document\\.body\\.attachEvent' : 'document.attachEvent',
'document\\.all\\.fieldsXML\\.XMLDocument\\.transformNode': 'document.transformNode',
'document\\.all' : 'document.getElementsByTagName("*")',
'document\\.frames\\(' : 'document.getElementsByTagName("iframe").item(',
'\\.XMLDocument\\.text' : '.textContent',
'document\\.body' : 'document.documentElement',
'\\.parentElement' : '.parentNode',
'\\.XMLDocument\\.documentElement': '',
'\\.XMLDocument' : '',
'basesRow\\.all\\.basesTable' : 'document.getElementById("basesTable")',
'\\.all\\.tags\\(' : '.getElementsByTagName(',
'\\.all\\.([^\\.;\\s]+)' : '.getElementById("$1")',
'\\.all\\(' : '.getElementById(',
'\\w+\\.getElementById' : 'document.getElementById',
'p\\.type\\.' : 'p.getAttribute("type").',
'\\.isEnabled\\s*=([^;]+)' : '.setAttribute("isEnabled", $1)',
'\\.isEnabled' : '.getAttribute("isEnabled")',
'(\\w+)\\.loadXML' : '$1=$1.loadXML',
}
for (var re in repl) {
e.element.text = e.element.text.replace(new RegExp(re, 'g'), repl[re])
}
}
,false);
opera.defineMagicFunction('writeInsertion', function () {});
opera.defineMagicFunction('getBasesTable', function (a, b, tDivRow) {
var basesRow = tDivRow && tDivRow.id ? document.getElementById("bases_" + tDivRow.id) : null;
return basesRow == null ? null : document.getElementById('basesTable');
})
opera.defineMagicFunction('forEachRow', function (a, b) { return true })
opera.defineMagicFunction('createXMLStylesheet', function (a, b, url) {
var r = new XMLHttpRequest()
r.open('GET', url, false)
r.send(null)
if (r.status == 200) {
var xml = new DOMParser()
return xml.parseFromString(r.responseText, "text/xml")
}
return null
})
opera.defineMagicFunction('isBaseVisible', function (a, b, code) {
return true
})
HTMLElement.prototype.transformNode = XMLDocument.prototype.transformNode = function (style) {
var xsltProcessor=new XSLTProcessor();
xsltProcessor.importStylesheet(style);
if (typeof this == 'HTMLElement') {
var resultDocument = (new DOMParser()).parseFromString(this.innerHTML, 'text/xml');
} else {
var resultDocument = xsltProcessor.transformToDocument(this);
}
var xmls = new XMLSerializer();
return xmls.serializeToString(resultDocument);
}
HTMLHtmlElement.prototype.selectSingleNode = HTMLInputElement.prototype.selectSingleNode = function (path) {
var xpe = new XPathEvaluator();
var nsResolver = xpe.createNSResolver(this.ownerDocument == null ? this.documentElement : this.ownerDocument.documentElement);
var results = xpe.evaluate(path,this,nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return results.singleNodeValue;
}
ActiveXObject = function (str) {
return new ({
'Microsoft.XMLDOM': DOMParser,
'Microsoft.XMLHTTP': XMLHttpRequest,
'Scripting.Dictionary': Array,
})[str]
}
})()
Кстати, «Опере» нужно не забыть сказать, чтобы она маскировалась под Internet Explorer (правая клавиша мыши, «Настройки сайта», закладка «Сеть»).
Обращаюсь к тем, кто читает RSS и трансляцию, текст userjs по ссылке на исходный пост.
Если бы ты разместил ссылку на скрипт, а не его текст, установка с помошью нового UJS менеджера сильно упростилась бы.
Комментарий для j-raf.livejournal.com:
А зачем его устанавливать? Он же не решает проблему, а иллюстрирует принцип. Я «поборол» две первых страницы, а самое интересное — третья, на ней результирующая информация.
Я в своё время подобным боролся с IE-only сайтом, на котором активно использовались модальные диалоги. Самое весёлое было то, что несмотря на то, что мы предоставляли и продавали этот конструктор, доступа к исходником мы не имели.
Комментарий для kildor.ya.ru:
Я как-то давно патчил на сервере клиента какой-то фреймворк (или это даже была CMS) на PHP, где в языке шаблонов допускались вызовы PHP-кода, но все «небезопасные» функции доступа к файловой системе и СУБД были выключены (а так же eval, create_user_func и прочее).
Можно было менять только код шаблона (на остальное прав не хватало, кажется, или просто запретили). Чекер небезопасного кода был примитивный и, показало моё исследование, его можно было обмануть — протолкнуть параметры в небезопасные функции через callback функций обработки массивов, например, array_map.
Вот так я его и патчил — создал функцию для вызова чего угодно через array_map и пропатчил. Владельцам сайта потом объяснил в чём проблема и как я её решил.
Не знаю, входит ли API для XSLT преобразований в стандарты, но его хотя бы можно эмулировать. В отличие от disable-output-escaping в Firefox =(
Я юзал такую фишку на Microsoft Imagine Cup в номинации Visual Gaming. Суть была в написании библиотеки AI, управляющей командой роботов. Неприятность была в том, что симулятор давал управление AI только каждые 4 «такта», хотя некоторые действия вроде (постройки робота) занимали всего один. Использование тредов было запрещено и нельзя было перейти к слудующему ходу, не отдав управление обратно в программу-вимулятор. Было найдена такая лазейка: при некорректном ходе в лог добавлялась запись об ошибке. И у этого лога был callback, на который и был успешно повешен вызов AI. Строился даже специальный робот, который всегда делал некорректные ходы, обеспечивая вызовы AI каждый такт.
Американцы, видя (на реплеях), как в начале раунда с учетверённой скоростью валятся с потолка (строятся) новые роботы, дружно орали «Ultra Russian build exploit» в саппорт форумах =)
Комментарий для sad-wind.ya.ru:
Да, в стандартах есть API для XSLT.