| 1 | # safe.tcl -- | 
|---|
| 2 | # | 
|---|
| 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. | 
|---|
| 9 | # | 
|---|
| 10 | # Copyright (c) 1996-1997 Sun Microsystems, Inc. | 
|---|
| 11 | # | 
|---|
| 12 | # See the file "license.terms" for information on usage and redistribution | 
|---|
| 13 | # of this file, and for a DISCLAIMER OF ALL WARRANTIES. | 
|---|
| 14 | # | 
|---|
| 15 | # RCS: @(#) $Id: safe.tcl,v 1.16 2006/11/03 00:34:52 hobbs Exp $ | 
|---|
| 16 |  | 
|---|
| 17 | # | 
|---|
| 18 | # The implementation is based on namespaces. These naming conventions | 
|---|
| 19 | # are followed: | 
|---|
| 20 | # Private procs starts with uppercase. | 
|---|
| 21 | # Public  procs are exported and starts with lowercase | 
|---|
| 22 | # | 
|---|
| 23 |  | 
|---|
| 24 | # Needed utilities package | 
|---|
| 25 | package require opt 0.4.1; | 
|---|
| 26 |  | 
|---|
| 27 | # Create the safe namespace | 
|---|
| 28 | namespace eval ::safe { | 
|---|
| 29 |  | 
|---|
| 30 |     # Exported API: | 
|---|
| 31 |     namespace export interpCreate interpInit interpConfigure interpDelete \ | 
|---|
| 32 |             interpAddToAccessPath interpFindInAccessPath setLogCmd | 
|---|
| 33 |  | 
|---|
| 34 |     #### | 
|---|
| 35 |     # | 
|---|
| 36 |     # Setup the arguments parsing | 
|---|
| 37 |     # | 
|---|
| 38 |     #### | 
|---|
| 39 |  | 
|---|
| 40 |     # Make sure that our temporary variable is local to this | 
|---|
| 41 |     # namespace.  [Bug 981733] | 
|---|
| 42 |     variable temp | 
|---|
| 43 |  | 
|---|
| 44 |     # Share the descriptions | 
|---|
| 45 |     set temp [::tcl::OptKeyRegister { | 
|---|
| 46 |         {-accessPath -list {} "access path for the slave"} | 
|---|
| 47 |         {-noStatics "prevent loading of statically linked pkgs"} | 
|---|
| 48 |         {-statics true "loading of statically linked pkgs"} | 
|---|
| 49 |         {-nestedLoadOk "allow nested loading"} | 
|---|
| 50 |         {-nested false "nested loading"} | 
|---|
| 51 |         {-deleteHook -script {} "delete hook"} | 
|---|
| 52 |     }] | 
|---|
| 53 |  | 
|---|
| 54 |     # create case (slave is optional) | 
|---|
| 55 |     ::tcl::OptKeyRegister { | 
|---|
| 56 |         {?slave? -name {} "name of the slave (optional)"} | 
|---|
| 57 |     } ::safe::interpCreate | 
|---|
| 58 |     # adding the flags sub programs to the command program | 
|---|
| 59 |     # (relying on Opt's internal implementation details) | 
|---|
| 60 |     lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) | 
|---|
| 61 |  | 
|---|
| 62 |     # init and configure (slave is needed) | 
|---|
| 63 |     ::tcl::OptKeyRegister { | 
|---|
| 64 |         {slave -name {} "name of the slave"} | 
|---|
| 65 |     } ::safe::interpIC | 
|---|
| 66 |     # adding the flags sub programs to the command program | 
|---|
| 67 |     # (relying on Opt's internal implementation details) | 
|---|
| 68 |     lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) | 
|---|
| 69 |     # temp not needed anymore | 
|---|
| 70 |     ::tcl::OptKeyDelete $temp | 
|---|
| 71 |  | 
|---|
| 72 |  | 
|---|
| 73 |     # Helper function to resolve the dual way of specifying staticsok | 
|---|
| 74 |     # (either by -noStatics or -statics 0) | 
|---|
| 75 |     proc InterpStatics {} { | 
|---|
| 76 |         foreach v {Args statics noStatics} { | 
|---|
| 77 |             upvar $v $v | 
|---|
| 78 |         } | 
|---|
| 79 |         set flag [::tcl::OptProcArgGiven -noStatics]; | 
|---|
| 80 |         if {$flag && (!$noStatics == !$statics)  | 
|---|
| 81 |                   && ([::tcl::OptProcArgGiven -statics])} { | 
|---|
| 82 |             return -code error\ | 
|---|
| 83 |                     "conflicting values given for -statics and -noStatics" | 
|---|
| 84 |         } | 
|---|
| 85 |         if {$flag} { | 
|---|
| 86 |             return [expr {!$noStatics}] | 
|---|
| 87 |         } else { | 
|---|
| 88 |             return $statics | 
|---|
| 89 |         } | 
|---|
| 90 |     } | 
|---|
| 91 |  | 
|---|
| 92 |     # Helper function to resolve the dual way of specifying nested loading | 
|---|
| 93 |     # (either by -nestedLoadOk or -nested 1) | 
|---|
| 94 |     proc InterpNested {} { | 
|---|
| 95 |         foreach v {Args nested nestedLoadOk} { | 
|---|
| 96 |             upvar $v $v | 
|---|
| 97 |         } | 
|---|
| 98 |         set flag [::tcl::OptProcArgGiven -nestedLoadOk]; | 
|---|
| 99 |         # note that the test here is the opposite of the "InterpStatics" | 
|---|
| 100 |         # one (it is not -noNested... because of the wanted default value) | 
|---|
| 101 |         if {$flag && (!$nestedLoadOk != !$nested)  | 
|---|
| 102 |                   && ([::tcl::OptProcArgGiven -nested])} { | 
|---|
| 103 |             return -code error\ | 
|---|
| 104 |                     "conflicting values given for -nested and -nestedLoadOk" | 
|---|
| 105 |         } | 
|---|
| 106 |         if {$flag} { | 
|---|
| 107 |             # another difference with "InterpStatics" | 
|---|
| 108 |             return $nestedLoadOk | 
|---|
| 109 |         } else { | 
|---|
| 110 |             return $nested | 
|---|
| 111 |         } | 
|---|
| 112 |     } | 
|---|
| 113 |  | 
|---|
| 114 |     #### | 
|---|
| 115 |     # | 
|---|
| 116 |     #  API entry points that needs argument parsing : | 
|---|
| 117 |     # | 
|---|
| 118 |     #### | 
|---|
| 119 |  | 
|---|
| 120 |  | 
|---|
| 121 |     # Interface/entry point function and front end for "Create" | 
|---|
| 122 |     proc interpCreate {args} { | 
|---|
| 123 |         set Args [::tcl::OptKeyParse ::safe::interpCreate $args] | 
|---|
| 124 |         InterpCreate $slave $accessPath \ | 
|---|
| 125 |                 [InterpStatics] [InterpNested] $deleteHook | 
|---|
| 126 |     } | 
|---|
| 127 |  | 
|---|
| 128 |     proc interpInit {args} { | 
|---|
| 129 |         set Args [::tcl::OptKeyParse ::safe::interpIC $args] | 
|---|
| 130 |         if {![::interp exists $slave]} { | 
|---|
| 131 |             return -code error "\"$slave\" is not an interpreter" | 
|---|
| 132 |         } | 
|---|
| 133 |         InterpInit $slave $accessPath \ | 
|---|
| 134 |                 [InterpStatics] [InterpNested] $deleteHook; | 
|---|
| 135 |     } | 
|---|
| 136 |  | 
|---|
| 137 |     proc CheckInterp {slave} { | 
|---|
| 138 |         if {![IsInterp $slave]} { | 
|---|
| 139 |             return -code error \ | 
|---|
| 140 |                     "\"$slave\" is not an interpreter managed by ::safe::" | 
|---|
| 141 |         } | 
|---|
| 142 |     } | 
|---|
| 143 |  | 
|---|
| 144 |     # Interface/entry point function and front end for "Configure" | 
|---|
| 145 |     # This code is awfully pedestrian because it would need | 
|---|
| 146 |     # more coupling and support between the way we store the | 
|---|
| 147 |     # configuration values in safe::interp's and the Opt package | 
|---|
| 148 |     # Obviously we would like an OptConfigure | 
|---|
| 149 |     # to avoid duplicating all this code everywhere. -> TODO | 
|---|
| 150 |     # (the app should share or access easily the program/value | 
|---|
| 151 |     #  stored by opt) | 
|---|
| 152 |     # This is even more complicated by the boolean flags with no values | 
|---|
| 153 |     # that we had the bad idea to support for the sake of user simplicity | 
|---|
| 154 |     # in create/init but which makes life hard in configure... | 
|---|
| 155 |     # So this will be hopefully written and some integrated with opt1.0 | 
|---|
| 156 |     # (hopefully for tcl8.1 ?) | 
|---|
| 157 |     proc interpConfigure {args} { | 
|---|
| 158 |         switch [llength $args] { | 
|---|
| 159 |             1 { | 
|---|
| 160 |                 # If we have exactly 1 argument | 
|---|
| 161 |                 # the semantic is to return all the current configuration | 
|---|
| 162 |                 # We still call OptKeyParse though we know that "slave" | 
|---|
| 163 |                 # is our given argument because it also checks | 
|---|
| 164 |                 # for the "-help" option. | 
|---|
| 165 |                 set Args [::tcl::OptKeyParse ::safe::interpIC $args] | 
|---|
| 166 |                 CheckInterp $slave | 
|---|
| 167 |                 set res {} | 
|---|
| 168 |                 lappend res [list -accessPath [Set [PathListName $slave]]] | 
|---|
| 169 |                 lappend res [list -statics    [Set [StaticsOkName $slave]]] | 
|---|
| 170 |                 lappend res [list -nested     [Set [NestedOkName $slave]]] | 
|---|
| 171 |                 lappend res [list -deleteHook [Set [DeleteHookName $slave]]] | 
|---|
| 172 |                 join $res | 
|---|
| 173 |             } | 
|---|
| 174 |             2 { | 
|---|
| 175 |                 # If we have exactly 2 arguments | 
|---|
| 176 |                 # the semantic is a "configure get" | 
|---|
| 177 |                 ::tcl::Lassign $args slave arg | 
|---|
| 178 |                 # get the flag sub program (we 'know' about Opt's internal | 
|---|
| 179 |                 # representation of data) | 
|---|
| 180 |                 set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] | 
|---|
| 181 |                 set hits [::tcl::OptHits desc $arg] | 
|---|
| 182 |                 if {$hits > 1} { | 
|---|
| 183 |                     return -code error [::tcl::OptAmbigous $desc $arg] | 
|---|
| 184 |                 } elseif {$hits == 0} { | 
|---|
| 185 |                     return -code error [::tcl::OptFlagUsage $desc $arg] | 
|---|
| 186 |                 } | 
|---|
| 187 |                 CheckInterp $slave | 
|---|
| 188 |                 set item [::tcl::OptCurDesc $desc] | 
|---|
| 189 |                 set name [::tcl::OptName $item] | 
|---|
| 190 |                 switch -exact -- $name { | 
|---|
| 191 |                     -accessPath { | 
|---|
| 192 |                         return [list -accessPath [Set [PathListName $slave]]] | 
|---|
| 193 |                     } | 
|---|
| 194 |                     -statics { | 
|---|
| 195 |                         return [list -statics    [Set [StaticsOkName $slave]]] | 
|---|
| 196 |                     } | 
|---|
| 197 |                     -nested { | 
|---|
| 198 |                         return [list -nested     [Set [NestedOkName $slave]]] | 
|---|
| 199 |                     } | 
|---|
| 200 |                     -deleteHook { | 
|---|
| 201 |                         return [list -deleteHook [Set [DeleteHookName $slave]]] | 
|---|
| 202 |                     } | 
|---|
| 203 |                     -noStatics { | 
|---|
| 204 |                         # it is most probably a set in fact | 
|---|
| 205 |                         # but we would need then to jump to the set part | 
|---|
| 206 |                         # and it is not *sure* that it is a set action | 
|---|
| 207 |                         # that the user want, so force it to use the | 
|---|
| 208 |                         # unambigous -statics ?value? instead: | 
|---|
| 209 |                         return -code error\ | 
|---|
| 210 |                                 "ambigous query (get or set -noStatics ?)\ | 
|---|
| 211 |                                 use -statics instead" | 
|---|
| 212 |                     } | 
|---|
| 213 |                     -nestedLoadOk { | 
|---|
| 214 |                         return -code error\ | 
|---|
| 215 |                                 "ambigous query (get or set -nestedLoadOk ?)\ | 
|---|
| 216 |                                 use -nested instead" | 
|---|
| 217 |                     } | 
|---|
| 218 |                     default { | 
|---|
| 219 |                         return -code error "unknown flag $name (bug)" | 
|---|
| 220 |                     } | 
|---|
| 221 |                 } | 
|---|
| 222 |             } | 
|---|
| 223 |             default { | 
|---|
| 224 |                 # Otherwise we want to parse the arguments like init and create | 
|---|
| 225 |                 # did | 
|---|
| 226 |                 set Args [::tcl::OptKeyParse ::safe::interpIC $args] | 
|---|
| 227 |                 CheckInterp $slave | 
|---|
| 228 |                 # Get the current (and not the default) values of | 
|---|
| 229 |                 # whatever has not been given: | 
|---|
| 230 |                 if {![::tcl::OptProcArgGiven -accessPath]} { | 
|---|
| 231 |                     set doreset 1 | 
|---|
| 232 |                     set accessPath [Set [PathListName $slave]] | 
|---|
| 233 |                 } else { | 
|---|
| 234 |                     set doreset 0 | 
|---|
| 235 |                 } | 
|---|
| 236 |                 if {(![::tcl::OptProcArgGiven -statics]) \ | 
|---|
| 237 |                         && (![::tcl::OptProcArgGiven -noStatics]) } { | 
|---|
| 238 |                     set statics    [Set [StaticsOkName $slave]] | 
|---|
| 239 |                 } else { | 
|---|
| 240 |                     set statics    [InterpStatics] | 
|---|
| 241 |                 } | 
|---|
| 242 |                 if {([::tcl::OptProcArgGiven -nested]) \ | 
|---|
| 243 |                         || ([::tcl::OptProcArgGiven -nestedLoadOk]) } { | 
|---|
| 244 |                     set nested     [InterpNested] | 
|---|
| 245 |                 } else { | 
|---|
| 246 |                     set nested     [Set [NestedOkName $slave]] | 
|---|
| 247 |                 } | 
|---|
| 248 |                 if {![::tcl::OptProcArgGiven -deleteHook]} { | 
|---|
| 249 |                     set deleteHook [Set [DeleteHookName $slave]] | 
|---|
| 250 |                 } | 
|---|
| 251 |                 # we can now reconfigure : | 
|---|
| 252 |                 InterpSetConfig $slave $accessPath $statics $nested $deleteHook | 
|---|
| 253 |                 # auto_reset the slave (to completly synch the new access_path) | 
|---|
| 254 |                 if {$doreset} { | 
|---|
| 255 |                     if {[catch {::interp eval $slave {auto_reset}} msg]} { | 
|---|
| 256 |                         Log $slave "auto_reset failed: $msg" | 
|---|
| 257 |                     } else { | 
|---|
| 258 |                         Log $slave "successful auto_reset" NOTICE | 
|---|
| 259 |                     } | 
|---|
| 260 |                 } | 
|---|
| 261 |             } | 
|---|
| 262 |         } | 
|---|
| 263 |     } | 
|---|
| 264 |  | 
|---|
| 265 |  | 
|---|
| 266 |     #### | 
|---|
| 267 |     # | 
|---|
| 268 |     #  Functions that actually implements the exported APIs | 
|---|
| 269 |     # | 
|---|
| 270 |     #### | 
|---|
| 271 |  | 
|---|
| 272 |  | 
|---|
| 273 |     # | 
|---|
| 274 |     # safe::InterpCreate : doing the real job | 
|---|
| 275 |     # | 
|---|
| 276 |     # This procedure creates a safe slave and initializes it with the | 
|---|
| 277 |     # safe base aliases. | 
|---|
| 278 |     # NB: slave name must be simple alphanumeric string, no spaces, | 
|---|
| 279 |     # no (), no {},...  {because the state array is stored as part of the name} | 
|---|
| 280 |     # | 
|---|
| 281 |     # Returns the slave name. | 
|---|
| 282 |     # | 
|---|
| 283 |     # Optional Arguments :  | 
|---|
| 284 |     # + slave name : if empty, generated name will be used | 
|---|
| 285 |     # + access_path: path list controlling where load/source can occur, | 
|---|
| 286 |     #                if empty: the master auto_path will be used. | 
|---|
| 287 |     # + staticsok  : flag, if 0 :no static package can be loaded (load {} Xxx) | 
|---|
| 288 |     #                      if 1 :static packages are ok. | 
|---|
| 289 |     # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub) | 
|---|
| 290 |     #                      if 1 : multiple levels are ok. | 
|---|
| 291 |      | 
|---|
| 292 |     # use the full name and no indent so auto_mkIndex can find us | 
|---|
| 293 |     proc ::safe::InterpCreate { | 
|---|
| 294 |         slave  | 
|---|
| 295 |         access_path | 
|---|
| 296 |         staticsok | 
|---|
| 297 |         nestedok | 
|---|
| 298 |         deletehook | 
|---|
| 299 |     } { | 
|---|
| 300 |         # Create the slave. | 
|---|
| 301 |         if {$slave ne ""} { | 
|---|
| 302 |             ::interp create -safe $slave | 
|---|
| 303 |         } else { | 
|---|
| 304 |             # empty argument: generate slave name | 
|---|
| 305 |             set slave [::interp create -safe] | 
|---|
| 306 |         } | 
|---|
| 307 |         Log $slave "Created" NOTICE | 
|---|
| 308 |  | 
|---|
| 309 |         # Initialize it. (returns slave name) | 
|---|
| 310 |         InterpInit $slave $access_path $staticsok $nestedok $deletehook | 
|---|
| 311 |     } | 
|---|
| 312 |  | 
|---|
| 313 |  | 
|---|
| 314 |     # | 
|---|
| 315 |     # InterpSetConfig (was setAccessPath) : | 
|---|
| 316 |     #    Sets up slave virtual auto_path and corresponding structure | 
|---|
| 317 |     #    within the master. Also sets the tcl_library in the slave | 
|---|
| 318 |     #    to be the first directory in the path. | 
|---|
| 319 |     #    Nb: If you change the path after the slave has been initialized | 
|---|
| 320 |     #    you probably need to call "auto_reset" in the slave in order that it | 
|---|
| 321 |     #    gets the right auto_index() array values. | 
|---|
| 322 |  | 
|---|
| 323 |     proc ::safe::InterpSetConfig {slave access_path staticsok\ | 
|---|
| 324 |             nestedok deletehook} { | 
|---|
| 325 |  | 
|---|
| 326 |         # determine and store the access path if empty | 
|---|
| 327 |         if {$access_path eq ""} { | 
|---|
| 328 |             set access_path [uplevel \#0 set auto_path] | 
|---|
| 329 |             # Make sure that tcl_library is in auto_path | 
|---|
| 330 |             # and at the first position (needed by setAccessPath) | 
|---|
| 331 |             set where [lsearch -exact $access_path [info library]] | 
|---|
| 332 |             if {$where == -1} { | 
|---|
| 333 |                 # not found, add it. | 
|---|
| 334 |                 set access_path [concat [list [info library]] $access_path] | 
|---|
| 335 |                 Log $slave "tcl_library was not in auto_path,\ | 
|---|
| 336 |                         added it to slave's access_path" NOTICE | 
|---|
| 337 |             } elseif {$where != 0} { | 
|---|
| 338 |                 # not first, move it first | 
|---|
| 339 |                 set access_path [concat [list [info library]]\ | 
|---|
| 340 |                         [lreplace $access_path $where $where]] | 
|---|
| 341 |                 Log $slave "tcl_libray was not in first in auto_path,\ | 
|---|
| 342 |                         moved it to front of slave's access_path" NOTICE | 
|---|
| 343 |              | 
|---|
| 344 |             } | 
|---|
| 345 |  | 
|---|
| 346 |             # Add 1st level sub dirs (will searched by auto loading from tcl | 
|---|
| 347 |             # code in the slave using glob and thus fail, so we add them | 
|---|
| 348 |             # here so by default it works the same). | 
|---|
| 349 |             set access_path [AddSubDirs $access_path] | 
|---|
| 350 |         } | 
|---|
| 351 |  | 
|---|
| 352 |         Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\ | 
|---|
| 353 |                 nestedok=$nestedok deletehook=($deletehook)" NOTICE | 
|---|
| 354 |  | 
|---|
| 355 |         # clear old autopath if it existed | 
|---|
| 356 |         set nname [PathNumberName $slave] | 
|---|
| 357 |         if {[Exists $nname]} { | 
|---|
| 358 |             set n [Set $nname] | 
|---|
| 359 |             for {set i 0} {$i<$n} {incr i} { | 
|---|
| 360 |                 Unset [PathToken $i $slave] | 
|---|
| 361 |             } | 
|---|
| 362 |         } | 
|---|
| 363 |  | 
|---|
| 364 |         # build new one | 
|---|
| 365 |         set slave_auto_path {} | 
|---|
| 366 |         set i 0 | 
|---|
| 367 |         foreach dir $access_path { | 
|---|
| 368 |             Set [PathToken $i $slave] $dir | 
|---|
| 369 |             lappend slave_auto_path "\$[PathToken $i]" | 
|---|
| 370 |             incr i | 
|---|
| 371 |         } | 
|---|
| 372 |         Set $nname $i | 
|---|
| 373 |         Set [PathListName $slave] $access_path | 
|---|
| 374 |         Set [VirtualPathListName $slave] $slave_auto_path | 
|---|
| 375 |  | 
|---|
| 376 |         Set [StaticsOkName $slave] $staticsok | 
|---|
| 377 |         Set [NestedOkName $slave] $nestedok | 
|---|
| 378 |         Set [DeleteHookName $slave] $deletehook | 
|---|
| 379 |  | 
|---|
| 380 |         SyncAccessPath $slave | 
|---|
| 381 |     } | 
|---|
| 382 |  | 
|---|
| 383 |     # | 
|---|
| 384 |     # | 
|---|
| 385 |     # FindInAccessPath: | 
|---|
| 386 |     #    Search for a real directory and returns its virtual Id | 
|---|
| 387 |     #    (including the "$") | 
|---|
| 388 | proc ::safe::interpFindInAccessPath {slave path} { | 
|---|
| 389 |         set access_path [GetAccessPath $slave] | 
|---|
| 390 |         set where [lsearch -exact $access_path $path] | 
|---|
| 391 |         if {$where == -1} { | 
|---|
| 392 |             return -code error "$path not found in access path $access_path" | 
|---|
| 393 |         } | 
|---|
| 394 |         return "\$[PathToken $where]" | 
|---|
| 395 |     } | 
|---|
| 396 |  | 
|---|
| 397 |     # | 
|---|
| 398 |     # addToAccessPath: | 
|---|
| 399 |     #    add (if needed) a real directory to access path | 
|---|
| 400 |     #    and return its virtual token (including the "$"). | 
|---|
| 401 | proc ::safe::interpAddToAccessPath {slave path} { | 
|---|
| 402 |         # first check if the directory is already in there | 
|---|
| 403 |         if {![catch {interpFindInAccessPath $slave $path} res]} { | 
|---|
| 404 |             return $res | 
|---|
| 405 |         } | 
|---|
| 406 |         # new one, add it: | 
|---|
| 407 |         set nname [PathNumberName $slave] | 
|---|
| 408 |         set n [Set $nname] | 
|---|
| 409 |         Set [PathToken $n $slave] $path | 
|---|
| 410 |  | 
|---|
| 411 |         set token "\$[PathToken $n]" | 
|---|
| 412 |  | 
|---|
| 413 |         Lappend [VirtualPathListName $slave] $token | 
|---|
| 414 |         Lappend [PathListName $slave] $path | 
|---|
| 415 |         Set $nname [expr {$n+1}] | 
|---|
| 416 |  | 
|---|
| 417 |         SyncAccessPath $slave | 
|---|
| 418 |  | 
|---|
| 419 |         return $token | 
|---|
| 420 |     } | 
|---|
| 421 |  | 
|---|
| 422 |     # This procedure applies the initializations to an already existing | 
|---|
| 423 |     # interpreter. It is useful when you want to install the safe base | 
|---|
| 424 |     # aliases into a preexisting safe interpreter. | 
|---|
| 425 |     proc ::safe::InterpInit { | 
|---|
| 426 |         slave  | 
|---|
| 427 |         access_path | 
|---|
| 428 |         staticsok | 
|---|
| 429 |         nestedok | 
|---|
| 430 |         deletehook | 
|---|
| 431 |     } { | 
|---|
| 432 |  | 
|---|
| 433 |         # Configure will generate an access_path when access_path is | 
|---|
| 434 |         # empty. | 
|---|
| 435 |         InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook | 
|---|
| 436 |  | 
|---|
| 437 |         # These aliases let the slave load files to define new commands | 
|---|
| 438 |  | 
|---|
| 439 |         # NB we need to add [namespace current], aliases are always | 
|---|
| 440 |         # absolute paths. | 
|---|
| 441 |         ::interp alias $slave source {} [namespace current]::AliasSource $slave | 
|---|
| 442 |         ::interp alias $slave load {} [namespace current]::AliasLoad $slave | 
|---|
| 443 |  | 
|---|
| 444 |         # This alias lets the slave use the encoding names, convertfrom, | 
|---|
| 445 |         # convertto, and system, but not "encoding system <name>" to set | 
|---|
| 446 |         # the system encoding. | 
|---|
| 447 |  | 
|---|
| 448 |         ::interp alias $slave encoding {} [namespace current]::AliasEncoding \ | 
|---|
| 449 |                 $slave | 
|---|
| 450 |  | 
|---|
| 451 |         # This alias lets the slave have access to a subset of the 'file' | 
|---|
| 452 |         # command functionality. | 
|---|
| 453 |  | 
|---|
| 454 |         AliasSubset $slave file file dir.* join root.* ext.* tail \ | 
|---|
| 455 |                 path.* split | 
|---|
| 456 |  | 
|---|
| 457 |         # This alias interposes on the 'exit' command and cleanly terminates | 
|---|
| 458 |         # the slave. | 
|---|
| 459 |  | 
|---|
| 460 |         ::interp alias $slave exit {} [namespace current]::interpDelete $slave | 
|---|
| 461 |  | 
|---|
| 462 |         # The allowed slave variables already have been set | 
|---|
| 463 |         # by Tcl_MakeSafe(3) | 
|---|
| 464 |  | 
|---|
| 465 |  | 
|---|
| 466 |         # Source init.tcl into the slave, to get auto_load and other | 
|---|
| 467 |         # procedures defined: | 
|---|
| 468 |  | 
|---|
| 469 |         if {[catch {::interp eval $slave\ | 
|---|
| 470 |                 {source [file join $tcl_library init.tcl]}} msg]} { | 
|---|
| 471 |             Log $slave "can't source init.tcl ($msg)" | 
|---|
| 472 |             error "can't source init.tcl into slave $slave ($msg)" | 
|---|
| 473 |         } | 
|---|
| 474 |  | 
|---|
| 475 |         return $slave | 
|---|
| 476 |     } | 
|---|
| 477 |  | 
|---|
| 478 |  | 
|---|
| 479 |     # Add (only if needed, avoid duplicates) 1 level of | 
|---|
| 480 |     # sub directories to an existing path list. | 
|---|
| 481 |     # Also removes non directories from the returned list. | 
|---|
| 482 |     proc AddSubDirs {pathList} { | 
|---|
| 483 |         set res {} | 
|---|
| 484 |         foreach dir $pathList { | 
|---|
| 485 |             if {[file isdirectory $dir]} { | 
|---|
| 486 |                 # check that we don't have it yet as a children | 
|---|
| 487 |                 # of a previous dir | 
|---|
| 488 |                 if {[lsearch -exact $res $dir]<0} { | 
|---|
| 489 |                     lappend res $dir | 
|---|
| 490 |                 } | 
|---|
| 491 |                 foreach sub [glob -directory $dir -nocomplain *] { | 
|---|
| 492 |                     if {([file isdirectory $sub]) \ | 
|---|
| 493 |                             && ([lsearch -exact $res $sub]<0) } { | 
|---|
| 494 |                         # new sub dir, add it ! | 
|---|
| 495 |                         lappend res $sub | 
|---|
| 496 |                     } | 
|---|
| 497 |                 } | 
|---|
| 498 |             } | 
|---|
| 499 |         } | 
|---|
| 500 |         return $res | 
|---|
| 501 |     } | 
|---|
| 502 |  | 
|---|
| 503 |     # This procedure deletes a safe slave managed by Safe Tcl and | 
|---|
| 504 |     # cleans up associated state: | 
|---|
| 505 |  | 
|---|
| 506 | proc ::safe::interpDelete {slave} { | 
|---|
| 507 |  | 
|---|
| 508 |         Log $slave "About to delete" NOTICE | 
|---|
| 509 |  | 
|---|
| 510 |         # If the slave has a cleanup hook registered, call it. | 
|---|
| 511 |         # check the existance because we might be called to delete an interp | 
|---|
| 512 |         # which has not been registered with us at all | 
|---|
| 513 |         set hookname [DeleteHookName $slave] | 
|---|
| 514 |         if {[Exists $hookname]} { | 
|---|
| 515 |             set hook [Set $hookname] | 
|---|
| 516 |             if {![::tcl::Lempty $hook]} { | 
|---|
| 517 |                 # remove the hook now, otherwise if the hook | 
|---|
| 518 |                 # calls us somehow, we'll loop | 
|---|
| 519 |                 Unset $hookname | 
|---|
| 520 |                 if {[catch {{*}$hook $slave} err]} { | 
|---|
| 521 |                     Log $slave "Delete hook error ($err)" | 
|---|
| 522 |                 } | 
|---|
| 523 |             } | 
|---|
| 524 |         } | 
|---|
| 525 |  | 
|---|
| 526 |         # Discard the global array of state associated with the slave, and | 
|---|
| 527 |         # delete the interpreter. | 
|---|
| 528 |  | 
|---|
| 529 |         set statename [InterpStateName $slave] | 
|---|
| 530 |         if {[Exists $statename]} { | 
|---|
| 531 |             Unset $statename | 
|---|
| 532 |         } | 
|---|
| 533 |  | 
|---|
| 534 |         # if we have been called twice, the interp might have been deleted | 
|---|
| 535 |         # already | 
|---|
| 536 |         if {[::interp exists $slave]} { | 
|---|
| 537 |             ::interp delete $slave | 
|---|
| 538 |             Log $slave "Deleted" NOTICE | 
|---|
| 539 |         } | 
|---|
| 540 |  | 
|---|
| 541 |         return | 
|---|
| 542 |     } | 
|---|
| 543 |  | 
|---|
| 544 |     # Set (or get) the loging mecanism  | 
|---|
| 545 |  | 
|---|
| 546 | proc ::safe::setLogCmd {args} { | 
|---|
| 547 |     variable Log | 
|---|
| 548 |     if {[llength $args] == 0} { | 
|---|
| 549 |         return $Log | 
|---|
| 550 |     } else { | 
|---|
| 551 |         if {[llength $args] == 1} { | 
|---|
| 552 |             set Log [lindex $args 0] | 
|---|
| 553 |         } else { | 
|---|
| 554 |             set Log $args | 
|---|
| 555 |         } | 
|---|
| 556 |     } | 
|---|
| 557 | } | 
|---|
| 558 |  | 
|---|
| 559 |     # internal variable | 
|---|
| 560 |     variable Log {} | 
|---|
| 561 |  | 
|---|
| 562 |     # ------------------- END OF PUBLIC METHODS ------------ | 
|---|
| 563 |  | 
|---|
| 564 |  | 
|---|
| 565 |     # | 
|---|
| 566 |     # sets the slave auto_path to the master recorded value. | 
|---|
| 567 |     # also sets tcl_library to the first token of the virtual path. | 
|---|
| 568 |     # | 
|---|
| 569 |     proc SyncAccessPath {slave} { | 
|---|
| 570 |         set slave_auto_path [Set [VirtualPathListName $slave]] | 
|---|
| 571 |         ::interp eval $slave [list set auto_path $slave_auto_path] | 
|---|
| 572 |         Log $slave "auto_path in $slave has been set to $slave_auto_path"\ | 
|---|
| 573 |                 NOTICE | 
|---|
| 574 |         ::interp eval $slave [list set tcl_library [lindex $slave_auto_path 0]] | 
|---|
| 575 |     } | 
|---|
| 576 |  | 
|---|
| 577 |     # base name for storing all the slave states | 
|---|
| 578 |     # the array variable name for slave foo is thus "Sfoo" | 
|---|
| 579 |     # and for sub slave {foo bar} "Sfoo bar" (spaces are handled | 
|---|
| 580 |     # ok everywhere (or should)) | 
|---|
| 581 |     # We add the S prefix to avoid that a slave interp called "Log" | 
|---|
| 582 |     # would smash our "Log" variable. | 
|---|
| 583 |     proc InterpStateName {slave} { | 
|---|
| 584 |         return "S$slave" | 
|---|
| 585 |     } | 
|---|
| 586 |  | 
|---|
| 587 |     # Check that the given slave is "one of us" | 
|---|
| 588 |     proc IsInterp {slave} { | 
|---|
| 589 |         expr {[Exists [InterpStateName $slave]] && [::interp exists $slave]} | 
|---|
| 590 |     } | 
|---|
| 591 |  | 
|---|
| 592 |     # returns the virtual token for directory number N | 
|---|
| 593 |     # if the slave argument is given,  | 
|---|
| 594 |     # it will return the corresponding master global variable name | 
|---|
| 595 |     proc PathToken {n {slave ""}} { | 
|---|
| 596 |         if {$slave ne ""} { | 
|---|
| 597 |             return "[InterpStateName $slave](access_path,$n)" | 
|---|
| 598 |         } else { | 
|---|
| 599 |             # We need to have a ":" in the token string so | 
|---|
| 600 |             # [file join] on the mac won't turn it into a relative | 
|---|
| 601 |             # path. | 
|---|
| 602 |             return "p(:$n:)" | 
|---|
| 603 |         } | 
|---|
| 604 |     } | 
|---|
| 605 |     # returns the variable name of the complete path list | 
|---|
| 606 |     proc PathListName {slave} { | 
|---|
| 607 |         return "[InterpStateName $slave](access_path)" | 
|---|
| 608 |     } | 
|---|
| 609 |     # returns the variable name of the complete path list | 
|---|
| 610 |     proc VirtualPathListName {slave} { | 
|---|
| 611 |         return "[InterpStateName $slave](access_path_slave)" | 
|---|
| 612 |     } | 
|---|
| 613 |     # returns the variable name of the number of items | 
|---|
| 614 |     proc PathNumberName {slave} { | 
|---|
| 615 |         return "[InterpStateName $slave](access_path,n)" | 
|---|
| 616 |     } | 
|---|
| 617 |     # returns the staticsok flag var name | 
|---|
| 618 |     proc StaticsOkName {slave} { | 
|---|
| 619 |         return "[InterpStateName $slave](staticsok)" | 
|---|
| 620 |     } | 
|---|
| 621 |     # returns the nestedok flag var name | 
|---|
| 622 |     proc NestedOkName {slave} { | 
|---|
| 623 |         return "[InterpStateName $slave](nestedok)" | 
|---|
| 624 |     } | 
|---|
| 625 |     # Run some code at the namespace toplevel | 
|---|
| 626 |     proc Toplevel {args} { | 
|---|
| 627 |         namespace eval [namespace current] $args | 
|---|
| 628 |     } | 
|---|
| 629 |     # set/get values | 
|---|
| 630 |     proc Set {args} { | 
|---|
| 631 |         Toplevel set {*}$args | 
|---|
| 632 |     } | 
|---|
| 633 |     # lappend on toplevel vars | 
|---|
| 634 |     proc Lappend {args} { | 
|---|
| 635 |         Toplevel lappend {*}$args | 
|---|
| 636 |     } | 
|---|
| 637 |     # unset a var/token (currently just an global level eval) | 
|---|
| 638 |     proc Unset {args} { | 
|---|
| 639 |         Toplevel unset {*}$args | 
|---|
| 640 |     } | 
|---|
| 641 |     # test existance  | 
|---|
| 642 |     proc Exists {varname} { | 
|---|
| 643 |         Toplevel info exists $varname | 
|---|
| 644 |     } | 
|---|
| 645 |     # short cut for access path getting | 
|---|
| 646 |     proc GetAccessPath {slave} { | 
|---|
| 647 |         Set [PathListName $slave] | 
|---|
| 648 |     } | 
|---|
| 649 |     # short cut for statics ok flag getting | 
|---|
| 650 |     proc StaticsOk {slave} { | 
|---|
| 651 |         Set [StaticsOkName $slave] | 
|---|
| 652 |     } | 
|---|
| 653 |     # short cut for getting the multiples interps sub loading ok flag | 
|---|
| 654 |     proc NestedOk {slave} { | 
|---|
| 655 |         Set [NestedOkName $slave] | 
|---|
| 656 |     } | 
|---|
| 657 |     # interp deletion storing hook name | 
|---|
| 658 |     proc DeleteHookName {slave} { | 
|---|
| 659 |         return [InterpStateName $slave](cleanupHook) | 
|---|
| 660 |     } | 
|---|
| 661 |  | 
|---|
| 662 |     # | 
|---|
| 663 |     # translate virtual path into real path | 
|---|
| 664 |     # | 
|---|
| 665 |     proc TranslatePath {slave path} { | 
|---|
| 666 |         # somehow strip the namespaces 'functionality' out (the danger | 
|---|
| 667 |         # is that we would strip valid macintosh "../" queries... : | 
|---|
| 668 |         if {[string match "*::*" $path] || [string match "*..*" $path]} { | 
|---|
| 669 |             error "invalid characters in path $path" | 
|---|
| 670 |         } | 
|---|
| 671 |         set n [expr {[Set [PathNumberName $slave]]-1}] | 
|---|
| 672 |         for {} {$n>=0} {incr n -1} { | 
|---|
| 673 |             # fill the token virtual names with their real value | 
|---|
| 674 |             set [PathToken $n] [Set [PathToken $n $slave]] | 
|---|
| 675 |         } | 
|---|
| 676 |         # replaces the token by their value | 
|---|
| 677 |         subst -nobackslashes -nocommands $path | 
|---|
| 678 |     } | 
|---|
| 679 |  | 
|---|
| 680 |  | 
|---|
| 681 |     # Log eventually log an error | 
|---|
| 682 |     # to enable error logging, set Log to {puts stderr} for instance | 
|---|
| 683 |     proc Log {slave msg {type ERROR}} { | 
|---|
| 684 |         variable Log | 
|---|
| 685 |         if {[info exists Log] && [llength $Log]} { | 
|---|
| 686 |             {*}$Log "$type for slave $slave : $msg" | 
|---|
| 687 |         } | 
|---|
| 688 |     } | 
|---|
| 689 |  | 
|---|
| 690 |  | 
|---|
| 691 |     # file name control (limit access to files/ressources that should be | 
|---|
| 692 |     # a valid tcl source file) | 
|---|
| 693 |     proc CheckFileName {slave file} { | 
|---|
| 694 |         # This used to limit what can be sourced to ".tcl" and forbid files | 
|---|
| 695 |         # with more than 1 dot and longer than 14 chars, but I changed that | 
|---|
| 696 |         # for 8.4 as a safe interp has enough internal protection already | 
|---|
| 697 |         # to allow sourcing anything. - hobbs | 
|---|
| 698 |  | 
|---|
| 699 |         if {![file exists $file]} { | 
|---|
| 700 |             # don't tell the file path | 
|---|
| 701 |             error "no such file or directory" | 
|---|
| 702 |         } | 
|---|
| 703 |  | 
|---|
| 704 |         if {![file readable $file]} { | 
|---|
| 705 |             # don't tell the file path | 
|---|
| 706 |             error "not readable" | 
|---|
| 707 |         } | 
|---|
| 708 |     } | 
|---|
| 709 |  | 
|---|
| 710 |  | 
|---|
| 711 |     # AliasSource is the target of the "source" alias in safe interpreters. | 
|---|
| 712 |  | 
|---|
| 713 |     proc AliasSource {slave args} { | 
|---|
| 714 |  | 
|---|
| 715 |         set argc [llength $args] | 
|---|
| 716 |         # Allow only "source filename" | 
|---|
| 717 |         if {$argc != 1} { | 
|---|
| 718 |             set msg "wrong # args: should be \"source fileName\"" | 
|---|
| 719 |             Log $slave "$msg ($args)" | 
|---|
| 720 |             return -code error $msg | 
|---|
| 721 |         } | 
|---|
| 722 |         set file [lindex $args 0] | 
|---|
| 723 |          | 
|---|
| 724 |         # get the real path from the virtual one. | 
|---|
| 725 |         if {[catch {set file [TranslatePath $slave $file]} msg]} { | 
|---|
| 726 |             Log $slave $msg | 
|---|
| 727 |             return -code error "permission denied" | 
|---|
| 728 |         } | 
|---|
| 729 |          | 
|---|
| 730 |         # check that the path is in the access path of that slave | 
|---|
| 731 |         if {[catch {FileInAccessPath $slave $file} msg]} { | 
|---|
| 732 |             Log $slave $msg | 
|---|
| 733 |             return -code error "permission denied" | 
|---|
| 734 |         } | 
|---|
| 735 |  | 
|---|
| 736 |         # do the checks on the filename : | 
|---|
| 737 |         if {[catch {CheckFileName $slave $file} msg]} { | 
|---|
| 738 |             Log $slave "$file:$msg" | 
|---|
| 739 |             return -code error $msg | 
|---|
| 740 |         } | 
|---|
| 741 |  | 
|---|
| 742 |         # passed all the tests , lets source it: | 
|---|
| 743 |         if {[catch {::interp invokehidden $slave source $file} msg]} { | 
|---|
| 744 |             Log $slave $msg | 
|---|
| 745 |             return -code error "script error" | 
|---|
| 746 |         } | 
|---|
| 747 |         return $msg | 
|---|
| 748 |     } | 
|---|
| 749 |  | 
|---|
| 750 |     # AliasLoad is the target of the "load" alias in safe interpreters. | 
|---|
| 751 |  | 
|---|
| 752 |     proc AliasLoad {slave file args} { | 
|---|
| 753 |  | 
|---|
| 754 |         set argc [llength $args] | 
|---|
| 755 |         if {$argc > 2} { | 
|---|
| 756 |             set msg "load error: too many arguments" | 
|---|
| 757 |             Log $slave "$msg ($argc) {$file $args}" | 
|---|
| 758 |             return -code error $msg | 
|---|
| 759 |         } | 
|---|
| 760 |  | 
|---|
| 761 |         # package name (can be empty if file is not). | 
|---|
| 762 |         set package [lindex $args 0] | 
|---|
| 763 |  | 
|---|
| 764 |         # Determine where to load. load use a relative interp path | 
|---|
| 765 |         # and {} means self, so we can directly and safely use passed arg. | 
|---|
| 766 |         set target [lindex $args 1] | 
|---|
| 767 |         if {$target ne ""} { | 
|---|
| 768 |             # we will try to load into a sub sub interp | 
|---|
| 769 |             # check that we want to authorize that. | 
|---|
| 770 |             if {![NestedOk $slave]} { | 
|---|
| 771 |                 Log $slave "loading to a sub interp (nestedok)\ | 
|---|
| 772 |                         disabled (trying to load $package to $target)" | 
|---|
| 773 |                 return -code error "permission denied (nested load)" | 
|---|
| 774 |             } | 
|---|
| 775 |              | 
|---|
| 776 |         } | 
|---|
| 777 |  | 
|---|
| 778 |         # Determine what kind of load is requested | 
|---|
| 779 |         if {$file eq ""} { | 
|---|
| 780 |             # static package loading | 
|---|
| 781 |             if {$package eq ""} { | 
|---|
| 782 |                 set msg "load error: empty filename and no package name" | 
|---|
| 783 |                 Log $slave $msg | 
|---|
| 784 |                 return -code error $msg | 
|---|
| 785 |             } | 
|---|
| 786 |             if {![StaticsOk $slave]} { | 
|---|
| 787 |                 Log $slave "static packages loading disabled\ | 
|---|
| 788 |                         (trying to load $package to $target)" | 
|---|
| 789 |                 return -code error "permission denied (static package)" | 
|---|
| 790 |             } | 
|---|
| 791 |         } else { | 
|---|
| 792 |             # file loading | 
|---|
| 793 |  | 
|---|
| 794 |             # get the real path from the virtual one. | 
|---|
| 795 |             if {[catch {set file [TranslatePath $slave $file]} msg]} { | 
|---|
| 796 |                 Log $slave $msg | 
|---|
| 797 |                 return -code error "permission denied" | 
|---|
| 798 |             } | 
|---|
| 799 |  | 
|---|
| 800 |             # check the translated path | 
|---|
| 801 |             if {[catch {FileInAccessPath $slave $file} msg]} { | 
|---|
| 802 |                 Log $slave $msg | 
|---|
| 803 |                 return -code error "permission denied (path)" | 
|---|
| 804 |             } | 
|---|
| 805 |         } | 
|---|
| 806 |  | 
|---|
| 807 |         if {[catch {::interp invokehidden\ | 
|---|
| 808 |                 $slave load $file $package $target} msg]} { | 
|---|
| 809 |             Log $slave $msg | 
|---|
| 810 |             return -code error $msg | 
|---|
| 811 |         } | 
|---|
| 812 |  | 
|---|
| 813 |         return $msg | 
|---|
| 814 |     } | 
|---|
| 815 |  | 
|---|
| 816 |     # FileInAccessPath raises an error if the file is not found in | 
|---|
| 817 |     # the list of directories contained in the (master side recorded) slave's | 
|---|
| 818 |     # access path. | 
|---|
| 819 |  | 
|---|
| 820 |     # the security here relies on "file dirname" answering the proper | 
|---|
| 821 |     # result.... needs checking ? | 
|---|
| 822 |     proc FileInAccessPath {slave file} { | 
|---|
| 823 |  | 
|---|
| 824 |         set access_path [GetAccessPath $slave] | 
|---|
| 825 |  | 
|---|
| 826 |         if {[file isdirectory $file]} { | 
|---|
| 827 |             error "\"$file\": is a directory" | 
|---|
| 828 |         } | 
|---|
| 829 |         set parent [file dirname $file] | 
|---|
| 830 |  | 
|---|
| 831 |         # Normalize paths for comparison since lsearch knows nothing of | 
|---|
| 832 |         # potential pathname anomalies. | 
|---|
| 833 |         set norm_parent [file normalize $parent] | 
|---|
| 834 |         foreach path $access_path { | 
|---|
| 835 |             lappend norm_access_path [file normalize $path] | 
|---|
| 836 |         } | 
|---|
| 837 |  | 
|---|
| 838 |         if {[lsearch -exact $norm_access_path $norm_parent] == -1} { | 
|---|
| 839 |             error "\"$file\": not in access_path" | 
|---|
| 840 |         } | 
|---|
| 841 |     } | 
|---|
| 842 |  | 
|---|
| 843 |     # This procedure enables access from a safe interpreter to only a subset of | 
|---|
| 844 |     # the subcommands of a command: | 
|---|
| 845 |  | 
|---|
| 846 |     proc Subset {slave command okpat args} { | 
|---|
| 847 |         set subcommand [lindex $args 0] | 
|---|
| 848 |         if {[regexp $okpat $subcommand]} { | 
|---|
| 849 |             return [$command {*}$args] | 
|---|
| 850 |         } | 
|---|
| 851 |         set msg "not allowed to invoke subcommand $subcommand of $command" | 
|---|
| 852 |         Log $slave $msg | 
|---|
| 853 |         error $msg | 
|---|
| 854 |     } | 
|---|
| 855 |  | 
|---|
| 856 |     # This procedure installs an alias in a slave that invokes "safesubset" | 
|---|
| 857 |     # in the master to execute allowed subcommands. It precomputes the pattern | 
|---|
| 858 |     # of allowed subcommands; you can use wildcards in the pattern if you wish | 
|---|
| 859 |     # to allow subcommand abbreviation. | 
|---|
| 860 |     # | 
|---|
| 861 |     # Syntax is: AliasSubset slave alias target subcommand1 subcommand2... | 
|---|
| 862 |  | 
|---|
| 863 |     proc AliasSubset {slave alias target args} { | 
|---|
| 864 |         set pat ^(; set sep "" | 
|---|
| 865 |         foreach sub $args { | 
|---|
| 866 |             append pat $sep$sub | 
|---|
| 867 |             set sep | | 
|---|
| 868 |         } | 
|---|
| 869 |         append pat )\$ | 
|---|
| 870 |         ::interp alias $slave $alias {}\ | 
|---|
| 871 |                 [namespace current]::Subset $slave $target $pat | 
|---|
| 872 |     } | 
|---|
| 873 |  | 
|---|
| 874 |     # AliasEncoding is the target of the "encoding" alias in safe interpreters. | 
|---|
| 875 |  | 
|---|
| 876 |     proc AliasEncoding {slave args} { | 
|---|
| 877 |  | 
|---|
| 878 |         set argc [llength $args] | 
|---|
| 879 |  | 
|---|
| 880 |         set okpat "^(name.*|convert.*)\$" | 
|---|
| 881 |         set subcommand [lindex $args 0] | 
|---|
| 882 |  | 
|---|
| 883 |         if {[regexp $okpat $subcommand]} { | 
|---|
| 884 |             return [::interp invokehidden $slave encoding {*}$args] | 
|---|
| 885 |         } | 
|---|
| 886 |  | 
|---|
| 887 |         if {[string first $subcommand system] == 0} { | 
|---|
| 888 |             if {$argc == 1} { | 
|---|
| 889 |                 # passed all the tests , lets source it: | 
|---|
| 890 |                 if {[catch {::interp invokehidden \ | 
|---|
| 891 |                         $slave encoding system} msg]} { | 
|---|
| 892 |                     Log $slave $msg | 
|---|
| 893 |                     return -code error "script error" | 
|---|
| 894 |                 } | 
|---|
| 895 |             } else { | 
|---|
| 896 |                 set msg "wrong # args: should be \"encoding system\"" | 
|---|
| 897 |                 Log $slave $msg | 
|---|
| 898 |                 error $msg | 
|---|
| 899 |             } | 
|---|
| 900 |         } else { | 
|---|
| 901 |             set msg "wrong # args: should be \"encoding option ?arg ...?\"" | 
|---|
| 902 |             Log $slave $msg | 
|---|
| 903 |             error $msg | 
|---|
| 904 |         } | 
|---|
| 905 |  | 
|---|
| 906 |         return $msg | 
|---|
| 907 |     } | 
|---|
| 908 |  | 
|---|
| 909 | } | 
|---|