允许用户提供字符串"mangler"作为正则表达式/过程/expr/ 的正确方法是什么



在我的Tcl/Tk项目中,我需要允许我的用户以定义良好的方式篡改字符串。

这个想法是,允许人们声明一个"字符串篡改"proc/epr/function/。。。在配置文件中,然后将其应用于有问题的字符串。

我有点担心如何正确实施。

到目前为止我考虑过的可能性:

  • 正则表达式

    这是我的第一个想法,但有两个注意事项:

    • 在Tcl中用正则表达式搜索/替换似乎很尴尬。至少对于regsub,我需要分别传递匹配和替换部分(而不是例如sed允许我传递为我做所有事情的单个复杂字符串(;Tcl有sed实现,但它们看起来很幼稚,可能迟早会崩溃
    • 正则表达式本身也可能很尴尬;使用它们来弄乱复杂的字符串通常比实际情况更复杂
  • 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的事实,那么这可能是最简单、最灵活的。

最新更新