Зачем нужно
Различная фильтрация есть везде. Например, файрволл netfilter (iptables) имеет свой синтаксис для описания пакетов. В файле .htaccess апача свой язык, как определять, кому давать доступ к каталогу, кому нет. В СУБД свой очень мощный язык (SQL WHERE ...) для фильтрации записей. В почтовых программах (thunderbird, gmail) — свой интерфейс описания фильтров, в соответствии с которыми письма будут раскидываться по папкам.
И везде — свой велосипед.
Для бухгалтерской программы вам может быть удобно позволить пользователю выбрать, кому будет повышена зарплата (все женщины, а так же мужчины возрастом от 25 до 32 лет, либо же до 50 лет если у мужчины имя Вася). И каждому подходящему повысить по пользовательскому выражению ( + 2000 рублей + 20% от прежней зарплаты + по 1000 рублей за каждый год стажа)
Для интернет-магазина (или его админки) — найти все ноутбуки, с памятью от 4 до 8 Gb, которых на складе более 3 штук, но не Acer, или даже Acer, если стоят меньше 30 000 рублей.
Конечно, можно присобачить свою сложную систему фильтров и критериев, сделать для них веб-интерфейс, но проще было бы все сделать в пару строк?
src="(RAM>=4 and RAM<=8 and stock>3 and not brand=='Acer') or (brand=='Acer' and price<30000)"
success, result = evalidate.safeeval(src,notebook)
Хочется и колется
Очевидный способ добавить любую логику в программу — через eval(). Решение самое простое, самое гибкое, но есть большие подводные камни — безопасность. Что если пользовательское выражение будет делать os.system('rm -rf /')?
Пример, как можно «завалить» питон через eval():
stackoverflow.com/questions/13066594/is-there-a-way-to-secure-strings-for-pythons-eval
nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
tav.espians.com/a-challenge-to-break-python-security.html
Right Way
Часто в советах рекомендуется «правильный путь» — использовать сам питон, чтобы он парсил код из текстовой формы в AST дерево, а потом уже самостоятельно парсить это дерево, отделяя зерна от козлищ. Но как? И тут выходит на арену главная проблема веломаркетинга — пока найдешь подходящий велосипед, или хотя бы хороший чертеж… проще самому велосипед изобрести.
Evalidate
Встречайте evalidate, мой маленький велосипед для этой цели. Кому-то может он сам пригодится (я постарался сделать его достаточно гибким), а остальным исходный код может послужить примером, как можно решать эту задачу (ну и как нельзя писать код, конечно же).
Ставим пипом
pip install evalidate
Простой пример:
import evalidate
depot = [
{
'book': 'Sirens of Titan',
'price': 12,
'stock': 4
},
{
'book': 'Gone Girl',
'price': 9.8,
'stock': 0
},
{
'book': 'Choke',
'price': 14,
'stock': 2
},
{
'book': 'Pulp',
'price': 7.45,
'stock': 4
}
]
#src='stock==0' # books out of stock
src='stock>0 and price>8' # expensive book available for sale
for book in depot:
success, result = evalidate.safeeval(src,book)
if success:
if result:
print book
else:
print "ERR:", result
В данном случае, в src у нас «пользовательский» код, который может быть каким-то плохим. В примере два варианта «хорошего» кода, первый показывает книги, которых у нас нет на складе, второй — дорогие книги, которые в наличии. Если попробовать подсунуть плохой код (просто который не парсится, код с обращением к переменным, которых у нас нет в контексте, код, который использует неразрешенные операции, например Call (вызов функции)), то success будет False, и программа сообщит об ошибке (Но не упадет, и не исполнит плохой код).
Как альтернатива — можно через evalidate.evalidate() получить AST-дерево, которое генерируется через ast.parse (либо exception, если код не парсится или содержит неразрешенные операции), и затем уже его скомпилировать и исполнить через eval().
node = evalidate.evalidate(src)
code = compile(node,'<usercode>','eval')
result = eval(code,{},data)
Ну и еще посмотреть в код модуля (благо он простой), и сделать свой велосипед :-)
Обращение к сообществу
В evalidate по умолчанию включен свой набор «безопасных» (?) питоновских операций. Просто, по моему личному мнению — они безопасны. Это значит, что в течение 15 минут мне не пришло в голову, как сделать что-то ужасное, используя только эти операции. Но может быть вам придет? Или, может стоит добавить в список еще какие-то операции, которые сделают дефолтную конфигурацию более гибкой (позволят использовать более богатый язык выражений), и при этом не создадут уязвимостей? Есть идеи?
Автор: xenon