summaryrefslogtreecommitdiff
path: root/ext/tk
diff options
context:
space:
mode:
authornagai <nagai@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-06-24 16:46:07 +0000
committernagai <nagai@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-06-24 16:46:07 +0000
commit17e1936d8bdd897cebd03ed198a826ee395572ac (patch)
treec911d2fd21b63dfccc1610f3afdc023233b50e2f /ext/tk
parentb60ba5942968e782cfc407385e175c449924f1d7 (diff)
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/MANIFEST3
-rw-r--r--ext/tk/lib/tk.rb150
-rw-r--r--ext/tk/sample/resource.en12
-rw-r--r--ext/tk/sample/resource.ja12
-rw-r--r--ext/tk/sample/tkoptdb.rb49
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