Пишу, по большей части, про историю, свою жизнь и немного про программирование.

«Консультант» и 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 по ссылке на исходный пост.

7 комментариев
j-raf.livejournal.com 2009

Если бы ты разместил ссылку на скрипт, а не его текст, установка с помошью нового UJS менеджера сильно упростилась бы.

Евгений Степанищев (bolknote.ru) 2009

Комментарий для j-raf.livejournal.com:

А зачем его устанавливать? Он же не решает проблему, а иллюстрирует принцип. Я «поборол» две первых страницы, а самое интересное — третья, на ней результирующая информация.

kildor (kildor.ya.ru) 2009

Я в своё время подобным боролся с IE-only сайтом, на котором активно использовались модальные диалоги. Самое весёлое было то, что несмотря на то, что мы предоставляли и продавали этот конструктор, доступа к исходником мы не имели.

Евгений Степанищев (bolknote.ru) 2009

Комментарий для kildor.ya.ru:

Я как-то давно патчил на сервере клиента какой-то фреймворк (или это даже была CMS) на PHP, где в языке шаблонов допускались вызовы PHP-кода, но все «небезопасные» функции доступа к файловой системе и СУБД были выключены (а так же eval, create_user_func и прочее).

Можно было менять только код шаблона (на остальное прав не хватало, кажется, или просто запретили). Чекер небезопасного кода был примитивный и, показало моё исследование, его можно было обмануть — протолкнуть параметры в небезопасные функции через callback функций обработки массивов, например, array_map.

Вот так я его и патчил — создал функцию для вызова чего угодно через array_map и пропатчил. Владельцам сайта потом объяснил в чём проблема и как я её решил.

Ной (sad-wind.ya.ru) 2009

с XSLT в IE нестандартно

Не знаю, входит ли API для XSLT преобразований в стандарты, но его хотя бы можно эмулировать. В отличие от disable-output-escaping в Firefox =(

Ной (sad-wind.ya.ru) 2009

протолкнуть параметры в небезопасные функции через callback функций

Я юзал такую фишку на Microsoft Imagine Cup в номинации Visual Gaming. Суть была в написании библиотеки AI, управляющей командой роботов. Неприятность была в том, что симулятор давал управление AI только каждые 4 «такта», хотя некоторые действия вроде (постройки робота) занимали всего один. Использование тредов было запрещено и нельзя было перейти к слудующему ходу, не отдав управление обратно в программу-вимулятор. Было найдена такая лазейка: при некорректном ходе в лог добавлялась запись об ошибке. И у этого лога был callback, на который и был успешно повешен вызов AI. Строился даже специальный робот, который всегда делал некорректные ходы, обеспечивая вызовы AI каждый такт.
Американцы, видя (на реплеях), как в начале раунда с учетверённой скоростью валятся с потолка (строятся) новые роботы, дружно орали «Ultra Russian build exploit» в саппорт форумах =)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для sad-wind.ya.ru:

Не знаю, входит ли API для XSLT преобразований в стандарты, но его хотя бы можно эмулировать. В отличие от disable-output-escaping в Firefox =(

Да, в стандартах есть API для XSLT.