在我的Tcl/Tk项目中,我需要允许我的用户以定义良好的方式篡改字符串。
这个想法是,允许人们声明一个"字符串篡改"proc/epr/function/。。。在配置文件中,然后将其应用于有问题的字符串。
我有点担心如何正确实施。
到目前为止我考虑过的可能性:
-
正则表达式
这是我的第一个想法,但有两个注意事项:
- 在Tcl中用正则表达式搜索/替换似乎很尴尬。至少对于regsub,我需要分别传递匹配和替换部分(而不是例如
sed
允许我传递为我做所有事情的单个复杂字符串(;Tcl有sed
实现,但它们看起来很幼稚,可能迟早会崩溃 - 正则表达式本身也可能很尴尬;使用它们来弄乱复杂的字符串通常比实际情况更复杂
- 在Tcl中用正则表达式搜索/替换似乎很尴尬。至少对于regsub,我需要分别传递匹配和替换部分(而不是例如
-
procs?
既然目标平台无论如何都是Tcl,为什么不使用Tcl的力量来进行字符串篡改呢?"函数"应该有一个输入并产生一个输出,理想情况下,应该促使用户正确地执行它(例如,无法定义需要两个参数的过程(,并且(几乎(不可能产生副作用(如更改应用程序的状态(。
一种简单的方法是使用
proc mymangler s $body
($body
是用户定义的字符串(,但有很多事情可能会出错:$body
采用不同的arg名称(例如$x
而不是$s
($body
未返回任何内容$body
改变变量,。。。在环境中
expr
会话看起来更像它(总是返回东西,不允许轻易修改环境(,但我无法使它们在字符串上工作,并且在不同意变量名称的情况下无法传递变量。
所以,到目前为止我想到的最好的是:
set userfun {return $s} # user-defined string
proc mymangler s ${userfun}
set output [mymangler $input]
有没有更好的方法可以在Tcl中实现用户定义的字符串篡改器?
您可以使用apply
——用户提供一个2元素列表:第二个元素是"proc-body",即执行mangling的代码;第一个元素是保存字符串的变量名,这个变量用于正文。
例如:
set userfun {{str} {string reverse $str}}
set input "some string"
set result [apply $userfun $input] ;# => "gnirts emos"
当然,您从用户那里得到的代码是任意的Tcl代码。你可以在一个安全的解释器中运行它:
set userfun {{str} {exec some malicious code; return [string reverse $str]}}
try {
set interp [safe::interpCreate]
set result [$interp eval [list apply $userfun $input]]
puts "mangled string is: $result"
safe::interpDelete $interp
} on error e {
error "Error: $e"
}
中的结果
Error: invalid command name "exec"
注:
- 使用标准Tcl命令
apply
- 用户必须指定主体中使用的变量名
这个方案确实保护了环境:
set userfun {{str} {set ::env(SOME_VAR) "safe slave"; return $str$str}} set env(SOME_VAR) "main" puts $env(SOME_VAR) try { set interp [safe::interpCreate] set result [$interp eval [list apply $userfun $input]] puts "mangled string is: $result" safe::interpDelete $interp } on error e { error "Error: $e" } puts $env(SOME_VAR)
输出
main mangled string is: some stringsome string main
- 如果用户没有返回值,那么损坏的字符串就是空字符串
"简单化"方法与foreach
类似,因为它要求用户提供一个变量名和一个使用该变量的脚本来评估,这是一种很好的方法。如果您不希望它影响程序的其余部分,请在一个单独的解释器中运行它:
set x 0
proc mymangler {name body} {
set i [interp create -safe]
set s "some string to change"
try {
# Build the lambda used by apply here instead of making
# the user do it.
$i eval [list apply [list $name $body] $s]
} on error e {
return $e
} finally {
interp delete $i
}
}
puts [mymangler s { set x 1; string toupper $s }]
puts $x
输出
SOME STRING TO CHANGE
0
如果调用这个函数的人说使用s
作为变量,然后使用体内的其他东西,那就由他们决定了。提供一个不返回任何内容的脚本也是如此。
我通常允许用户将命令前缀指定为Tcl列表(大多数简单的命令名都非常适合(,然后通过以下操作将其应用于参数:
set mangled [{*}$commandPrefix $valueToMangle]
这让人们可以提供他们想要的任何东西,尤其是当他们可以根据需要使用apply
和lambda术语来破坏东西时。当然,如果你在做一个手术,那么你可能实际上做得更好:
set mangled [uplevel 1 [list {*}$commandPrefix $valueToMangle]]
以便您在调用方的上下文中运行(将1
更改为#0
以使用全局上下文(,这有助于保护您的过程免受意外更改的影响,并使在mangler中使用upvar
变得更容易。
如果篡改前缀的来源是不可信的(这意味着什么在很大程度上取决于您的应用程序和部署(,那么您可以在一个单独的解释器中运行篡改代码:
# Make the safe evaluation context; this is *expensive*
set context [interp create -safe]
# You might want to let them define extra procedures too
# interp invokehidden $context source /the/users/file.tcl
# Use the context
try {
set mangled [interp eval $context [list {*}$commandPrefix $valueToMangle]]
} on error {msg} {
# User supplied something bad; error message in $msg
}
有各种方法可以支持用户指定转换,但如果你能向他们公开你正在使用Tcl的事实,那么这可能是最简单、最灵活的。