[eval] может быть полезным и элегантным решением в тех случаях, когда требуется раскрытие (expansion) аргумента, например, для добавления элементов одного списка к другому:

CODE
eval lappend thislist $thatlist

Однако опасайтесь двойных подстановок!

Эта небольшая статья посвящена "подводным камням" тикля и способам их обхода. Первоначально TCL кажется достаточно прямолинейным и простым в использовании. Однако попытка решения нетривиальных задач может поставить перед вами некоторые проблемы, например: чрезмерное обилие скобок, "нежелательные" вычисления и т.д. Эти проблемы, с которыми сталкивались многие очень умные люди, указывают на то, что представление программиста о работе интерпретатора TCL неверно. Задача данной статьи - в общих чертах описать базовую модель, "подводные камни" и способ мышления (программирования).

БАЗОВАЯ МОДЕЛЬ
Практически все проблемы подпадают под три простых правила:
  • За каждый проход интерпретатора TCL выполняется один, и только дин, уровень подстановок и/или вычислений.
  • Интерпретатор TCL за один свой проход сканирует каждый символ только один раз.
  • Любой правильно оформленный список является также правильно оформленной командой; при вычислении, каждый элемент списка рассматривается как одно слово команды, без каких-либо дальнейших подстановок.

Например, рассмотрим следующие четыре однострочных скрипта:

CODE
set a $b
eval {set a $b}
eval "set a $b"
eval [list set a $b]



В первом скрипте команда set рассматривается интерпретатором единожды. Она разбивается на три слова: "set", "a" и значение переменной "b". Со значением переменной b не выполняется никаких дальнейших подстановок: пробелы в b не рассматриваются как разделители слов команды "set", знак доллара в значении переменной b не станет причиной подстановки переменной и тд.

Во втором скрипте команда "set" будет рассмотрена интерпретатором дважды: первый раз - при разборе команды "eval" и ещё раз - когда "eval" передаст свой аргумент интерпретатору для вычисления. Однако фигурные скобки вокруг выражения предотвратят подстановки значения переменной b: аргумент команды eval - "set a $b". Таким образом, результат вычисления данной команды идентичен результату первого скрипта.

В третьем скрипте вместо фигурных скобок используются кавычки, таким образом, у аргумента команды eval происходит подстановка переменной, и это может привести к нежелательным эффектам, когда eval, в свою очередь, вычислит свой аргумент. Например, если b содержит строку "x y z", то аргументом eval будет "set a x y z"; при вычислении такого скрипта (команда set из пяти слов) возникнет ошибка. Проблема возникла из-за того, что сначала была выполнена подстановка $b, а затем скрипт был "перевычислен". Это двойное вычисление иногда может использоваться для достижения интересных эффектов. Например, если $b содержит строку "$c", то наш скрипт присвоит переменной a значение переменной c (т.е. косвенность).

Четвертый скрипт, как и второй, безопасен. При разборе команды "eval" выполняется подстановка команды, при которой результат команды "list" становится аргументом команды "eval". Результом команды list будет правильный TCL-список из трёх элементов: "set", "a" и содержимого переменной b (всё в одном элементе). Например, если $b равно "x y z", то результатом команды "list" станет строка "set a {x y z}". Эта строка передаётся eval в качестве аргумента, и затем eval "перевычисляет" правильно оформленную команду "set", согласно правилу #3: каждый элемент списка рассматривается как одна часть команды. Таким образом, четвёртый скрипт приводит к тому же результату, что первый и второй.

"ПОДВОДНЫЕ КАМНИ"

Основная идея проблемы заключается в том, что мы имеем случайную строку и хотим предотвратить её вычисление в скрипте и, возможно, в вашем С коде. Самое простое решение - использование команду list для защиты от выполнения строки, если она сгенерирована TCL-скриптом, или использование библиотечной процедуры Tcl_Merge, если строка порождена вашим С кодом. Также, старайтесь избегать кавычек и использовать вместо них списки.

Что это значит для eggdrop'а? Многие скрипты используют команды [timer] и [utimer], которые передают свои аргументы для вычисления интерпретатору (пусть и отложенного) точно так же, как и eval. Так что, если вы на столько беспечны чтобы написать что-нибудь вроде этого:

CODE
utimer 10 "putkick $chan $nick $reason"

и таймер сработает на кого-нибудь с ником [die], то ваш бот "сдохнет" не успев даже кикнуть негодяя. Поэтому всегда используйте [list] для защиты от двойной подстановки.

CODE
utimer 10 [list putkick $chan $nick $reason]

А также никогда не допускайте вычисления/выполнения данных пришедших от непроверенных пользователей. Наивный скрипт для выполнения shell-команд может выглядеть так:

CODE
proc doexec {nick uhost hand chan text} {
   puthelp "privmsg $chan :Shell command results:"
   foreach line [split [eval exec $text] \n] {
       puthelp "privmsg $chan :$line"
   }
   return 1
}

Таким образом, некий подонок оп Johny пишет '!exec echo "mypublickeydatastring" >>../.ssh/authorized_keys', затем просто открывает ssh-соединение с вашим шеллом и коннектится под вашим аккаунтом даже без пароля.

------------------
оригинал поста здесь -> http://forum.egghelp.org/viewtopic.php?t=9945
------------------
glhf


--------------------
-------
glhf
-------
#scripting@WeNet && #tcl@WeNet