pasaran / yate

Yet Another Template Engine
MIT License
214 stars 28 forks source link

Экстернал с типом xml анэскейпит html entities #237

Closed smoogly closed 10 years ago

smoogly commented 10 years ago

Компилирую шаблон

module 'main'

external xml unescapes(scalar)

match / {
    unescapes('<h1>&lt;img src=x onerror=alert(1)&gt;</h1>')
}

и запускаю через

var yr = require('yate/lib/runtime.js');
require('./template.yate.compiled.js');

yr.externals['unescapes'] = function(arg) {
    return arg;
};

var result = yr.run('main', {});

console.log(result);

Получаю <h1><img src=x onerror=alert(1)></h1>, ожидаю увидеть <h1>&lt;img src=x onerror=alert(1)&gt;</h1>

Версия яти: 0.0.72

pasaran commented 10 years ago

Да не, вроде все правильно. Энтити резолвятся сразу в момент компиляции во всех строковых литералах. Т.е. ты по факту написал так:

match / {
    unescapes('<h1><img src=x onerror=alert(1)></h1>')
}

После чего ты обманул компилятор, сказав, что вернул заэскейпленный текст, а на деле ничего такого не сделал. Ну и получил, что хотел.

pasaran commented 10 years ago

Наверное, тебе нужно было написать так:

match / {
    unescapes('<h1>&amp;lt;img src=x onerror=alert(1)&amp;gt;</h1>')
}
smoogly commented 10 years ago

Если передавать в рантайме значения, то то же самое происходит.

module 'main'

external xml unescapes(scalar)
external xml format (scalar, scalar)

match / {
    unescapes(format(.format, .substitute))
}
var yr = require('yate/lib/runtime.js');
require('./template.yate.compiled.js');

yr.externals['unescapes'] = function(arg) {
    return arg;
};

yr.externals['format'] = function(format, substitute) {
    return require('util').format(format, substitute);
};

var result = yr.run('main', {
    format: '<h1>%s</h1>',
    substitute: '&lt;img src=x onerror=alert(1)&gt;'
});

console.log(result);

Результат: <h1><img src=x onerror=alert(1)></h1>, Ожидаю: <h1>&lt;img src=x onerror=alert(1)&gt;</h1>

smoogly commented 10 years ago

При этом

module 'main'

external xml format (scalar, scalar)

match / {
    format(.format, .substitute)
}
var yr = require('yate/lib/runtime.js');
require('./template.yate.compiled.js');

yr.externals['format'] = function(format, substitute) {
    return require('util').format(format, substitute);
};

var result = yr.run('main', {
    format: '<h1>%s</h1>',
    substitute: '&lt;img src=x onerror=alert(1)&gt;'
});

console.log(result);

Даёт правильную строку <h1>&lt;img src=x onerror=alert(1)&gt;</h1> Значит проблема при конвертации между scalar и xml в экстернале.

pasaran commented 10 years ago

Еще раз повторю. Энтити внутри строк: т.е. вот такие: "blabla &copy;" — это ровно тоже самое, что и соответствующий символ. По сути, энтити нужны только для всяких символов типа &nbsp; — которые невидимы. Энтити, которые ты берешь из входящего дерева — никак специально не обрабатываются. Это просто символы в строке, без какого-либо специального значения.

А конвертации как раз никакой нет. Ты просто принудительно меняешь тип строки. Она была скаляр — если бы ты ее вывел, то в ней все бы заэскейпилось. А ты сказал, что там xml — т.е. ты по сути сказал, что ее эскейпить не нужно, ты сам про все позаботился.

Вообще, я не очень понимаю, чего ты добиваешься, но вот так будет то, что тебе нужно безо всяких функций:

match / {
    <h1>
        "<img src=x onerror=alert(1)>"
    </h1>
}
pasaran commented 10 years ago

Если передавать в рантайме значения, то то же самое происходит.

Мне кажется, тут дело в сигнатурах. Попробуй вот так:

external xml unescapes(xml)
external xml format (scalar, scalar)

(Ну или просто unescapes не используй, у тебя уже и так format xml отдает)

Потому что у тебя unescapes(format(...)) — формат отдает xml, а unescapes принимает scalar. По идее тут должно случиться автоматическое преобразование xml -> scalar.

pasaran commented 10 years ago

И да, можно скомпилированный код посмотреть:

r0 += (yr.externals['unescapes'])(xml2scalar( (yr.externals['format'])(simpleScalar('format', c0), simpleScalar('substitute', c0)) ));
smoogly commented 10 years ago

Но почему xml2scalar преобразует entities? Не понимаю, почему &lt; — то же самое, что <. Преобразование безобидное для случаев типа &copy;, но не для &lt; и &gt по очевидным причинам.

Ок, может быть я использую ять неправильно. Естественно, в примере я старался локализовать проблему и всё по-максимуму упростить.

Код выглядит вот так:

bold = (
  <em class="{pageId}-delete-popup-content-title">
    '%s'
  </em>
)
i18n-xml(meta.language, 'app.delete.confirm', format(bold, .title))

Определены вот так:

external xml i18n-xml(scalar, scalar)
external xml format(xml)
    yr.externals['i18n-xml'] = function(lang, locKey) {
        /* jshint unused:false */
        var i18n = require('putils').i18n;
        return i18n.apply(i18n, arguments);
    };

    yr.externals['format'] = function(format, values) {
        /* jshint unused:false */

        var args = Array.prototype.slice.call(arguments, 1);

        var util = require('util');
        return util.format.apply(util, [format].concat(args.map(function(arg) {
            return require('lodash').escape(arg);
        })));
    };

Какой true yate way, чтобы оно работало?

pasaran commented 10 years ago

Но почему xml2scalar преобразует entities?

Потому что это обратное преобразование к scalar2xml. Посмотри в runtime.js что они делают.

Какой true yate way, чтобы оно работало?

Я не могу все это у себя воспроизвести, слишком много каких-то левых зависимостей. Могу предположить, что надо таки указать явно тип третьего аргумента в i18-xml:

external xml i18n-xml(scalar, scalar, xml)
smoogly commented 10 years ago

Понял, спасибо за объяснения.

Есть ли способ указать тип для всех остальных аргументов, или сказать яти "не конвертировать тип, пришедший в аргумент экстернала"?