2011-02-02 72 views
1

我正在为朋友的心理调查项目创建一个简单的Ruby on Rails调查应用程序。 所以我们有调查,每个调查都有一堆问题,每个问题都有一个参与者可以选择的选项。没什么好激动Ruby:解析,替换和评估字符串公式

其中一个有趣的方面是每个答案选项都有一个与它相关的分数值。 因此,对于每项调查,总分都需要根据这些值进行计算。

现在我的想法是,而不是硬编码计算是让用户添加一个公式,总调查得分将被计算。示例公式:

"Q1 + Q2 + Q3" 
"(Q1 + Q2 + Q3)/3" 
"(10 - Q1) + Q2 + (Q3 * 2)" 

所以只是基本的数学(为了清晰起见,附加了一些括号)。这个想法是保持公式非常简单,这样任何具有基本数学的人都可以在不解决一些花哨的语法的情况下进入它们。

我的想法是采取任何给定的公式,并根据参与者选择的分数来替换占位符,如Q1,Q2等。然后eval()新形成的字符串。事情是这样的:

f = "(Q1 + Q2 + Q3)/2" # some crazy formula for this survey 
values = {:Q1 => 1, :Q2 => 2, :Q3 => 2} # values for substitution 
result = f.gsub(/(Q\d+)/) {|m| values[$1.to_sym] } # string to be eval()-ed 
eval(result) 

所以我的问题是:

  1. 有没有更好的方式来做到这一点? 我愿意接受任何建议。

  2. 如何处理并非所有 占位符都被成功替换的公式(例如,一个 问题未得到解答)?例如:{:Q2 => 2}不是 值散列?我的想法是拯救eval(),但它不会在这种情况下失败coz (1 + + 2)/2仍然可以eval() - ed ...任何想法?

  3. 如何获得正确的结果?应该是2.5,但由于整数算术,它将截断为2.我不能指望提供正确公式(例如/ 2.0)的人理解这种细微差别。

  4. 我不指望这一点,但如何 最佳保护的eval()的滥用(例如 坏公式,操纵值 进来的)?例如:f = 'system("ruby -v"); (Q1 + (Q2/3) + Q3 + (Q4 * 2))/2 '

谢谢!

+0

是否有人会添加至少一些有助于我开始的Treetop示例。我在Treetop上读过的所有内容都有点超载。或者我应该开始一个新的问题?这变得太复杂了,因为我只是希望把东西扔在一起。没有任何长寿。 *叹息* – Swartz 2011-02-03 00:34:16

回答

5

好的,现在它是完全安全的。我发誓!

我通常会克隆formula变量,但在这种情况下,因为你担心敌对的用户我打扫到位变量:

class Evaluator 

    def self.formula(formula, values) 
    # remove anything but Q's, numbers,()'s, decimal points, and basic math operators 
    formula.gsub!(/((?![qQ0-9\s\.\-\+\*\/\(\)]).)*/,'').upcase! 
    begin 
     formula.gsub!(/Q\d+/) { |match| 
     ( 
      values[match.to_sym] && 
      values[match.to_sym].class.ancestors.include?(Numeric) ? 
      values[match.to_sym].to_s : 
      '0' 
     )+'.0' 
     } 
     instance_eval(formula) 
    rescue Exception => e 
     e.inspect 
    end 
    end 

end 

f = '(q1 + (q2/3) + q3 + (q4 * 2))' # some crazy formula for this survey 
values = {:Q2 => 1, :Q4 => 2} # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}" 
=> formula: (0.0 + (1.0/3) + 0.0 + (2.0 * 2)) = 4.333333333333333 

f = '(Q1 + (Q2/3) + Q3 + (Q4 * 2))/2' # some crazy formula for this survey 
values = {:Q1 => 1, :Q3 => 2} # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}" 
=> formula: (1.0 + (0.0/3) + 2.0 + (0.0 * 2))/2 = 1.5 

f = '(Q1 + (Q2/3) + Q3 + (Q4 * 2))/2' # some crazy formula for this survey 
values = {:Q1 => 'delete your hard drive', :Q3 => 2} # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}" 
=> formula: (0.0 + (0.0/3) + 2.0 + (0.0 * 2))/2 = 1.0 

f = 'system("ruby -v")' # some crazy formula for this survey 
values = {:Q1 => 'delete your hard drive', :Q3 => 2} # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}" 
=> formula: (-) = #<SyntaxError: (eval):1: syntax error, unexpected ')'> 
4

这可能是不值得的,但如果我这样做,我会使用Treetop来定义解析语法。甚至有一些例子使用这种PEG风格的语法来进行简单的算术运算,所以你将成为语法的90%,以及评估加权的大部分方法。

+0

看看Treetop的页面。这看起来像是过火了。 – Swartz 2011-02-02 23:30:32

+1

当然,这可能是矫枉过正;但是,它是确保您的环境不会受到恶意输入影响的一种方法。它只需要一个人输入`exec(“rm/-rf”)`(或类似破坏性的东西,但可用于您的Web服务器进程)造成严重破坏。 – Phrogz 2011-02-02 23:42:15

2

您可以使用RubyParser来解释节点的迭代表达式,以检查是否存在危险代码,如函数调用。看:

require 'ruby_parser' 
def valid_formula?(str, consts=[]) 
    !!valid_formula_node?(RubyParser.new.process(str), consts) 
rescue Racc::ParseError 
    false 
end 
def valid_formula_node?(node, consts) 
    case node.shift 
    when :call 
    node[1].to_s !~ /^[a-z_0-9]+$/i and 
    valid_formula_node?(node[0], consts) and 
    valid_formula_node?(node[2], consts) 
    when :arglist 
    node.all? {|inner| valid_formula_node?(inner, consts) } 
    when :lit 
    Numeric === node[0] 
    when :const 
    consts.include? node[0] 
    end 
end 

这只是允许运营商,数字和specifc常数。

valid_formula?("(Q1 + Q2 + Q3)/2", [:Q1, :Q2, :Q3]) #=> true 
valid_formula?("exit!", [:Q1, :Q2, :Q3])    #=> false 
valid_formula?("!(%&$)%*", [:Q1, :Q2, :Q3])   #=> false 
0

RE 2)尽管这是丑陋的,你可以只创建使用默认值的哈希,并确保当to_s被调用它失败(我没有说这是丑陋的,对不对?):

>> class NaN ; def to_s; raise ArgumentError ; end; end #=> nil 
>> h = Hash.new { NaN.new } #=> {} 
>> h[:q1] = 12 #=> 12 
>> h[:q1] #=> 12 
>> h[:q2] 
ArgumentError: ArgumentError 

回复3)只要确保你有至少一个浮子你计算。最简单的方法是在更换过程中,只是把所有的提供的值在float:

>> result = f.gsub(/(Q\d+)/) {|m| values[$1.to_sym].to_f } #=> "(1.0 + 2.0 + 2.0)/2" 
>> eval result #=> 2.5 

重4),你可能想在$SAFE阅读起来。该“镐”实际上包含了一个例子约eval在Web表单中输入荷兰国际集团的东西:

http://ruby-doc.org/docs/ProgrammingRuby/html/taint.html

这是,如果你真的想下去eval路线,千万不要忽视在本次讨论中提供的替代品。

2

使用Dentaku

Dentaku为数学和逻辑式语言,允许运行时在公式中引用的变量的值的结合解析器和评估。它旨在安全地评估不受信任的表达式,而不会打开安全漏洞。