2011-02-02 159 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爲數學和邏輯式語言,允許運行時在公式中引用的變量的值的結合解析器和評估。它旨在安全地評估不受信任的表達式,而不會打開安全漏洞。