# safe.tcl --## This file provide a safe loading/sourcing mechanism for safe interpreters.# It implements a virtual path mecanism to hide the real pathnames from the# slave. It runs in a master interpreter and sets up data structure and# aliases that will be invoked when used from a slave interpreter.## See the safe.n man page for details.## Copyright (c) 1996-1997 Sun Microsystems, Inc.## See the file "license.terms" for information on usage and redistribution of# this file, and for a DISCLAIMER OF ALL WARRANTIES.## The implementation is based on namespaces. These naming conventions are# followed:# Private procs starts with uppercase.# Public procs are exported and starts with lowercase## Needed utilities packagepackage require opt 0.4.1# Create the safe namespacenamespace eval ::safe {# Exported API:namespace export interpCreate interpInit interpConfigure interpDelete \interpAddToAccessPath interpFindInAccessPath setLogCmd}# Helper function to resolve the dual way of specifying staticsok (either# by -noStatics or -statics 0)proc ::safe::InterpStatics {} {foreach v {Args statics noStatics} {upvar $v $v}set flag [::tcl::OptProcArgGiven -noStatics]if {$flag && (!$noStatics == !$statics)&& ([::tcl::OptProcArgGiven -statics])} {return -code error\"conflicting values given for -statics and -noStatics"}if {$flag} {return [expr {!$noStatics}]} else {return $statics}}# Helper function to resolve the dual way of specifying nested loading# (either by -nestedLoadOk or -nested 1)proc ::safe::InterpNested {} {foreach v {Args nested nestedLoadOk} {upvar $v $v}set flag [::tcl::OptProcArgGiven -nestedLoadOk]# note that the test here is the opposite of the "InterpStatics" one# (it is not -noNested... because of the wanted default value)if {$flag && (!$nestedLoadOk != !$nested)&& ([::tcl::OptProcArgGiven -nested])} {return -code error\"conflicting values given for -nested and -nestedLoadOk"}if {$flag} {# another difference with "InterpStatics"return $nestedLoadOk} else {return $nested}}###### API entry points that needs argument parsing :###### Interface/entry point function and front end for "Create"proc ::safe::interpCreate {args} {set Args [::tcl::OptKeyParse ::safe::interpCreate $args]InterpCreate $slave $accessPath \[InterpStatics] [InterpNested] $deleteHook}proc ::safe::interpInit {args} {set Args [::tcl::OptKeyParse ::safe::interpIC $args]if {![::interp exists $slave]} {return -code error "\"$slave\" is not an interpreter"}InterpInit $slave $accessPath \[InterpStatics] [InterpNested] $deleteHook}# Check that the given slave is "one of us"proc ::safe::CheckInterp {slave} {namespace upvar ::safe S$slave stateif {![info exists state] || ![::interp exists $slave]} {return -code error \"\"$slave\" is not an interpreter managed by ::safe::"}}# Interface/entry point function and front end for "Configure". This code# is awfully pedestrian because it would need more coupling and support# between the way we store the configuration values in safe::interp's and# the Opt package. Obviously we would like an OptConfigure to avoid# duplicating all this code everywhere.# -> TODO (the app should share or access easily the program/value stored# by opt)# This is even more complicated by the boolean flags with no values that# we had the bad idea to support for the sake of user simplicity in# create/init but which makes life hard in configure...# So this will be hopefully written and some integrated with opt1.0# (hopefully for tcl8.1 ?)proc ::safe::interpConfigure {args} {switch [llength $args] {1 {# If we have exactly 1 argument the semantic is to return all# the current configuration. We still call OptKeyParse though# we know that "slave" is our given argument because it also# checks for the "-help" option.set Args [::tcl::OptKeyParse ::safe::interpIC $args]CheckInterp $slavenamespace upvar ::safe S$slave statereturn [join [list \[list -accessPath $state(access_path)] \[list -statics $state(staticsok)] \[list -nested $state(nestedok)] \[list -deleteHook $state(cleanupHook)]]]}2 {# If we have exactly 2 arguments the semantic is a "configure# get"lassign $args slave arg# get the flag sub program (we 'know' about Opt's internal# representation of data)set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2]set hits [::tcl::OptHits desc $arg]if {$hits > 1} {return -code error [::tcl::OptAmbigous $desc $arg]} elseif {$hits == 0} {return -code error [::tcl::OptFlagUsage $desc $arg]}CheckInterp $slavenamespace upvar ::safe S$slave stateset item [::tcl::OptCurDesc $desc]set name [::tcl::OptName $item]switch -exact -- $name {-accessPath {return [list -accessPath $state(access_path)]}-statics {return [list -statics $state(staticsok)]}-nested {return [list -nested $state(nestedok)]}-deleteHook {return [list -deleteHook $state(cleanupHook)]}-noStatics {# it is most probably a set in fact but we would need# then to jump to the set part and it is not *sure*# that it is a set action that the user want, so force# it to use the unambigous -statics ?value? instead:return -code error\"ambigous query (get or set -noStatics ?)\use -statics instead"}-nestedLoadOk {return -code error\"ambigous query (get or set -nestedLoadOk ?)\use -nested instead"}default {return -code error "unknown flag $name (bug)"}}}default {# Otherwise we want to parse the arguments like init and# create didset Args [::tcl::OptKeyParse ::safe::interpIC $args]CheckInterp $slavenamespace upvar ::safe S$slave state# Get the current (and not the default) values of whatever has# not been given:if {![::tcl::OptProcArgGiven -accessPath]} {set doreset 1set accessPath $state(access_path)} else {set doreset 0}if {![::tcl::OptProcArgGiven -statics]&& ![::tcl::OptProcArgGiven -noStatics]} then {set statics $state(staticsok)} else {set statics [InterpStatics]}if {[::tcl::OptProcArgGiven -nested] ||[::tcl::OptProcArgGiven -nestedLoadOk]} then {set nested [InterpNested]} else {set nested $state(nestedok)}if {![::tcl::OptProcArgGiven -deleteHook]} {set deleteHook $state(cleanupHook)}# we can now reconfigure :InterpSetConfig $slave $accessPath $statics $nested $deleteHook# auto_reset the slave (to completly synch the new access_path)if {$doreset} {if {[catch {::interp eval $slave {auto_reset}} msg]} {Log $slave "auto_reset failed: $msg"} else {Log $slave "successful auto_reset" NOTICE}}}}}###### Functions that actually implements the exported APIs####### safe::InterpCreate : doing the real job## This procedure creates a safe slave and initializes it with the safe# base aliases.# NB: slave name must be simple alphanumeric string, no spaces, no (), no# {},... {because the state array is stored as part of the name}## Returns the slave name.## Optional Arguments :# + slave name : if empty, generated name will be used# + access_path: path list controlling where load/source can occur,# if empty: the master auto_path will be used.# + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx)# if 1 :static packages are ok.# + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)# if 1 : multiple levels are ok.# use the full name and no indent so auto_mkIndex can find usproc ::safe::InterpCreate {slaveaccess_pathstaticsoknestedokdeletehook} {# Create the slave.if {$slave ne ""} {::interp create -safe $slave} else {# empty argument: generate slave nameset slave [::interp create -safe]}Log $slave "Created" NOTICE# Initialize it. (returns slave name)InterpInit $slave $access_path $staticsok $nestedok $deletehook}## InterpSetConfig (was setAccessPath) :# Sets up slave virtual auto_path and corresponding structure within# the master. Also sets the tcl_library in the slave to be the first# directory in the path.# NB: If you change the path after the slave has been initialized you# probably need to call "auto_reset" in the slave in order that it gets# the right auto_index() array values.proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {global auto_path# determine and store the access path if emptyif {$access_path eq ""} {set access_path $auto_path# Make sure that tcl_library is in auto_path and at the first# position (needed by setAccessPath)set where [lsearch -exact $access_path [info library]]if {$where == -1} {# not found, add it.set access_path [linsert $access_path 0 [info library]]Log $slave "tcl_library was not in auto_path,\added it to slave's access_path" NOTICE} elseif {$where != 0} {# not first, move it firstset access_path [linsert \[lreplace $access_path $where $where] \0 [info library]]Log $slave "tcl_libray was not in first in auto_path,\moved it to front of slave's access_path" NOTICE}# Add 1st level sub dirs (will searched by auto loading from tcl# code in the slave using glob and thus fail, so we add them here# so by default it works the same).set access_path [AddSubDirs $access_path]}Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\nestedok=$nestedok deletehook=($deletehook)" NOTICEnamespace upvar ::safe S$slave state# clear old autopath if it existed# build new one# Extend the access list with the paths used to look for Tcl Modules.# We save the virtual form separately as well, as syncing it with the# slave has to be defered until the necessary commands are present for# setup.set norm_access_path {}set slave_access_path {}set map_access_path {}set remap_access_path {}set slave_tm_path {}set i 0foreach dir $access_path {set token [PathToken $i]lappend slave_access_path $tokenlappend map_access_path $token $dirlappend remap_access_path $dir $tokenlappend norm_access_path [file normalize $dir]incr i}set morepaths [::tcl::tm::list]while {[llength $morepaths]} {set addpaths $morepathsset morepaths {}foreach dir $addpaths {# Prevent the addition of dirs on the tm list to the# result if they are already known.if {[dict exists $remap_access_path $dir]} {continue}set token [PathToken $i]lappend access_path $dirlappend slave_access_path $tokenlappend map_access_path $token $dirlappend remap_access_path $dir $tokenlappend norm_access_path [file normalize $dir]lappend slave_tm_path $tokenincr i# [Bug 2854929]# Recursively find deeper paths which may contain# modules. Required to handle modules with names like# 'platform::shell', which translate into# 'platform/shell-X.tm', i.e arbitrarily deep# subdirectories.lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]}}set state(access_path) $access_pathset state(access_path,map) $map_access_pathset state(access_path,remap) $remap_access_pathset state(access_path,norm) $norm_access_pathset state(access_path,slave) $slave_access_pathset state(tm_path_slave) $slave_tm_pathset state(staticsok) $staticsokset state(nestedok) $nestedokset state(cleanupHook) $deletehookSyncAccessPath $slave}### FindInAccessPath:# Search for a real directory and returns its virtual Id (including the# "$")proc ::safe::interpFindInAccessPath {slave path} {namespace upvar ::safe S$slave stateif {![dict exists $state(access_path,remap) $path]} {return -code error "$path not found in access path $access_path"}return [dict get $state(access_path,remap) $path]}## addToAccessPath:# add (if needed) a real directory to access path and return its# virtual token (including the "$").proc ::safe::interpAddToAccessPath {slave path} {# first check if the directory is already in there# (inlined interpFindInAccessPath).namespace upvar ::safe S$slave stateif {[dict exists $state(access_path,remap) $path]} {return [dict get $state(access_path,remap) $path]}# new one, add it:set token [PathToken [llength $state(access_path)]]lappend state(access_path) $pathlappend state(access_path,slave) $tokenlappend state(access_path,map) $token $pathlappend state(access_path,remap) $path $tokenlappend state(access_path,norm) [file normalize $path]SyncAccessPath $slavereturn $token}# This procedure applies the initializations to an already existing# interpreter. It is useful when you want to install the safe base aliases# into a preexisting safe interpreter.proc ::safe::InterpInit {slaveaccess_pathstaticsoknestedokdeletehook} {# Configure will generate an access_path when access_path is empty.InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook# NB we need to add [namespace current], aliases are always absolute# paths.# These aliases let the slave load files to define new commands# This alias lets the slave use the encoding names, convertfrom,# convertto, and system, but not "encoding system <name>" to set the# system encoding.# Handling Tcl Modules, we need a restricted form of Glob.# This alias interposes on the 'exit' command and cleanly terminates# the slave.foreach {command alias} {source AliasSourceload AliasLoadencoding AliasEncodingexit interpDeleteglob AliasGlob} {::interp alias $slave $command {} [namespace current]::$alias $slave}# This alias lets the slave have access to a subset of the 'file'# command functionality.::interp expose $slave fileforeach subcommand {dirname extension rootname tail} {::interp alias $slave ::tcl::file::$subcommand {} \::safe::AliasFileSubcommand $slave $subcommand}foreach subcommand {atime attributes copy delete executable exists isdirectory isfilelink lstat mtime mkdir nativename normalize owned readable readlinkrename size stat tempfile type volumes writable} {::interp alias $slave ::tcl::file::$subcommand {} \::safe::BadSubcommand $slave file $subcommand}# Subcommands of infoforeach {subcommand alias} {nameofexecutable AliasExeName} {::interp alias $slave ::tcl::info::$subcommand \{} [namespace current]::$alias $slave}# The allowed slave variables already have been set by Tcl_MakeSafe(3)# Source init.tcl and tm.tcl into the slave, to get auto_load and# other procedures defined:if {[catch {::interp eval $slave {source [file join $tcl_library init.tcl]}} msg opt]} {Log $slave "can't source init.tcl ($msg)"return -options $opt "can't source init.tcl into slave $slave ($msg)"}if {[catch {::interp eval $slave {source [file join $tcl_library tm.tcl]}} msg opt]} {Log $slave "can't source tm.tcl ($msg)"return -options $opt "can't source tm.tcl into slave $slave ($msg)"}# Sync the paths used to search for Tcl modules. This can be done only# now, after tm.tcl was loaded.namespace upvar ::safe S$slave stateif {[llength $state(tm_path_slave)] > 0} {::interp eval $slave [list \::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]}return $slave}# Add (only if needed, avoid duplicates) 1 level of sub directories to an# existing path list. Also removes non directories from the returned# list.proc ::safe::AddSubDirs {pathList} {set res {}foreach dir $pathList {if {[file isdirectory $dir]} {# check that we don't have it yet as a children of a previous# dirif {$dir ni $res} {lappend res $dir}foreach sub [glob -directory $dir -nocomplain *] {if {[file isdirectory $sub] && ($sub ni $res)} {# new sub dir, add it !lappend res $sub}}}}return $res}# This procedure deletes a safe slave managed by Safe Tcl and cleans up# associated state:proc ::safe::interpDelete {slave} {Log $slave "About to delete" NOTICEnamespace upvar ::safe S$slave state# If the slave has a cleanup hook registered, call it. Check the# existance because we might be called to delete an interp which has# not been registered with us at allif {[info exists state(cleanupHook)]} {set hook $state(cleanupHook)if {[llength $hook]} {# remove the hook now, otherwise if the hook calls us somehow,# we'll loopunset state(cleanupHook)try {{*}$hook $slave} on error err {Log $slave "Delete hook error ($err)"}}}# Discard the global array of state associated with the slave, and# delete the interpreter.if {[info exists state]} {unset state}# if we have been called twice, the interp might have been deleted# alreadyif {[::interp exists $slave]} {::interp delete $slaveLog $slave "Deleted" NOTICE}return}# Set (or get) the logging mecanismproc ::safe::setLogCmd {args} {variable Logset la [llength $args]if {$la == 0} {return $Log} elseif {$la == 1} {set Log [lindex $args 0]} else {set Log $args}if {$Log eq ""} {# Disable logging completely. Calls to it will be compiled out# of all users.proc ::safe::Log {args} {}} else {# Activate logging, define proper command.proc ::safe::Log {slave msg {type ERROR}} {variable Log{*}$Log "$type for slave $slave : $msg"return}}}# ------------------- END OF PUBLIC METHODS ------------## Sets the slave auto_path to the master recorded value. Also sets# tcl_library to the first token of the virtual path.#proc ::safe::SyncAccessPath {slave} {namespace upvar ::safe S$slave stateset slave_access_path $state(access_path,slave)::interp eval $slave [list set auto_path $slave_access_path]Log $slave "auto_path in $slave has been set to $slave_access_path"\NOTICE# This code assumes that info library is the first element in the# list of auto_path's. See -> InterpSetConfig for the code which# ensures this condition.::interp eval $slave [list \set tcl_library [lindex $slave_access_path 0]]}# Returns the virtual token for directory number N.proc ::safe::PathToken {n} {# We need to have a ":" in the token string so [file join] on the# mac won't turn it into a relative path.return "\$p(:$n:)" ;# Form tested by case 7.2}## translate virtual path into real path#proc ::safe::TranslatePath {slave path} {namespace upvar ::safe S$slave state# somehow strip the namespaces 'functionality' out (the danger is that# we would strip valid macintosh "../" queries... :if {[string match "*::*" $path] || [string match "*..*" $path]} {return -code error "invalid characters in path $path"}# Use a cached map instead of computed local vars and subst.return [string map $state(access_path,map) $path]}# file name control (limit access to files/resources that should be a# valid tcl source file)proc ::safe::CheckFileName {slave file} {# This used to limit what can be sourced to ".tcl" and forbid files# with more than 1 dot and longer than 14 chars, but I changed that# for 8.4 as a safe interp has enough internal protection already to# allow sourcing anything. - hobbsif {![file exists $file]} {# don't tell the file pathreturn -code error "no such file or directory"}if {![file readable $file]} {# don't tell the file pathreturn -code error "not readable"}}# AliasFileSubcommand handles selected subcommands of [file] in safe# interpreters that are *almost* safe. In particular, it just acts to# prevent discovery of what home directories exist.proc ::safe::AliasFileSubcommand {slave subcommand name} {if {[string match ~* $name]} {set name ./$name}tailcall ::interp invokehidden $slave tcl:file:$subcommand $name}# AliasGlob is the target of the "glob" alias in safe interpreters.proc ::safe::AliasGlob {slave args} {Log $slave "GLOB ! $args" NOTICEset cmd {}set at 0array set got {-directory 0-nocomplain 0-join 0-tails 0-- 0}if {$::tcl_platform(platform) eq "windows"} {set dirPartRE {^(.*)[\\/]([^\\/]*)$}} else {set dirPartRE {^(.*)/([^/]*)$}}set dir {}set virtualdir {}while {$at < [llength $args]} {switch -glob -- [set opt [lindex $args $at]] {-nocomplain - -- - -join - -tails {lappend cmd $optset got($opt) 1incr at}-types - -type {lappend cmd -types [lindex $args [incr at]]incr at}-directory {if {$got($opt)} {return -code error \{"-directory" cannot be used with "-path"}}set got($opt) 1set virtualdir [lindex $args [incr at]]incr at}pkgIndex.tcl {# Oops, this is globbing a subdirectory in regular package# search. That is not wanted. Abort, handler does catch# already (because glob was not defined before). See# package.tcl, lines 484ff in tclPkgUnknown.return -code error "unknown command glob"}-* {Log $slave "Safe base rejecting glob option '$opt'"return -code error "Safe base rejecting glob option '$opt'"}default {break}}if {$got(--)} break}# Get the real path from the virtual one and check that the path is in the# access path of that slave. Done after basic argument processing so that# we know if -nocomplain is set.if {$got(-directory)} {try {set dir [TranslatePath $slave $virtualdir]DirInAccessPath $slave $dir} on error msg {Log $slave $msgif {$got(-nocomplain)} returnreturn -code error "permission denied"}lappend cmd -directory $dir}# Apply the -join semantics ourselvesif {$got(-join)} {set args [lreplace $args $at end [join [lrange $args $at end] "/"]]}# Process remaining pattern argumentsset firstPattern [llength $cmd]foreach opt [lrange $args $at end] {if {![regexp $dirPartRE $opt -> thedir thefile]} {set thedir .} elseif {[string match ~* $thedir]} {set thedir ./$thedir}if {$thedir eq "*" &&($thefile eq "pkgIndex.tcl" || $thefile eq "*.tm")} {set mapped 0foreach d [glob -directory [TranslatePath $slave $virtualdir] \-types d -tails *] {catch {DirInAccessPath $slave \[TranslatePath $slave [file join $virtualdir $d]]lappend cmd [file join $d $thefile]set mapped 1}}if {$mapped} continue}try {DirInAccessPath $slave [TranslatePath $slave \[file join $virtualdir $thedir]]} on error msg {Log $slave $msgif {$got(-nocomplain)} continuereturn -code error "permission denied"}lappend cmd $opt}Log $slave "GLOB = $cmd" NOTICEif {$got(-nocomplain) && [llength $cmd] eq $firstPattern} {return}try {set entries [::interp invokehidden $slave glob {*}$cmd]} on error msg {Log $slave $msgreturn -code error "script error"}Log $slave "GLOB < $entries" NOTICE# Translate path back to what the slave should see.set res {}set l [string length $dir]foreach p $entries {if {[string equal -length $l $dir $p]} {set p [string replace $p 0 [expr {$l-1}] $virtualdir]}lappend res $p}Log $slave "GLOB > $res" NOTICEreturn $res}# AliasSource is the target of the "source" alias in safe interpreters.proc ::safe::AliasSource {slave args} {set argc [llength $args]# Extended for handling of Tcl Modules to allow not only "source# filename", but "source -encoding E filename" as well.if {[lindex $args 0] eq "-encoding"} {incr argc -2set encoding [lindex $args 1]set at 2if {$encoding eq "identity"} {Log $slave "attempt to use the identity encoding"return -code error "permission denied"}} else {set at 0set encoding {}}if {$argc != 1} {set msg "wrong # args: should be \"source ?-encoding E? fileName\""Log $slave "$msg ($args)"return -code error $msg}set file [lindex $args $at]# get the real path from the virtual one.if {[catch {set realfile [TranslatePath $slave $file]} msg]} {Log $slave $msgreturn -code error "permission denied"}# check that the path is in the access path of that slaveif {[catch {FileInAccessPath $slave $realfile} msg]} {Log $slave $msgreturn -code error "permission denied"}# do the checks on the filename :if {[catch {CheckFileName $slave $realfile} msg]} {Log $slave "$realfile:$msg"return -code error $msg}# Passed all the tests, lets source it. Note that we do this all manually# because we want to control [info script] in the slave so information# doesn't leak so much. [Bug 2913625]set old [::interp eval $slave {info script}]set replacementMsg "script error"set code [catch {set f [open $realfile]fconfigure $f -eofchar 032円if {$encoding ne ""} {fconfigure $f -encoding $encoding}set contents [read $f]close $f::interp eval $slave [list info script $file]} msg opt]if {$code == 0} {set code [catch {::interp eval $slave $contents} msg opt]set replacementMsg $msg}catch {interp eval $slave [list info script $old]}# Note that all non-errors are fine result codes from [source], so we must# take a little care to do it properly. [Bug 2923613]if {$code == 1} {Log $slave $msgreturn -code error $replacementMsg}return -code $code -options $opt $msg}# AliasLoad is the target of the "load" alias in safe interpreters.proc ::safe::AliasLoad {slave file args} {set argc [llength $args]if {$argc > 2} {set msg "load error: too many arguments"Log $slave "$msg ($argc) {$file$args}"return -code error $msg}# package name (can be empty if file is not).set package [lindex $args 0]namespace upvar ::safe S$slave state# Determine where to load. load use a relative interp path and {}# means self, so we can directly and safely use passed arg.set target [lindex $args 1]if {$target ne ""} {# we will try to load into a sub sub interp; check that we want to# authorize that.if {!$state(nestedok)} {Log $slave "loading to a sub interp (nestedok)\disabled (trying to load $package to $target)"return -code error "permission denied (nested load)"}}# Determine what kind of load is requestedif {$file eq ""} {# static package loadingif {$package eq ""} {set msg "load error: empty filename and no package name"Log $slave $msgreturn -code error $msg}if {!$state(staticsok)} {Log $slave "static packages loading disabled\(trying to load $package to $target)"return -code error "permission denied (static package)"}} else {# file loading# get the real path from the virtual one.try {set file [TranslatePath $slave $file]} on error msg {Log $slave $msgreturn -code error "permission denied"}# check the translated pathtry {FileInAccessPath $slave $file} on error msg {Log $slave $msgreturn -code error "permission denied (path)"}}try {return [::interp invokehidden $slave load $file $package $target]} on error msg {Log $slave $msgreturn -code error $msg}}# FileInAccessPath raises an error if the file is not found in the list of# directories contained in the (master side recorded) slave's access path.# the security here relies on "file dirname" answering the proper# result... needs checking ?proc ::safe::FileInAccessPath {slave file} {namespace upvar ::safe S$slave stateset access_path $state(access_path)if {[file isdirectory $file]} {return -code error "\"$file\": is a directory"}set parent [file dirname $file]# Normalize paths for comparison since lsearch knows nothing of# potential pathname anomalies.set norm_parent [file normalize $parent]namespace upvar ::safe S$slave stateif {$norm_parent ni $state(access_path,norm)} {return -code error "\"$file\": not in access_path"}}proc ::safe::DirInAccessPath {slave dir} {namespace upvar ::safe S$slave stateset access_path $state(access_path)if {[file isfile $dir]} {return -code error "\"$dir\": is a file"}# Normalize paths for comparison since lsearch knows nothing of# potential pathname anomalies.set norm_dir [file normalize $dir]namespace upvar ::safe S$slave stateif {$norm_dir ni $state(access_path,norm)} {return -code error "\"$dir\": not in access_path"}}# This procedure is used to report an attempt to use an unsafe member of an# ensemble command.proc ::safe::BadSubcommand {slave command subcommand args} {set msg "not allowed to invoke subcommand $subcommand of $command"Log $slave $msgreturn -code error -errorcode {TCL SAFE SUBCOMMAND} $msg}# AliasEncoding is the target of the "encoding" alias in safe interpreters.proc ::safe::AliasEncoding {slave option args} {# Note that [encoding dirs] is not supported in safe slaves at allset subcommands {convertfrom convertto names system}try {set option [tcl::prefix match -error [list -level 1 -errorcode \[list TCL LOOKUP INDEX option $option]] $subcommands $option]# Special case: [encoding system] ok, but [encoding system foo] notif {$option eq "system" && [llength $args]} {return -code error -errorcode {TCL WRONGARGS} \"wrong # args: should be \"encoding system\""}} on error {msg options} {Log $slave $msgreturn -options $options $msg}tailcall ::interp invokehidden $slave encoding $option {*}$args}# Various minor hiding of platform features. [Bug 2913625]proc ::safe::AliasExeName {slave} {return ""}proc ::safe::Setup {} {###### Setup the arguments parsing###### Share the descriptionsset temp [::tcl::OptKeyRegister {{-accessPath -list {} "access path for the slave"}{-noStatics "prevent loading of statically linked pkgs"}{-statics true "loading of statically linked pkgs"}{-nestedLoadOk "allow nested loading"}{-nested false "nested loading"}{-deleteHook -script {} "delete hook"}}]# create case (slave is optional)::tcl::OptKeyRegister {{?slave? -name {} "name of the slave (optional)"}} ::safe::interpCreate# adding the flags sub programs to the command program (relying on Opt's# internal implementation details)lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp)# init and configure (slave is needed)::tcl::OptKeyRegister {{slave -name {} "name of the slave"}} ::safe::interpIC# adding the flags sub programs to the command program (relying on Opt's# internal implementation details)lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp)# temp not needed anymore::tcl::OptKeyDelete $temp###### Default: No logging.#####setLogCmd {}# Log eventually.# To enable error logging, set Log to {puts stderr} for instance,# via setLogCmd.return}namespace eval ::safe {# internal variables# Log command, set via 'setLogCmd'. Logging is disabled when empty.variable Log {}# The package maintains a state array per slave interp under its# control. The name of this array is S<interp-name>. This array is# brought into scope where needed, using 'namespace upvar'. The S# prefix is used to avoid that a slave interp called "Log" smashes# the "Log" variable.## The array's elements are:## access_path : List of paths accessible to the slave.# access_path,norm : Ditto, in normalized form.# access_path,slave : Ditto, as the path tokens as seen by the slave.# access_path,map : dict ( token -> path )# access_path,remap : dict ( path -> token )# tm_path_slave : List of TM root directories, as tokens seen by the slave.# staticsok : Value of option -statics# nestedok : Value of option -nested# cleanupHook : Value of option -deleteHook}::safe::Setup
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
1. 开源生态
2. 协作、人、软件
3. 评估模型