Update tcl to version 8.5.13
[msysgit/kirr.git] / mingw / lib / tcl8.5 / safe.tcl
blob1a340a1c28e3a4c0cc475eeada38b44124bfdb7e
1 # safe.tcl --
3 # This file provide a safe loading/sourcing mechanism for safe interpreters.
4 # It implements a virtual path mecanism to hide the real pathnames from the
5 # slave. It runs in a master interpreter and sets up data structure and
6 # aliases that will be invoked when used from a slave interpreter.
7 #
8 # See the safe.n man page for details.
10 # Copyright (c) 1996-1997 Sun Microsystems, Inc.
12 # See the file "license.terms" for information on usage and redistribution of
13 # this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 # The implementation is based on namespaces. These naming conventions are
17 # followed:
18 # Private procs starts with uppercase.
19 # Public procs are exported and starts with lowercase
22 # Needed utilities package
23 package require opt 0.4.1
25 # Create the safe namespace
26 namespace eval ::safe {
27 # Exported API:
28 namespace export interpCreate interpInit interpConfigure interpDelete \
29 interpAddToAccessPath interpFindInAccessPath setLogCmd
32 # Helper function to resolve the dual way of specifying staticsok (either
33 # by -noStatics or -statics 0)
34 proc ::safe::InterpStatics {} {
35 foreach v {Args statics noStatics} {
36 upvar $v $v
38 set flag [::tcl::OptProcArgGiven -noStatics]
39 if {$flag && (!$noStatics == !$statics)
40 && ([::tcl::OptProcArgGiven -statics])} {
41 return -code error\
42 "conflicting values given for -statics and -noStatics"
44 if {$flag} {
45 return [expr {!$noStatics}]
46 } else {
47 return $statics
51 # Helper function to resolve the dual way of specifying nested loading
52 # (either by -nestedLoadOk or -nested 1)
53 proc ::safe::InterpNested {} {
54 foreach v {Args nested nestedLoadOk} {
55 upvar $v $v
57 set flag [::tcl::OptProcArgGiven -nestedLoadOk]
58 # note that the test here is the opposite of the "InterpStatics" one
59 # (it is not -noNested... because of the wanted default value)
60 if {$flag && (!$nestedLoadOk != !$nested)
61 && ([::tcl::OptProcArgGiven -nested])} {
62 return -code error\
63 "conflicting values given for -nested and -nestedLoadOk"
65 if {$flag} {
66 # another difference with "InterpStatics"
67 return $nestedLoadOk
68 } else {
69 return $nested
73 ####
75 # API entry points that needs argument parsing :
77 ####
79 # Interface/entry point function and front end for "Create"
80 proc ::safe::interpCreate {args} {
81 set Args [::tcl::OptKeyParse ::safe::interpCreate $args]
82 InterpCreate $slave $accessPath \
83 [InterpStatics] [InterpNested] $deleteHook
86 proc ::safe::interpInit {args} {
87 set Args [::tcl::OptKeyParse ::safe::interpIC $args]
88 if {![::interp exists $slave]} {
89 return -code error "\"$slave\" is not an interpreter"
91 InterpInit $slave $accessPath \
92 [InterpStatics] [InterpNested] $deleteHook
95 # Check that the given slave is "one of us"
96 proc ::safe::CheckInterp {slave} {
97 namespace upvar ::safe S$slave state
98 if {![info exists state] || ![::interp exists $slave]} {
99 return -code error \
100 "\"$slave\" is not an interpreter managed by ::safe::"
104 # Interface/entry point function and front end for "Configure". This code
105 # is awfully pedestrian because it would need more coupling and support
106 # between the way we store the configuration values in safe::interp's and
107 # the Opt package. Obviously we would like an OptConfigure to avoid
108 # duplicating all this code everywhere.
109 # -> TODO (the app should share or access easily the program/value stored
110 # by opt)
112 # This is even more complicated by the boolean flags with no values that
113 # we had the bad idea to support for the sake of user simplicity in
114 # create/init but which makes life hard in configure...
115 # So this will be hopefully written and some integrated with opt1.0
116 # (hopefully for tcl8.1 ?)
117 proc ::safe::interpConfigure {args} {
118 switch [llength $args] {
120 # If we have exactly 1 argument the semantic is to return all
121 # the current configuration. We still call OptKeyParse though
122 # we know that "slave" is our given argument because it also
123 # checks for the "-help" option.
124 set Args [::tcl::OptKeyParse ::safe::interpIC $args]
125 CheckInterp $slave
126 namespace upvar ::safe S$slave state
128 return [join [list \
129 [list -accessPath $state(access_path)] \
130 [list -statics $state(staticsok)] \
131 [list -nested $state(nestedok)] \
132 [list -deleteHook $state(cleanupHook)]]]
135 # If we have exactly 2 arguments the semantic is a "configure
136 # get"
137 lassign $args slave arg
139 # get the flag sub program (we 'know' about Opt's internal
140 # representation of data)
141 set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2]
142 set hits [::tcl::OptHits desc $arg]
143 if {$hits > 1} {
144 return -code error [::tcl::OptAmbigous $desc $arg]
145 } elseif {$hits == 0} {
146 return -code error [::tcl::OptFlagUsage $desc $arg]
148 CheckInterp $slave
149 namespace upvar ::safe S$slave state
151 set item [::tcl::OptCurDesc $desc]
152 set name [::tcl::OptName $item]
153 switch -exact -- $name {
154 -accessPath {return [list -accessPath $state(access_path)]}
155 -statics {return [list -statics $state(staticsok)]}
156 -nested {return [list -nested $state(nestedok)]}
157 -deleteHook {return [list -deleteHook $state(cleanupHook)]}
158 -noStatics {
159 # it is most probably a set in fact but we would need
160 # then to jump to the set part and it is not *sure*
161 # that it is a set action that the user want, so force
162 # it to use the unambigous -statics ?value? instead:
163 return -code error\
164 "ambigous query (get or set -noStatics ?)\
165 use -statics instead"
167 -nestedLoadOk {
168 return -code error\
169 "ambigous query (get or set -nestedLoadOk ?)\
170 use -nested instead"
172 default {
173 return -code error "unknown flag $name (bug)"
177 default {
178 # Otherwise we want to parse the arguments like init and
179 # create did
180 set Args [::tcl::OptKeyParse ::safe::interpIC $args]
181 CheckInterp $slave
182 namespace upvar ::safe S$slave state
184 # Get the current (and not the default) values of whatever has
185 # not been given:
186 if {![::tcl::OptProcArgGiven -accessPath]} {
187 set doreset 1
188 set accessPath $state(access_path)
189 } else {
190 set doreset 0
192 if {
193 ![::tcl::OptProcArgGiven -statics]
194 && ![::tcl::OptProcArgGiven -noStatics]
196 set statics $state(staticsok)
197 } else {
198 set statics [InterpStatics]
200 if {
201 [::tcl::OptProcArgGiven -nested] ||
202 [::tcl::OptProcArgGiven -nestedLoadOk]
204 set nested [InterpNested]
205 } else {
206 set nested $state(nestedok)
208 if {![::tcl::OptProcArgGiven -deleteHook]} {
209 set deleteHook $state(cleanupHook)
211 # we can now reconfigure :
212 InterpSetConfig $slave $accessPath $statics $nested $deleteHook
213 # auto_reset the slave (to completly synch the new access_path)
214 if {$doreset} {
215 if {[catch {::interp eval $slave {auto_reset}} msg]} {
216 Log $slave "auto_reset failed: $msg"
217 } else {
218 Log $slave "successful auto_reset" NOTICE
225 ####
227 # Functions that actually implements the exported APIs
229 ####
232 # safe::InterpCreate : doing the real job
234 # This procedure creates a safe slave and initializes it with the safe
235 # base aliases.
236 # NB: slave name must be simple alphanumeric string, no spaces, no (), no
237 # {},... {because the state array is stored as part of the name}
239 # Returns the slave name.
241 # Optional Arguments :
242 # + slave name : if empty, generated name will be used
243 # + access_path: path list controlling where load/source can occur,
244 # if empty: the master auto_path will be used.
245 # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx)
246 # if 1 :static packages are ok.
247 # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)
248 # if 1 : multiple levels are ok.
250 # use the full name and no indent so auto_mkIndex can find us
251 proc ::safe::InterpCreate {
252 slave
253 access_path
254 staticsok
255 nestedok
256 deletehook
258 # Create the slave.
259 if {$slave ne ""} {
260 ::interp create -safe $slave
261 } else {
262 # empty argument: generate slave name
263 set slave [::interp create -safe]
265 Log $slave "Created" NOTICE
267 # Initialize it. (returns slave name)
268 InterpInit $slave $access_path $staticsok $nestedok $deletehook
272 # InterpSetConfig (was setAccessPath) :
273 # Sets up slave virtual auto_path and corresponding structure within
274 # the master. Also sets the tcl_library in the slave to be the first
275 # directory in the path.
276 # NB: If you change the path after the slave has been initialized you
277 # probably need to call "auto_reset" in the slave in order that it gets
278 # the right auto_index() array values.
280 proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
281 global auto_path
283 # determine and store the access path if empty
284 if {$access_path eq ""} {
285 set access_path $auto_path
287 # Make sure that tcl_library is in auto_path and at the first
288 # position (needed by setAccessPath)
289 set where [lsearch -exact $access_path [info library]]
290 if {$where == -1} {
291 # not found, add it.
292 set access_path [linsert $access_path 0 [info library]]
293 Log $slave "tcl_library was not in auto_path,\
294 added it to slave's access_path" NOTICE
295 } elseif {$where != 0} {
296 # not first, move it first
297 set access_path [linsert \
298 [lreplace $access_path $where $where] \
299 0 [info library]]
300 Log $slave "tcl_libray was not in first in auto_path,\
301 moved it to front of slave's access_path" NOTICE
304 # Add 1st level sub dirs (will searched by auto loading from tcl
305 # code in the slave using glob and thus fail, so we add them here
306 # so by default it works the same).
307 set access_path [AddSubDirs $access_path]
310 Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\
311 nestedok=$nestedok deletehook=($deletehook)" NOTICE
313 namespace upvar ::safe S$slave state
315 # clear old autopath if it existed
316 # build new one
317 # Extend the access list with the paths used to look for Tcl Modules.
318 # We save the virtual form separately as well, as syncing it with the
319 # slave has to be defered until the necessary commands are present for
320 # setup.
322 set norm_access_path {}
323 set slave_access_path {}
324 set map_access_path {}
325 set remap_access_path {}
326 set slave_tm_path {}
328 set i 0
329 foreach dir $access_path {
330 set token [PathToken $i]
331 lappend slave_access_path $token
332 lappend map_access_path $token $dir
333 lappend remap_access_path $dir $token
334 lappend norm_access_path [file normalize $dir]
335 incr i
338 set morepaths [::tcl::tm::list]
339 while {[llength $morepaths]} {
340 set addpaths $morepaths
341 set morepaths {}
343 foreach dir $addpaths {
344 # Prevent the addition of dirs on the tm list to the
345 # result if they are already known.
346 if {[dict exists $remap_access_path $dir]} {
347 continue
350 set token [PathToken $i]
351 lappend access_path $dir
352 lappend slave_access_path $token
353 lappend map_access_path $token $dir
354 lappend remap_access_path $dir $token
355 lappend norm_access_path [file normalize $dir]
356 lappend slave_tm_path $token
357 incr i
359 # [Bug 2854929]
360 # Recursively find deeper paths which may contain
361 # modules. Required to handle modules with names like
362 # 'platform::shell', which translate into
363 # 'platform/shell-X.tm', i.e arbitrarily deep
364 # subdirectories.
365 lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
369 set state(access_path) $access_path
370 set state(access_path,map) $map_access_path
371 set state(access_path,remap) $remap_access_path
372 set state(access_path,norm) $norm_access_path
373 set state(access_path,slave) $slave_access_path
374 set state(tm_path_slave) $slave_tm_path
375 set state(staticsok) $staticsok
376 set state(nestedok) $nestedok
377 set state(cleanupHook) $deletehook
379 SyncAccessPath $slave
384 # FindInAccessPath:
385 # Search for a real directory and returns its virtual Id (including the
386 # "$")
387 proc ::safe::interpFindInAccessPath {slave path} {
388 namespace upvar ::safe S$slave state
390 if {![dict exists $state(access_path,remap) $path]} {
391 return -code error "$path not found in access path $access_path"
394 return [dict get $state(access_path,remap) $path]
398 # addToAccessPath:
399 # add (if needed) a real directory to access path and return its
400 # virtual token (including the "$").
401 proc ::safe::interpAddToAccessPath {slave path} {
402 # first check if the directory is already in there
403 # (inlined interpFindInAccessPath).
404 namespace upvar ::safe S$slave state
406 if {[dict exists $state(access_path,remap) $path]} {
407 return [dict get $state(access_path,remap) $path]
410 # new one, add it:
411 set token [PathToken [llength $state(access_path)]]
413 lappend state(access_path) $path
414 lappend state(access_path,slave) $token
415 lappend state(access_path,map) $token $path
416 lappend state(access_path,remap) $path $token
417 lappend state(access_path,norm) [file normalize $path]
419 SyncAccessPath $slave
420 return $token
423 # This procedure applies the initializations to an already existing
424 # interpreter. It is useful when you want to install the safe base aliases
425 # into a preexisting safe interpreter.
426 proc ::safe::InterpInit {
427 slave
428 access_path
429 staticsok
430 nestedok
431 deletehook
433 # Configure will generate an access_path when access_path is empty.
434 InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook
436 # NB we need to add [namespace current], aliases are always absolute
437 # paths.
439 # These aliases let the slave load files to define new commands
440 # This alias lets the slave use the encoding names, convertfrom,
441 # convertto, and system, but not "encoding system <name>" to set the
442 # system encoding.
443 # Handling Tcl Modules, we need a restricted form of Glob.
444 # This alias interposes on the 'exit' command and cleanly terminates
445 # the slave.
447 foreach {command alias} {
448 source AliasSource
449 load AliasLoad
450 encoding AliasEncoding
451 exit interpDelete
452 glob AliasGlob
454 ::interp alias $slave $command {} [namespace current]::$alias $slave
457 # This alias lets the slave have access to a subset of the 'file'
458 # command functionality.
460 AliasSubset $slave file \
461 file dir.* join root.* ext.* tail path.* split
463 # Subcommands of info
464 foreach {subcommand alias} {
465 nameofexecutable AliasExeName
467 ::interp alias $slave ::tcl::info::$subcommand \
468 {} [namespace current]::$alias $slave
471 # The allowed slave variables already have been set by Tcl_MakeSafe(3)
473 # Source init.tcl and tm.tcl into the slave, to get auto_load and
474 # other procedures defined:
476 if {[catch {::interp eval $slave {
477 source [file join $tcl_library init.tcl]
478 }} msg]} {
479 Log $slave "can't source init.tcl ($msg)"
480 return -code error "can't source init.tcl into slave $slave ($msg)"
483 if {[catch {::interp eval $slave {
484 source [file join $tcl_library tm.tcl]
485 }} msg]} {
486 Log $slave "can't source tm.tcl ($msg)"
487 return -code error "can't source tm.tcl into slave $slave ($msg)"
490 # Sync the paths used to search for Tcl modules. This can be done only
491 # now, after tm.tcl was loaded.
492 namespace upvar ::safe S$slave state
493 if {[llength $state(tm_path_slave)] > 0} {
494 ::interp eval $slave [list \
495 ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
497 return $slave
500 # Add (only if needed, avoid duplicates) 1 level of sub directories to an
501 # existing path list. Also removes non directories from the returned
502 # list.
503 proc ::safe::AddSubDirs {pathList} {
504 set res {}
505 foreach dir $pathList {
506 if {[file isdirectory $dir]} {
507 # check that we don't have it yet as a children of a previous
508 # dir
509 if {$dir ni $res} {
510 lappend res $dir
512 foreach sub [glob -directory $dir -nocomplain *] {
513 if {[file isdirectory $sub] && ($sub ni $res)} {
514 # new sub dir, add it !
515 lappend res $sub
520 return $res
523 # This procedure deletes a safe slave managed by Safe Tcl and cleans up
524 # associated state:
526 proc ::safe::interpDelete {slave} {
527 Log $slave "About to delete" NOTICE
529 namespace upvar ::safe S$slave state
531 # If the slave has a cleanup hook registered, call it. Check the
532 # existance because we might be called to delete an interp which has
533 # not been registered with us at all
535 if {[info exists state(cleanupHook)]} {
536 set hook $state(cleanupHook)
537 if {[llength $hook]} {
538 # remove the hook now, otherwise if the hook calls us somehow,
539 # we'll loop
540 unset state(cleanupHook)
541 if {[catch {
542 {*}$hook $slave
543 } err]} {
544 Log $slave "Delete hook error ($err)"
549 # Discard the global array of state associated with the slave, and
550 # delete the interpreter.
552 if {[info exists state]} {
553 unset state
556 # if we have been called twice, the interp might have been deleted
557 # already
558 if {[::interp exists $slave]} {
559 ::interp delete $slave
560 Log $slave "Deleted" NOTICE
563 return
566 # Set (or get) the logging mecanism
568 proc ::safe::setLogCmd {args} {
569 variable Log
570 set la [llength $args]
571 if {$la == 0} {
572 return $Log
573 } elseif {$la == 1} {
574 set Log [lindex $args 0]
575 } else {
576 set Log $args
579 if {$Log eq ""} {
580 # Disable logging completely. Calls to it will be compiled out
581 # of all users.
582 proc ::safe::Log {args} {}
583 } else {
584 # Activate logging, define proper command.
586 proc ::safe::Log {slave msg {type ERROR}} {
587 variable Log
588 {*}$Log "$type for slave $slave : $msg"
589 return
594 # ------------------- END OF PUBLIC METHODS ------------
597 # Sets the slave auto_path to the master recorded value. Also sets
598 # tcl_library to the first token of the virtual path.
600 proc ::safe::SyncAccessPath {slave} {
601 namespace upvar ::safe S$slave state
603 set slave_access_path $state(access_path,slave)
604 ::interp eval $slave [list set auto_path $slave_access_path]
606 Log $slave "auto_path in $slave has been set to $slave_access_path"\
607 NOTICE
609 # This code assumes that info library is the first element in the
610 # list of auto_path's. See -> InterpSetConfig for the code which
611 # ensures this condition.
613 ::interp eval $slave [list \
614 set tcl_library [lindex $slave_access_path 0]]
617 # Returns the virtual token for directory number N.
618 proc ::safe::PathToken {n} {
619 # We need to have a ":" in the token string so [file join] on the
620 # mac won't turn it into a relative path.
621 return "\$p(:$n:)" ;# Form tested by case 7.2
625 # translate virtual path into real path
627 proc ::safe::TranslatePath {slave path} {
628 namespace upvar ::safe S$slave state
630 # somehow strip the namespaces 'functionality' out (the danger is that
631 # we would strip valid macintosh "../" queries... :
632 if {[string match "*::*" $path] || [string match "*..*" $path]} {
633 return -code error "invalid characters in path $path"
636 # Use a cached map instead of computed local vars and subst.
638 return [string map $state(access_path,map) $path]
641 # file name control (limit access to files/resources that should be a
642 # valid tcl source file)
643 proc ::safe::CheckFileName {slave file} {
644 # This used to limit what can be sourced to ".tcl" and forbid files
645 # with more than 1 dot and longer than 14 chars, but I changed that
646 # for 8.4 as a safe interp has enough internal protection already to
647 # allow sourcing anything. - hobbs
649 if {![file exists $file]} {
650 # don't tell the file path
651 return -code error "no such file or directory"
654 if {![file readable $file]} {
655 # don't tell the file path
656 return -code error "not readable"
660 # AliasGlob is the target of the "glob" alias in safe interpreters.
661 proc ::safe::AliasGlob {slave args} {
662 Log $slave "GLOB ! $args" NOTICE
663 set cmd {}
664 set at 0
665 array set got {
666 -directory 0
667 -nocomplain 0
668 -join 0
669 -tails 0
670 -- 0
673 if {$::tcl_platform(platform) eq "windows"} {
674 set dirPartRE {^(.*)[\\/]([^\\/]*)$}
675 } else {
676 set dirPartRE {^(.*)/([^/]*)$}
679 set dir {}
680 set virtualdir {}
682 while {$at < [llength $args]} {
683 switch -glob -- [set opt [lindex $args $at]] {
684 -nocomplain - -- - -join - -tails {
685 lappend cmd $opt
686 set got($opt) 1
687 incr at
689 -types - -type {
690 lappend cmd -types [lindex $args [incr at]]
691 incr at
693 -directory {
694 if {$got($opt)} {
695 return -code error \
696 {"-directory" cannot be used with "-path"}
698 set got($opt) 1
699 set virtualdir [lindex $args [incr at]]
700 incr at
702 pkgIndex.tcl {
703 # Oops, this is globbing a subdirectory in regular package
704 # search. That is not wanted. Abort, handler does catch
705 # already (because glob was not defined before). See
706 # package.tcl, lines 484ff in tclPkgUnknown.
707 return -code error "unknown command glob"
709 -* {
710 Log $slave "Safe base rejecting glob option '$opt'"
711 return -code error "Safe base rejecting glob option '$opt'"
713 default {
714 break
717 if {$got(--)} break
720 # Get the real path from the virtual one and check that the path is in the
721 # access path of that slave. Done after basic argument processing so that
722 # we know if -nocomplain is set.
723 if {$got(-directory)} {
724 if {[catch {
725 set dir [TranslatePath $slave $virtualdir]
726 DirInAccessPath $slave $dir
727 } msg]} {
728 Log $slave $msg
729 if {$got(-nocomplain)} {
730 return
732 return -code error "permission denied"
734 lappend cmd -directory $dir
737 # Apply the -join semantics ourselves
738 if {$got(-join)} {
739 set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
742 # Process remaining pattern arguments
743 set firstPattern [llength $cmd]
744 foreach opt [lrange $args $at end] {
745 if {![regexp $dirPartRE $opt -> thedir thefile]} {
746 set thedir .
748 if {$thedir eq "*"} {
749 set mapped 0
750 foreach d [glob -directory [TranslatePath $slave $virtualdir] \
751 -types d -tails *] {
752 catch {
753 DirInAccessPath $slave \
754 [TranslatePath $slave [file join $virtualdir $d]]
755 if {$thefile eq "pkgIndex.tcl" || $thefile eq "*.tm"} {
756 lappend cmd [file join $d $thefile]
757 set mapped 1
761 if {$mapped} continue
763 if {[catch {
764 set thedir [file join $virtualdir $thedir]
765 DirInAccessPath $slave [TranslatePath $slave $thedir]
766 } msg]} {
767 Log $slave $msg
768 if {$got(-nocomplain)} continue
769 return -code error "permission denied"
771 lappend cmd $opt
774 Log $slave "GLOB = $cmd" NOTICE
776 if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} {
777 return
779 if {[catch {
780 ::interp invokehidden $slave glob {*}$cmd
781 } msg]} {
782 Log $slave $msg
783 return -code error "script error"
786 Log $slave "GLOB < $msg" NOTICE
788 # Translate path back to what the slave should see.
789 set res {}
790 set l [string length $dir]
791 foreach p $msg {
792 if {[string equal -length $l $dir $p]} {
793 set p [string replace $p 0 [expr {$l-1}] $virtualdir]
795 lappend res $p
798 Log $slave "GLOB > $res" NOTICE
799 return $res
802 # AliasSource is the target of the "source" alias in safe interpreters.
804 proc ::safe::AliasSource {slave args} {
805 set argc [llength $args]
806 # Extended for handling of Tcl Modules to allow not only "source
807 # filename", but "source -encoding E filename" as well.
808 if {[lindex $args 0] eq "-encoding"} {
809 incr argc -2
810 set encoding [lindex $args 1]
811 set at 2
812 if {$encoding eq "identity"} {
813 Log $slave "attempt to use the identity encoding"
814 return -code error "permission denied"
816 } else {
817 set at 0
818 set encoding {}
820 if {$argc != 1} {
821 set msg "wrong # args: should be \"source ?-encoding E? fileName\""
822 Log $slave "$msg ($args)"
823 return -code error $msg
825 set file [lindex $args $at]
827 # get the real path from the virtual one.
828 if {[catch {
829 set realfile [TranslatePath $slave $file]
830 } msg]} {
831 Log $slave $msg
832 return -code error "permission denied"
835 # check that the path is in the access path of that slave
836 if {[catch {
837 FileInAccessPath $slave $realfile
838 } msg]} {
839 Log $slave $msg
840 return -code error "permission denied"
843 # do the checks on the filename :
844 if {[catch {
845 CheckFileName $slave $realfile
846 } msg]} {
847 Log $slave "$realfile:$msg"
848 return -code error $msg
851 # Passed all the tests, lets source it. Note that we do this all manually
852 # because we want to control [info script] in the slave so information
853 # doesn't leak so much. [Bug 2913625]
854 set old [::interp eval $slave {info script}]
855 set code [catch {
856 set f [open $realfile]
857 fconfigure $f -eofchar \032
858 if {$encoding ne ""} {
859 fconfigure $f -encoding $encoding
861 set contents [read $f]
862 close $f
863 ::interp eval $slave [list info script $file]
864 ::interp eval $slave $contents
865 } msg opt]
866 catch {interp eval $slave [list info script $old]}
867 # Note that all non-errors are fine result codes from [source], so we must
868 # take a little care to do it properly. [Bug 2923613]
869 if {$code == 1} {
870 Log $slave $msg
871 return -code error "script error"
873 return -code $code -options $opt $msg
876 # AliasLoad is the target of the "load" alias in safe interpreters.
878 proc ::safe::AliasLoad {slave file args} {
879 set argc [llength $args]
880 if {$argc > 2} {
881 set msg "load error: too many arguments"
882 Log $slave "$msg ($argc) {$file $args}"
883 return -code error $msg
886 # package name (can be empty if file is not).
887 set package [lindex $args 0]
889 namespace upvar ::safe S$slave state
891 # Determine where to load. load use a relative interp path and {}
892 # means self, so we can directly and safely use passed arg.
893 set target [lindex $args 1]
894 if {$target ne ""} {
895 # we will try to load into a sub sub interp; check that we want to
896 # authorize that.
897 if {!$state(nestedok)} {
898 Log $slave "loading to a sub interp (nestedok)\
899 disabled (trying to load $package to $target)"
900 return -code error "permission denied (nested load)"
904 # Determine what kind of load is requested
905 if {$file eq ""} {
906 # static package loading
907 if {$package eq ""} {
908 set msg "load error: empty filename and no package name"
909 Log $slave $msg
910 return -code error $msg
912 if {!$state(staticsok)} {
913 Log $slave "static packages loading disabled\
914 (trying to load $package to $target)"
915 return -code error "permission denied (static package)"
917 } else {
918 # file loading
920 # get the real path from the virtual one.
921 if {[catch {
922 set file [TranslatePath $slave $file]
923 } msg]} {
924 Log $slave $msg
925 return -code error "permission denied"
928 # check the translated path
929 if {[catch {
930 FileInAccessPath $slave $file
931 } msg]} {
932 Log $slave $msg
933 return -code error "permission denied (path)"
937 if {[catch {
938 ::interp invokehidden $slave load $file $package $target
939 } msg]} {
940 Log $slave $msg
941 return -code error $msg
944 return $msg
947 # FileInAccessPath raises an error if the file is not found in the list of
948 # directories contained in the (master side recorded) slave's access path.
950 # the security here relies on "file dirname" answering the proper
951 # result... needs checking ?
952 proc ::safe::FileInAccessPath {slave file} {
953 namespace upvar ::safe S$slave state
954 set access_path $state(access_path)
956 if {[file isdirectory $file]} {
957 return -code error "\"$file\": is a directory"
959 set parent [file dirname $file]
961 # Normalize paths for comparison since lsearch knows nothing of
962 # potential pathname anomalies.
963 set norm_parent [file normalize $parent]
965 namespace upvar ::safe S$slave state
966 if {$norm_parent ni $state(access_path,norm)} {
967 return -code error "\"$file\": not in access_path"
971 proc ::safe::DirInAccessPath {slave dir} {
972 namespace upvar ::safe S$slave state
973 set access_path $state(access_path)
975 if {[file isfile $dir]} {
976 return -code error "\"$dir\": is a file"
979 # Normalize paths for comparison since lsearch knows nothing of
980 # potential pathname anomalies.
981 set norm_dir [file normalize $dir]
983 namespace upvar ::safe S$slave state
984 if {$norm_dir ni $state(access_path,norm)} {
985 return -code error "\"$dir\": not in access_path"
989 # This procedure enables access from a safe interpreter to only a subset
990 # of the subcommands of a command:
992 proc ::safe::Subset {slave command okpat args} {
993 set subcommand [lindex $args 0]
994 if {[regexp $okpat $subcommand]} {
995 return [$command {*}$args]
997 set msg "not allowed to invoke subcommand $subcommand of $command"
998 Log $slave $msg
999 return -code error $msg
1002 # This procedure installs an alias in a slave that invokes "safesubset" in
1003 # the master to execute allowed subcommands. It precomputes the pattern of
1004 # allowed subcommands; you can use wildcards in the pattern if you wish to
1005 # allow subcommand abbreviation.
1007 # Syntax is: AliasSubset slave alias target subcommand1 subcommand2...
1009 proc ::safe::AliasSubset {slave alias target args} {
1010 set pat "^([join $args |])\$"
1011 ::interp alias $slave $alias {}\
1012 [namespace current]::Subset $slave $target $pat
1015 # AliasEncoding is the target of the "encoding" alias in safe interpreters.
1017 proc ::safe::AliasEncoding {slave option args} {
1018 # Careful; do not want empty option to get through to the [string equal]
1019 if {[regexp {^(name.*|convert.*|)$} $option]} {
1020 return [::interp invokehidden $slave encoding $option {*}$args]
1023 if {[string equal -length [string length $option] $option "system"]} {
1024 if {[llength $args] == 0} {
1025 # passed all the tests , lets source it:
1026 if {[catch {
1027 set sysenc [::interp invokehidden $slave encoding system]
1028 } msg]} {
1029 Log $slave $msg
1030 return -code error "script error"
1032 return $sysenc
1034 set msg "wrong # args: should be \"encoding system\""
1035 set code {TCL WRONGARGS}
1036 } else {
1037 set msg "bad option \"$option\": must be convertfrom, convertto, names, or system"
1038 set code [list TCL LOOKUP INDEX option $option]
1040 Log $slave $msg
1041 return -code error -errorcode $code $msg
1044 # Various minor hiding of platform features. [Bug 2913625]
1046 proc ::safe::AliasExeName {slave} {
1047 return ""
1050 proc ::safe::Setup {} {
1051 ####
1053 # Setup the arguments parsing
1055 ####
1057 # Share the descriptions
1058 set temp [::tcl::OptKeyRegister {
1059 {-accessPath -list {} "access path for the slave"}
1060 {-noStatics "prevent loading of statically linked pkgs"}
1061 {-statics true "loading of statically linked pkgs"}
1062 {-nestedLoadOk "allow nested loading"}
1063 {-nested false "nested loading"}
1064 {-deleteHook -script {} "delete hook"}
1067 # create case (slave is optional)
1068 ::tcl::OptKeyRegister {
1069 {?slave? -name {} "name of the slave (optional)"}
1070 } ::safe::interpCreate
1072 # adding the flags sub programs to the command program (relying on Opt's
1073 # internal implementation details)
1074 lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp)
1076 # init and configure (slave is needed)
1077 ::tcl::OptKeyRegister {
1078 {slave -name {} "name of the slave"}
1079 } ::safe::interpIC
1081 # adding the flags sub programs to the command program (relying on Opt's
1082 # internal implementation details)
1083 lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp)
1085 # temp not needed anymore
1086 ::tcl::OptKeyDelete $temp
1088 ####
1090 # Default: No logging.
1092 ####
1094 setLogCmd {}
1096 # Log eventually.
1097 # To enable error logging, set Log to {puts stderr} for instance,
1098 # via setLogCmd.
1099 return
1102 namespace eval ::safe {
1103 # internal variables
1105 # Log command, set via 'setLogCmd'. Logging is disabled when empty.
1106 variable Log {}
1108 # The package maintains a state array per slave interp under its
1109 # control. The name of this array is S<interp-name>. This array is
1110 # brought into scope where needed, using 'namespace upvar'. The S
1111 # prefix is used to avoid that a slave interp called "Log" smashes
1112 # the "Log" variable.
1114 # The array's elements are:
1116 # access_path : List of paths accessible to the slave.
1117 # access_path,norm : Ditto, in normalized form.
1118 # access_path,slave : Ditto, as the path tokens as seen by the slave.
1119 # access_path,map : dict ( token -> path )
1120 # access_path,remap : dict ( path -> token )
1121 # tm_path_slave : List of TM root directories, as tokens seen by the slave.
1122 # staticsok : Value of option -statics
1123 # nestedok : Value of option -nested
1124 # cleanupHook : Value of option -deleteHook
1127 ::safe::Setup