diff --git a/ChangeLog b/ChangeLog index b7965b4ab1..62a9b9d071 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Sun Sep 7 16:08:28 2003 + + * ext/tcltklib/tcltklib.c (lib_mainloop_core): fixed signal-trap bug + + * ext/tk/lib/*.rb : Ruby/Tk works at $SAFE == 4 + Fri Sep 6 02:26:34 2003 NAKAMURA, Hiroshi * test/ruby/test_*.rb: assert_same, assert_match, and so on. diff --git a/ext/tcltklib/MANUAL.euc b/ext/tcltklib/MANUAL.euc index f04d036c64..8a7143892c 100644 --- a/ext/tcltklib/MANUAL.euc +++ b/ext/tcltklib/MANUAL.euc @@ -192,13 +192,16 @@ require "tcltklib" : ( 監視スレッドを生成した後にイベントループを実行する ). : 引数の意味は mainloop と同じである. - do_one_event(flag = TclTkLib::EventFlag::ALL) + do_one_event(flag = TclTkLib::EventFlag::ALL | + TclTkLib::EventFlag::DONT_WAIT) : 処理待ちのイベント 1 個を実行する. : イベントを処理した場合は true を返す. : フラグで DONT_WAIT を指定していない場合,フラグで処理対 : 象となっている種類のイベントが発生するまで待ち続ける. : DONT_WAIT を指定していた場合,処理対象イベントがなくても : すぐに終了し false を返す. + : $SAFE >= 4 か,$SAFE >= 1 かつ flag が汚染されているならば + : flag には DONT_WAIT が強制的に付けられる. set_eventloop_tick(timer_tick) : イベントループと同時に別スレッドが稼働している場合に,時 @@ -213,6 +216,7 @@ require "tcltklib" : 定される. : 詳細な説明は略すが,これは CPU パワーを節約しつつ安全で : 安定した動作を実現するために実装した仕様である. + : $SAFE >= 4 では実行が禁止される. get_eventloop_tick : timer_tick の現在値を返す. @@ -222,6 +226,7 @@ require "tcltklib" : く存在しなかった際に sleep 状態に入る時間長を指定する. : 稼働スレッドがイベントループだけの場合には意味をなさない. : デフォルトの値は 20 (ms) + : $SAFE >= 4 では実行が禁止される. get_no_event_wait : no_event_wait の現在値を返す. @@ -239,6 +244,7 @@ require "tcltklib" : が全く発生しないままに 80 回の処理待ちイベント検査が完了 : するとかでカウントが 800 以上になるとスレッドスイッチング : が発生することになる. + : $SAFE >= 4 では実行が禁止される. get_eventloop_weight : 現在の loop_max と no_event_tick との値を返す. @@ -258,6 +264,7 @@ require "tcltklib" : 他のインタープリタの処理継続が不可能になることがある.その : ような場合でもエラーを無視してイベントループが稼働を続ける : ことで,他のインタープリタが正常に動作し続けることができる. + : $SAFE >= 4 では実行が禁止される. mainloop_abort_on_exception : Tk インタープリタ上で例外を発生した際に,イベントループをエ @@ -295,6 +302,7 @@ require "tcltklib" : たとえ明確に false を指定していたとしても,親となるインター : プリタが safe インタープリタであれば,その設定を引き継いで : safe インタープリタとして生成される. + : $SAFE >= 4 では,safe インタープリタ以外の生成が禁止される. make_safe : Tcl/Tk インタープリタを safe インタープリタに変更する. @@ -319,6 +327,7 @@ require "tcltklib" : Tcl/Tk インタープリタの Tk 部分の初期化,再起動を行う. : 一旦 root widget を破壊した後に再度 Tk の機能が必要と : なった場合に用いる. + : $SAFE >= 4 では実行が禁止される. _eval(str) _invoke(*args) diff --git a/ext/tcltklib/tcltklib.c b/ext/tcltklib/tcltklib.c index ef91fcae3d..fdc4b00305 100644 --- a/ext/tcltklib/tcltklib.c +++ b/ext/tcltklib/tcltklib.c @@ -154,6 +154,8 @@ set_eventloop_tick(self, tick) { int ttick = NUM2INT(tick); + rb_secure(4); + if (ttick < 0) { rb_raise(rb_eArgError, "timer-tick parameter must be 0 or positive number"); @@ -209,6 +211,8 @@ set_no_event_wait(self, wait) { int t_wait = NUM2INT(wait); + rb_secure(4); + if (t_wait <= 0) { rb_raise(rb_eArgError, "no_event_wait parameter must be positive number"); @@ -256,6 +260,8 @@ set_eventloop_weight(self, loop_max, no_event) int lpmax = NUM2INT(loop_max); int no_ev = NUM2INT(no_event); + rb_secure(4); + if (lpmax <= 0 || no_ev <= 0) { rb_raise(rb_eArgError, "weight parameters must be positive numbers"); } @@ -336,6 +342,7 @@ ip_evloop_abort_on_exc_set(self, val) { struct tcltkip *ptr = get_ip(self); + rb_secure(4); if (Tcl_GetMaster(ptr->ip) != (Tcl_Interp*)NULL) { /* slave IP */ return lib_evloop_abort_on_exc(self); @@ -392,6 +399,8 @@ lib_mainloop_core(check_root_widget) if (run_timer_flag) { DUMP1("timer interrupt"); run_timer_flag = 0; + DUMP1("call rb_trap_exec()"); + rb_trap_exec(); DUMP1("check Root Widget"); if (check && Tk_GetNumMainWindows() == 0) { return Qnil; @@ -619,15 +628,18 @@ lib_do_one_event_core(argc, argv, self, is_ip) { VALUE vflags; int flags; - int ret; if (rb_scan_args(argc, argv, "01", &vflags) == 0) { - flags = TCL_ALL_EVENTS; + flags = TCL_ALL_EVENTS | TCL_DONT_WAIT; } else { Check_Type(vflags, T_FIXNUM); flags = FIX2INT(vflags); } - + + if (rb_safe_level() >= 4 || (rb_safe_level() >=1 && OBJ_TAINTED(vflags))) { + flags |= TCL_DONT_WAIT; + } + if (is_ip) { /* check IP */ struct tcltkip *ptr = get_ip(self); @@ -637,8 +649,7 @@ lib_do_one_event_core(argc, argv, self, is_ip) } } - ret = Tcl_DoOneEvent(flags); - if (ret) { + if (Tcl_DoOneEvent(flags)) { return Qtrue; } else { return Qfalse; @@ -681,6 +692,8 @@ lib_restart(self) { struct tcltkip *ptr = get_ip(self); + rb_secure(4); + /* destroy the root wdiget */ ptr->return_value = Tcl_Eval(ptr->ip, "destroy ."); /* ignore ERROR */ @@ -715,6 +728,7 @@ ip_restart(self) { struct tcltkip *ptr = get_ip(self); + rb_secure(4); if (Tcl_GetMaster(ptr->ip) != (Tcl_Interp*)NULL) { /* slave IP */ return Qnil; @@ -912,6 +926,7 @@ ip_create_slave(argc, argv, self) safe = 1; } else if (safemode == Qfalse || safemode == Qnil) { safe = 0; + rb_secure(4); } else { safe = 1; } @@ -961,7 +976,7 @@ ip_delete(self) VALUE self; { struct tcltkip *ptr = get_ip(self); - + Tcl_DeleteInterp(ptr->ip); return Qnil; @@ -1131,7 +1146,6 @@ ip_invoke_real(argc, argv, obj) /* ip is deleted? */ if (Tcl_InterpDeleted(ptr->ip)) { - Tcl_ResetResult(ptr->ip); return rb_tainted_str_new2(""); } @@ -1139,7 +1153,7 @@ ip_invoke_real(argc, argv, obj) if (!Tcl_GetCommandInfo(ptr->ip, cmd, &info)) { /* if (event_loop_abort_on_exc || cmd[0] != '.') { */ if (event_loop_abort_on_exc > 0) { - /*rb_ip_raise(obj, rb_eNameError, "invalid command name `%s'", cmd);*/ + /*rb_ip_raise(obj,rb_eNameError,"invalid command name `%s'",cmd);*/ return create_ip_exc(obj, rb_eNameError, "invalid command name `%s'", cmd); } else { @@ -1349,11 +1363,11 @@ ip_invoke(argc, argv, obj) /* get result & free allocated memory */ result = *alloc_result; - if (rb_obj_is_kind_of(result, rb_eException)) { - rb_exc_raise(result); - } free(alloc_argv); free(alloc_result); + if (rb_obj_is_kind_of(result, rb_eException)) { + rb_exc_raise(result); + } return result; } diff --git a/ext/tk/lib/multi-tk.rb b/ext/tk/lib/multi-tk.rb index 12ed7aceff..7053dddef9 100644 --- a/ext/tk/lib/multi-tk.rb +++ b/ext/tk/lib/multi-tk.rb @@ -35,16 +35,16 @@ MultiTkIp_OK.freeze ################################################ # methods for construction class MultiTkIp - SLAVE_IP_ID = ['slave'.freeze, '0'].freeze + SLAVE_IP_ID = ['slave'.freeze, '0'.taint].freeze - @@IP_TABLE = {} + @@IP_TABLE = {}.taint - @@INIT_IP_ENV = [] # table of Procs - @@ADD_TK_PROCS = [] # table of [name, args, body] + @@INIT_IP_ENV = [].taint # table of Procs + @@ADD_TK_PROCS = [].taint # table of [name, args, body] - @@TK_TABLE_LIST = [] + @@TK_TABLE_LIST = [].taint - @@TK_CMD_TBL = {} + @@TK_CMD_TBL = {}.taint ###################################### @@ -64,7 +64,7 @@ class MultiTkIp rescue Exception end end - } + }.freeze ###################################### @@ -200,13 +200,13 @@ class MultiTkIp @@DEFAULT_MASTER = self.allocate @@DEFAULT_MASTER.instance_eval{ - @encoding = [] + @encoding = [].taint - @tk_windows = {} + @tk_windows = {}.taint - @tk_table_list = [] + @tk_table_list = [].taint - @slave_ip_tbl = {} + @slave_ip_tbl = {}.taint unless keys.kind_of? Hash fail ArgumentError, "expecting a Hash object for the 2nd argument" @@ -266,6 +266,7 @@ class MultiTkIp 'nested'.freeze, 'deleteHook'.freeze ].freeze + def _parse_slaveopts(keys) name = nil safe = false @@ -462,18 +463,24 @@ class MultiTkIp fail SecurityError, "slave-ip cannot create master-ip" end + if safeip == nil && $SAFE >= 4 + fail SecurityError, "cannot create master-ip at level #{$SAFE}" + end + unless keys.kind_of? Hash fail ArgumentError, "expecting a Hash object for the 2nd argument" end @encoding = [] - @tk_windows = {} - @tk_table_list = [] - @slave_ip_tbl = {} + @encoding.taint unless @encoding.tainted? + @tk_windows.taint unless @tk_windows.tainted? + @tk_table_list.taint unless @tk_table_list.tainted? + @slave_ip_tbl.taint unless @slave_ip_tbl.tainted? + name, safe, safe_opts, tk_opts = _parse_slaveopts(keys) if safeip == nil @@ -508,7 +515,10 @@ class MultiTkIp @@IP_TABLE[@threadgroup] = self _init_ip_internal(@@INIT_IP_ENV, @@ADD_TK_PROCS) - @@TK_TABLE_LIST.size.times{ @tk_table_list << {} } + @@TK_TABLE_LIST.size.times{ + (tbl = {}).tainted? || tbl.taint + @tk_table_list << tbl + } self.freeze # defend against modification end @@ -661,7 +671,10 @@ class MultiTkIp end def _add_new_tables - (@@TK_TABLE_LIST.size - @tk_table_list.size).times{ @tk_table_list << {} } + (@@TK_TABLE_LIST.size - @tk_table_list.size).times{ + (tbl = {}).tainted? || tbl.taint + @tk_table_list << tbl + } end def _init_ip_env(script) @@ -701,9 +714,9 @@ class MultiTkIp __getip._tk_table_list[id] end def self.create_table - if __getip.slave? - raise SecurityError, "slave-IP has no permission creating a new table" - end + #if __getip.slave? + # raise SecurityError, "slave-IP has no permission creating a new table" + #end id = @@TK_TABLE_LIST.size obj = Object.new @@TK_TABLE_LIST << obj diff --git a/ext/tk/lib/tk.rb b/ext/tk/lib/tk.rb index 6bd2736fe4..3dd3b3edb7 100644 --- a/ext/tk/lib/tk.rb +++ b/ext/tk/lib/tk.rb @@ -8,7 +8,7 @@ require "tcltklib" require "tkutil" module TkComm - WidgetClassNames = {} + WidgetClassNames = {}.taint None = Object.new def None.to_s @@ -18,7 +18,7 @@ module TkComm #Tk_CMDTBL = {} #Tk_WINDOWS = {} - Tk_IDs = ["00000", "00000"].freeze # [0]-cmdid, [1]-winid + Tk_IDs = ["00000".taint, "00000".taint].freeze # [0]-cmdid, [1]-winid # for backward compatibility Tk_CMDTBL = Object.new @@ -33,7 +33,7 @@ module TkComm Tk_WINDOWS.freeze self.instance_eval{ - @cmdtbl = [] + @cmdtbl = [].taint } def error_at @@ -332,6 +332,7 @@ module TkComm #Tk_CMDTBL[id] = cmd TkCore::INTERP.tk_cmd_tbl[id] = TkCore::INTERP.get_cb_entry(cmd) @cmdtbl = [] unless defined? @cmdtbl + @cmdtbl.taint unless @cmdtbl.tainted? @cmdtbl.push id return format("rb_out %s", id); end @@ -679,13 +680,13 @@ module TkCore end INTERP.instance_eval{ - @tk_cmd_tbl = {} - @tk_windows = {} + @tk_cmd_tbl = {}.taint + @tk_windows = {}.taint - @tk_table_list = [] + @tk_table_list = [].taint - @init_ip_env = [] # table of Procs - @add_tk_procs = [] # table of [name, args, body] + @init_ip_env = [].taint # table of Procs + @add_tk_procs = [].taint # table of [name, args, body] @cb_entry_class = Class.new{|c| def initialize(ip, cmd) @@ -711,7 +712,8 @@ module TkCore end def INTERP.create_table id = @tk_table_list.size - @tk_table_list << {} + (tbl = {}).tainted? || tbl.taint + @tk_table_list << tbl obj = Object.new obj.instance_eval <<-EOD def self.method_missing(m, *args) @@ -1636,7 +1638,7 @@ class TkBindTag #BTagID_TBL = {} BTagID_TBL = TkCore::INTERP.create_table - Tk_BINDTAG_ID = ["btag".freeze, "00000"].freeze + Tk_BINDTAG_ID = ["btag".freeze, "00000".taint].freeze TkCore::INTERP.init_ip_env{ BTagID_TBL.clear } @@ -1713,7 +1715,7 @@ class TkVariable #TkVar_ID_TBL = {} TkVar_CB_TBL = TkCore::INTERP.create_table TkVar_ID_TBL = TkCore::INTERP.create_table - Tk_VARIABLE_ID = ["v".freeze, "00000"].freeze + Tk_VARIABLE_ID = ["v".freeze, "00000".taint].freeze TkCore::INTERP.add_tk_procs('rb_var', 'args', "ruby [format \"TkVariable.callback %%Q!%s!\" $args]") @@ -3004,20 +3006,76 @@ module TkOptionDB end def add(pat, value, pri=None) + if $SAFE >= 4 + fail SecurityError, "can't call 'TkOptionDB.add' at $SAFE >= 4" + end tk_call 'option', 'add', pat, value, pri end def clear + if $SAFE >= 4 + fail SecurityError, "can't call 'TkOptionDB.crear' at $SAFE >= 4" + end tk_call 'option', 'clear' end def get(win, name, klass) - tk_call('option', 'get', win ,name, klass).taint + tk_call('option', 'get', win ,name, klass) end def readfile(file, pri=None) tk_call 'option', 'readfile', file, pri end module_function :add, :clear, :get, :readfile + + def read_entries(file, f_enc=nil) + if TkCore::INTERP.safe? + fail SecurityError, + "can't call 'TkOptionDB.read_entries' on a safe interpreter" + end + + i_enc = Tk.encoding() + + unless f_enc + f_enc = i_enc + end + + ent = [] + cline = '' + open(file, 'r') {|f| + while line = f.gets + cline += line.chomp! + case cline + when /\\$/ # continue + cline.chop! + next + when /^!/ # coment + cline = '' + next + when /^([^:]+):\s(.*)$/ + pat = $1 + val = $2 + p "ResourceDB: #{[pat, val].inspect}" if $DEBUG + pat = TkCore::INTERP._toUTF8(pat, f_enc) + pat = TkCore::INTERP._fromUTF8(pat, i_enc) + val = TkCore::INTERP._toUTF8(val, f_enc) + val = TkCore::INTERP._fromUTF8(val, i_enc) + ent << [pat, val] + cline = '' + else # unknown --> ignore + cline = '' + next + end + end + } + ent + end + module_function :read_entries def read_with_encoding(file, f_enc=nil, pri=None) + # try to read the file as an OptionDB file + readfile(file, pri).each{|pat, val| + add(pat, val, pri) + } + +=begin i_enc = Tk.encoding() unless f_enc @@ -3051,6 +3109,7 @@ module TkOptionDB end end } +=end end module_function :read_with_encoding @@ -4101,7 +4160,10 @@ class TkWindow".freeze].freeze + TkVirtualEventID = ["".freeze].freeze TkVirtualEventTBL = TkCore::INTERP.create_table TkCore::INTERP.init_ip_env{ TkVirtualEventTBL.clear } diff --git a/ext/tk/sample/resource.en b/ext/tk/sample/resource.en index bfdc809278..39b4013971 100644 --- a/ext/tk/sample/resource.en +++ b/ext/tk/sample/resource.en @@ -8,5 +8,6 @@ *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"} + print "Hello!! This is a sample of #{arg}.";\ + print "(<<< $SAFE=#{$SAFE})\n"} +*BTN_CMD.bye_msg: {print "($SAFE=#{$SAFE} >>>) Good-bye.(<<< $SAFE=#{$SAFE})\n"} diff --git a/ext/tk/sample/resource.ja b/ext/tk/sample/resource.ja index 8b715f36b0..a61390f95d 100644 --- a/ext/tk/sample/resource.ja +++ b/ext/tk/sample/resource.ja @@ -7,6 +7,7 @@ *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"} +*BTN_CMD.show_msg: {|arg| print "($SAFE=#{$SAFE} >>>) ";\ + print "こんにちは!! #{arg} のサンプルです.";\ + print "(<<< $SAFE=#{$SAFE})\n"} +*BTN_CMD.bye_msg: {print "($SAFE=#{$SAFE} >>>) さようなら.(<<< $SAFE=#{$SAFE})\n"} diff --git a/ext/tk/sample/safe-tk.rb b/ext/tk/sample/safe-tk.rb index 8be13a32d0..0a25c804f0 100644 --- a/ext/tk/sample/safe-tk.rb +++ b/ext/tk/sample/safe-tk.rb @@ -86,6 +86,13 @@ TkTimer.new(2000, -1, proc{p ['safe1', safe_slave1.deleted?]}).start TkTimer.new(2000, -1, proc{p ['safe2', safe_slave2.deleted?]}).start TkTimer.new(2000, -1, proc{p ['trusted', trusted_slave.deleted?]}).start +TkTimer.new(7000, 1, + proc{ + safe_slave1.eval_proc{Tk.root.destroy} + safe_slave1.delete + print "*** The safe_slave1 is deleted by the timer.\n" + }).start + TkTimer.new(10000, 1, proc{ trusted_slave.eval_proc{Tk.root.destroy} diff --git a/ext/tk/sample/tkhello.rb b/ext/tk/sample/tkhello.rb index 5188fe1c8c..ab236963e8 100644 --- a/ext/tk/sample/tkhello.rb +++ b/ext/tk/sample/tkhello.rb @@ -5,6 +5,6 @@ TkButton.new(nil, 'command' => proc{print "hello\n"}).pack('fill'=>'x') TkButton.new(nil, 'text' => 'quit', - 'command' => 'exit').pack('fill'=>'x') + 'command' => proc{exit}).pack('fill'=>'x') Tk.mainloop diff --git a/ext/tk/sample/tkmenubutton.rb b/ext/tk/sample/tkmenubutton.rb index 02a903ebb8..1c7f51000b 100644 --- a/ext/tk/sample/tkmenubutton.rb +++ b/ext/tk/sample/tkmenubutton.rb @@ -113,7 +113,6 @@ TkFrame.new{|f| } } - ############################ TkFrame.new(:borderwidth=>2, :relief=>:sunken, :height=>5).pack(:side=>:top, :fill=>:x, :padx=>20) diff --git a/ext/tk/sample/tkoptdb-safeTk.rb b/ext/tk/sample/tkoptdb-safeTk.rb index 169cd3c171..a5e394b230 100644 --- a/ext/tk/sample/tkoptdb-safeTk.rb +++ b/ext/tk/sample/tkoptdb-safeTk.rb @@ -5,6 +5,9 @@ require 'multi-tk' TkMessage.new(:text => <>>) check!! str.tainted?::#{str.tainted?}" str.untaint - print "==>#{str.tainted?} : " + print "==>#{str.tainted?} (<<< $SAFE=#{$SAFE}): " str end end @@ -38,12 +41,18 @@ TkFrame.new(:class=>'BtnFrame'){|f| pack(:padx=>5, :pady=>5) TkButton.new(:parent=>f, :widgetname=>'hello'){ command proc{ - print "($SAFE=#{$SAFE}) : " + print "($SAFE=#{$SAFE} >>>) : " cmd.show_msg(TkOptionDB.inspect) + print "(<<< $SAFE=#{$SAFE})" } pack(:fill=>:x, :padx=>10, :pady=>10) } - TkButton.new(:command=>proc{print "($SAFE=#{$SAFE}) : "; cmd.bye_msg; exit}, + TkButton.new(:command=>proc{ + print "($SAFE=#{$SAFE} >>>) : " + cmd.bye_msg + print "(<<< $SAFE=#{$SAFE} ) : " + exit + }, :parent=>f, :widgetname=>'quit'){ pack(:fill=>:x, :padx=>10, :pady=>10) } @@ -54,12 +63,18 @@ BtnFrame.new{|f| pack(:padx=>5, :pady=>5) TkButton.new(:parent=>f, :widgetname=>'hello'){ command proc{ - print "($SAFE=#{$SAFE}) : " + print "($SAFE=#{$SAFE} >>>) : " cmd.show_msg(TkOptionDB.inspect) + print "(<<< $SAFE=#{$SAFE})" } pack(:fill=>:x, :padx=>10, :pady=>10) } - TkButton.new(:command=>proc{print "($SAFE=#{$SAFE}) : "; cmd.bye_msg; exit}, + TkButton.new(:command=>proc{ + print "($SAFE=#{$SAFE} >>>) : " + cmd.bye_msg + print "(<<< $SAFE=#{$SAFE})" + exit + }, :parent=>f, :widgetname=>'quit'){ pack(:fill=>:x, :padx=>10, :pady=>10) } @@ -70,12 +85,18 @@ TkFrame.new(:class=>'BtnFrame2'){|f| pack(:padx=>5, :pady=>5) TkButton.new(:parent=>f, :widgetname=>'hello'){ command proc{ - print "($SAFE=#{$SAFE}) : " + print "($SAFE=#{$SAFE} >>>) : " cmd.show_msg(TkOptionDB.inspect) + print "(<<< $SAFE=#{$SAFE})" } pack(:fill=>:x, :padx=>10, :pady=>10) } - TkButton.new(:command=>proc{print "($SAFE=#{$SAFE}) : "; cmd.bye_msg; exit}, + TkButton.new(:command=>proc{ + print "($SAFE=#{$SAFE} >>>) : " + cmd.bye_msg + print "(<<< $SAFE=#{$SAFE})" + exit + }, :parent=>f, :widgetname=>'quit'){ pack(:fill=>:x, :padx=>10, :pady=>10) }