diff options
author | nagai <nagai@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2003-06-24 16:46:07 +0000 |
---|---|---|
committer | nagai <nagai@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2003-06-24 16:46:07 +0000 |
commit | 17e1936d8bdd897cebd03ed198a826ee395572ac (patch) | |
tree | c911d2fd21b63dfccc1610f3afdc023233b50e2f /ext/tk | |
parent | b60ba5942968e782cfc407385e175c449924f1d7 (diff) | |
download | ruby-17e1936d8bdd897cebd03ed198a826ee395572ac.tar.gz |
tk.rb :
* TkToplevel, TkFrame, TkPanedwindow, TkOptionDB : bug fix
* TkOptionDB : make it more secure to use procs defined on resourceDB
sample/tkoptdb.rb, sample/resource.ja, sample/resource.en :
* sample script how to use TkOptionDB.
resource.ja and resource.en are samples of resource definition file
which are read by tkoptdb.rb.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@3998 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ext/tk')
-rw-r--r-- | ext/tk/MANIFEST | 3 | ||||
-rw-r--r-- | ext/tk/lib/tk.rb | 150 | ||||
-rw-r--r-- | ext/tk/sample/resource.en | 12 | ||||
-rw-r--r-- | ext/tk/sample/resource.ja | 12 | ||||
-rw-r--r-- | ext/tk/sample/tkoptdb.rb | 49 |
5 files changed, 204 insertions, 22 deletions
diff --git a/ext/tk/MANIFEST b/ext/tk/MANIFEST index 4f7fca4430..def3d3ddb0 100644 --- a/ext/tk/MANIFEST +++ b/ext/tk/MANIFEST @@ -27,5 +27,8 @@ sample/tkfrom.rb sample/tkhello.rb sample/tkline.rb sample/tkmenubutton.rb +sample/tkoptdb.rb +sample/resource.ja +sample/resource.en sample/tktimer.rb sample/tktimer2.rb diff --git a/ext/tk/lib/tk.rb b/ext/tk/lib/tk.rb index dabc78753c..d7531b2f8f 100644 --- a/ext/tk/lib/tk.rb +++ b/ext/tk/lib/tk.rb @@ -2676,26 +2676,43 @@ module TkOptionDB @@resource_proc_class = Class.new class << @@resource_proc_class private :new - + CARRIER = '.'.freeze METHOD_TBL = {} ADD_METHOD = false SAFE_MODE = 4 + def __closed_block_check__(str) + depth = 0 + str.scan(/[{}]/){|x| + if x == "{" + depth += 1 + elsif x == "}" + depth -= 1 + end + if depth <= 0 && !($' =~ /\A\s*\Z/) + fail RuntimeError, "bad string for procedure : #{str.inspect}" + end + } + str + end + def __check_proc_string__(str) # If you want to check the proc_string, do it in this method. + # Please define this in the block given to 'new_proc_class' method. str end def method_missing(id, *args) res_proc = self::METHOD_TBL[id] unless res_proc.kind_of? Proc - if id == :new || (!self::METHOD_TBL.has_key?(id) && !self::ADD_METHOD) + if id == :new || !(self::METHOD_TBL.has_key?(id) || self::ADD_METHOD) raise NoMethodError, "not support resource-proc '#{id.id2name}' for #{self.name}" end - proc_str = TkOptionDB.get(self::CARRIER, id.id2name, '') + proc_str = TkOptionDB.get(self::CARRIER, id.id2name, '').strip proc_str = '{' + proc_str + '}' unless /\A\{.*\}\Z/ =~ proc_str + proc_str = __closed_block_check__(proc_str) proc_str = __check_proc_string__(proc_str) res_proc = eval 'Proc.new' + proc_str self::METHOD_TBL[id] = res_proc @@ -2706,10 +2723,11 @@ module TkOptionDB }.call end - private :__check_proc_string__, :method_missing + private :__closed_block_check__, :__check_proc_string__, :method_missing end + @@resource_proc_class.freeze - def new_proc_class(klass, func, safe = 4, add = false, parent = nil) + def __create_new_class(klass, func, safe = 4, add = false, parent = nil) klass = klass.to_s if klass.kind_of? Symbol unless (?A..?Z) === klass[0] fail ArgumentError, "bad string '#{klass}' for class name" @@ -2733,21 +2751,86 @@ module TkOptionDB METHOD_TBL = {} ADD_METHOD = #{add} SAFE_MODE = #{safe} - %w(#{func_str}).each{|f| METHOD_TBL.delete(f.intern) } + %w(#{func_str}).each{|f| METHOD_TBL[f.intern] = nil } end EOD if parent.kind_of?(Class) && parent <= @@resource_proc_class - parent.class_eval body - eval parent.name + '::' + klass + parent.class_eval(body) + eval(parent.name + '::' + klass) else - eval body - eval 'TkOptionDB::' + klass - end + eval(body) + eval('TkOptionDB::' + klass) + end + end + module_function :__create_new_class + private_class_method :__create_new_class + + def __remove_methods_of_proc_class(klass) + # for security, make these methods invalid + class << klass + attr_reader :class_eval, :name, :superclass, + :ancestors, :const_defined?, :const_get, :const_set, + :constants, :included_modules, :instance_methods, + :method_defined?, :module_eval, :private_instance_methods, + :protected_instance_methods, :public_instance_methods, + :remove_const, :remove_method, :undef_method, + :to_s, :inspect, :display, :method, :methods, + :instance_eval, :instance_variables, :kind_of?, :is_a?, + :private_methods, :protected_methods, :public_methods + end + end + module_function :__remove_methods_of_proc_class + private_class_method :__remove_methods_of_proc_class + + RAND_BASE_CNT = [0] + RAND_BASE_HEAD = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + RAND_BASE_CHAR = RAND_BASE_HEAD + 'abcdefghijklmnopqrstuvwxyz0123456789_' + def __get_random_basename + name = '%s%03d' % [RAND_BASE_HEAD[rand(RAND_BASE_HEAD.size),1], + RAND_BASE_CNT[0]] + len = RAND_BASE_CHAR.size + (6+rand(10)).times{ + name << RAND_BASE_CHAR[rand(len),1] + } + RAND_BASE_CNT[0] = RAND_BASE_CNT[0] + 1 + name + end + module_function :__get_random_basename + private_class_method :__get_random_basename + + # define new proc class : + # If you want to modify the new class or create a new subclass, + # you must do such operation in the block parameter. + # Because the created class is flozen after evaluating the block. + def new_proc_class(klass, func, safe = 4, add = false, parent = nil, &b) + new_klass = __create_new_class(klass, func, safe, add, parent) + new_klass.class_eval(&b) if block_given? + __remove_methods_of_proc_class(new_klass) + new_klass.freeze + new_klass end module_function :new_proc_class + + def eval_under_random_base(parent = nil, &b) + new_klass = __create_new_class(__get_random_basename(), + [], 4, false, parent) + ret = new_klass.class_eval(&b) if block_given? + __remove_methods_of_proc_class(new_klass) + new_klass.freeze + ret + end + module_function :eval_under_random_base + + def new_proc_class_random(klass, func, safe = 4, add = false, &b) + eval_under_random_base(){ + TkOption.new_proc_class(klass, func, safe, add, self, &b) + } + end + module_function :new_proc_class_random end TkOption = TkOptionDB +TkResourceDB = TkOptionDB module TkTreatFont def font_configinfo @@ -3638,7 +3721,6 @@ class TkToplevel<TkWindow keys['class'] = keys.delete('classname') end @classname = keys['class'] - @screen = keys['screen'] @colormap = keys['colormap'] @container = keys['container'] @screen = keys['screen'] @@ -3647,8 +3729,11 @@ class TkToplevel<TkWindow if @classname.kind_of? TkBindTag @db_class = @classname @classname = @classname.id - else + elsif @classname @db_class = TkDatabaseClass.new(@classname) + else + @db_class = self.class + @classname = @db_class::WidgetClassName end keys, cmds = _wm_command_option_chk(keys) super(keys) @@ -3661,33 +3746,47 @@ class TkToplevel<TkWindow } return end + if screen.kind_of? Hash keys = screen else @screen = screen + if classname.kind_of? Hash + keys = classname + else + @classname = classname + end end - @classname = classname if keys.kind_of? Hash keys = _symbolkey2str(keys) if keys.key?('classname') keys['class'] = keys.delete('classname') end - @classname = keys['class'] + @classname = keys['class'] unless @classname @colormap = keys['colormap'] @container = keys['container'] - @screen = keys['screen'] + @screen = keys['screen'] unless @screen @use = keys['use'] @visual = keys['visual'] end if @classname.kind_of? TkBindTag @db_class = @classname @classname = @classname.id - else + elsif @classname @db_class = TkDatabaseClass.new(@classname) + else + @db_class = self.class + @classname = @db_class::WidgetClassName end keys, cmds = _wm_command_option_chk(keys) super(parent, keys) - cmds.each{|k,v| self.send(k,v)} + cmds.each{|k,v| + if v.kind_of? Array + self.send(k,*v) + else + self.send(k,v) + end + } end def create_self(keys) @@ -3750,8 +3849,11 @@ class TkFrame<TkWindow if @classname.kind_of? TkBindTag @db_class = @classname @classname = @classname.id - else + elsif @classname @db_class = TkDatabaseClass.new(@classname) + else + @db_class = self.class + @classname = @db_class::WidgetClassName end super(keys) end @@ -3812,7 +3914,7 @@ class TkPanedWindow<TkWindow alias remove forget def identify(x, y) - ######### + list(tk_send('identify', x, y)) end def proxy_coord @@ -3830,12 +3932,16 @@ class TkPanedWindow<TkWindow def sash_coord(index) list(tk_send('sash', 'coord', index)) end + def sash_dragto(index) + tk_send('sash', 'dragto', index, x, y) + self + end def sash_mark(index, x, y) - tk_send('sash', 'mark', x, y) + tk_send('sash', 'mark', index, x, y) self end def sash_place(index, x, y) - tk_send('sash', 'place', x, y) + tk_send('sash', 'place', index, x, y) self end diff --git a/ext/tk/sample/resource.en b/ext/tk/sample/resource.en new file mode 100644 index 0000000000..29bd587ea2 --- /dev/null +++ b/ext/tk/sample/resource.en @@ -0,0 +1,12 @@ +# +# see Tcl/Tk's "options" manual for "Database Name" and "Database Class" +# +*BtnFrame.borderWidth: 5 +*BtnFrame.relief: ridge +*BtnFrame.Button.background: wheat +*BtnFrame.Button.foreground: red +*hello.text: HELLO +*quit.text: QUIT +*BTN_CMD.show_msg: {|arg| print "($SAFE=#{$SAFE}) ";\ + print "Hello!! This is a sample of #{arg}.\n"} +*BTN_CMD.bye_msg: {print "($SAFE=#{$SAFE}) Good-bye.\n"} diff --git a/ext/tk/sample/resource.ja b/ext/tk/sample/resource.ja new file mode 100644 index 0000000000..61098db4f8 --- /dev/null +++ b/ext/tk/sample/resource.ja @@ -0,0 +1,12 @@ +# +# see Tcl/Tk's "options" manual for "Database Name" and "Database Class" +# +*BtnFrame.borderWidth: 5 +*BtnFrame.relief: ridge +*BtnFrame.Button.background: wheat +*BtnFrame.Button.foreground: red +*hello.text: こんにちは +*quit.text: 終了 +*BTN_CMD.show_msg: {|arg| print "($SAFE=#{$SAFE}) ";\ + print "こんにちは!! #{arg} のサンプルです.\n"} +*BTN_CMD.bye_msg: {print "($SAFE=#{$SAFE}) さようなら.\n"} diff --git a/ext/tk/sample/tkoptdb.rb b/ext/tk/sample/tkoptdb.rb new file mode 100644 index 0000000000..930985dc22 --- /dev/null +++ b/ext/tk/sample/tkoptdb.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby +# +# sample script of TkOptionDB +# +# If 'LANG' environment variable's value is started by 'ja', +# then read Japanese resource data and display Japanese button text. +# In other case, read English resource data and display English text. +# +require "tk" + +if ENV['LANG'] =~ /^ja/ + # read Japanese resource + TkOptionDB.readfile(File.expand_path('resource.ja', File.dirname(__FILE__))) +else + # read English resource + TkOptionDB.readfile(File.expand_path('resource.en', File.dirname(__FILE__))) +end + +# 'show_msg' and 'bye_msg' procedures can be defined on BTN_CMD resource. +# Those procedures are called under $SAFE==2 +cmd = TkOptionDB.new_proc_class(:BTN_CMD, [:show_msg, :bye_msg], 2) { + # If you want to check resource string (str), + # please define __check_proc_string__(str) like this. + class << self + def __check_proc_string__(str) + print "($SAFE=#{$SAFE}) check!! str.tainted?::#{str.tainted?}" + str.untaint + print "==>#{str.tainted?} : " + str + end + end +} + +TkFrame.new(:class=>'BtnFrame'){|f| + pack(:padx=>5, :pady=>5) + TkButton.new(:parent=>f, :widgetname=>'hello'){ + command proc{ + print "($SAFE=#{$SAFE}) : " + cmd.show_msg(TkOptionDB.inspect) + } + pack(:fill=>:x, :padx=>10, :pady=>10) + } + TkButton.new(:command=>proc{print "($SAFE=#{$SAFE}) : "; cmd.bye_msg; exit}, + :parent=>f, :widgetname=>'quit'){ + pack(:fill=>:x, :padx=>10, :pady=>10) + } +} + +Tk.mainloop |