Tcl Source Code

Artifact [a72fc74d7a]
Login

Artifact a72fc74d7a461f3b78b58299c3d7e159be33ac9c:

Attachment "safe.tcl.patch1-3" to ticket [2964715fff] added by kjnash 2010-03-10 23:38:58.
--- original/library/tcl8.5/safe.tcl	2010-01-23 04:36:42.000000000 +0000
+++ patched/library/tcl8.5/safe.tcl	2010-03-10 16:13:18.000000000 +0000
@@ -337,11 +337,15 @@
 	incr i
     }
 
-    set morepaths [::tcl::tm::list]
+    # Reverse the list because ::tcl::tm::add adds to the head of the list.
+    set morepaths [lreverse [::tcl::tm::list]]
+    set firstpass 1
     while {[llength $morepaths]} {
 	set addpaths $morepaths
 	set morepaths {}
 
+	# Add each $dir to the access path if it is not already there.
+	# Set morepaths to the subdirectories of the $dir that are added.
 	foreach dir $addpaths {
 	    # Prevent the addition of dirs on the tm list to the
 	    # result if they are already known.
@@ -355,7 +359,12 @@
 	    lappend map_access_path    $token $dir
 	    lappend remap_access_path  $dir $token
 	    lappend norm_access_path   [file normalize $dir]
-	    lappend slave_tm_path $token
+	    if {$firstpass} {
+		# $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
+		# Later passes handle subdirectories, which belong in the
+		# access path but not in the module path.
+		lappend slave_tm_path  $token
+	    }
 	    incr i
 
 	    # [Bug 2854929]
@@ -366,6 +375,7 @@
 	    # subdirectories.
 	    lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
 	}
+	set firstpass 0
     }
 
     set state(access_path)       $access_path
@@ -658,9 +668,69 @@
     }
 }
 
+
+# Description of alterations to ::safe::AliasGlob from core-8-5-branch v 1.16.4.6
+#
+# (1) - add a comment header
+# (2) - change test for "pkgIndex.tcl" and move it so that it is effective
+# (3) - give a meaningful error message if pkgIndex.tcl is detected
+# (4) - give a meaningful error message if -directory is used twice
+# (5) - supply a value for dir in cmd
+# (6) - require option -directory
+#       - This is a change to the (undocumented) behaviour of the exposed glob
+#         command.
+#       - The change is compatible with the required use cases for tm.tcl and
+#         (if the pkgIndex.tcl test is removed in future) package.tcl.
+#       - It is a good fit to the Safe Base idea that operations are permitted
+#         only in certain directories.
+
+# Further Alterations against {core-8-5-branch v 1.16.4.6 patched by safe.tcl.patch, safe.tcl.patch2}
+#  (7) Reverse (4) - use the same misleading error message as glob itself
+#  (8) -nocomplain protects against error return if there are no matches; it
+#      should not protect against errors in arguments, e.g. no pattern
+#  (9) - therefore we can move the test of -directory back into the
+#        argument-processing loop
+# (10) Return a specific error message for option -path
+# (11) Give a standard error message for invalid arguments other than -path
+# (12) There is no requirement for -join (if present) to be the last argument
+# (13) Remove the test that the directory part of a (compounded) glob pattern
+#      belongs to the access path.  Instead, test whether each result of the
+#      glob belongs to a directory in the access path.  The reason is that the
+#      DirInAccessPath test fails for a directory that has a glob wildcard.
+# (14) - therefore we do not need to do the pattern -join here - let glob do it
+
+
+# ------------------------------------------------------------------------------
+#  Proc ::safe::AliasGlob
+# ------------------------------------------------------------------------------
 # AliasGlob is the target of the "glob" alias in safe interpreters.
+#
+# This command provides a restricted form of glob that a safe interpreter
+# requires to handle Tcl Modules. See tm.tcl, proc ::tcl::tm::UnknownHandler.
+#
+# The restricted glob must use the -directory option (which must supply a
+# directory in the slave's access path), and returns only files and directories
+# whose parent directory belongs to the access_path.
+#
+# Restrictions cf. standard glob:
+# (1) option -path      is not allowed
+# (2) option -directory must be supplied and must be a directory in the safe
+#                       interpreter's access path.
+# (3) Final argument must not be "pkgIndex.tcl".  This restriction is a kludge
+#     to prevent the existence of "safe" glob from breaking tclPkgUnknown.
+# ------------------------------------------------------------------------------
+
 proc ::safe::AliasGlob {slave args} {
     Log $slave "GLOB ! $args" NOTICE
+
+    if {[lindex $args end] eq "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 "Safe base rejecting glob call apparently from tclPkgUnknown"
+    }
+
     set cmd {}
     set at 0
     array set got {
@@ -671,12 +741,6 @@
 	-- 0
     }
 
-    if {$::tcl_platform(platform) eq "windows"} {
-	set dirPartRE {^(.*)[\\/]}
-    } else {
-	set dirPartRE {^(.*)/}
-    }
-
     set dir        {}
     set virtualdir {}
 
@@ -692,6 +756,7 @@
 		incr at
 	    }
 	    -directory {
+		# Use the same misleading error message as glob:
 		if {$got($opt)} {
 		    return -code error \
 			{"-directory" cannot be used with "-path"}
@@ -699,72 +764,50 @@
 		set got($opt) 1
 		set virtualdir [lindex $args [incr at]]
 		incr at
+
+		# Get the real path from the virtual one and check that the path
+		# is in the access path of that slave.
+		if {[catch {
+		    set dir [TranslatePath $slave $virtualdir]
+		    DirInAccessPath $slave $dir
+		} msg]} {
+		    Log $slave $msg
+		    return -code error {directory is not in the access path}
+		}
+
 		lappend cmd -directory $dir
 	    }
-	    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"
+	    -path {
+		Log $slave {Safe Base does not allow glob option "-path"}
+		return -code error {Safe Base does not allow glob option "-path"}
 	    }
 	    -* {
-		Log $slave "Safe base rejecting glob option '$opt'"
-		return -code error "Safe base rejecting glob option '$opt'"
+		Log $slave "bad option \"$opt\": must be -directory, -join, -nocomplain, -tails, -types, or --"
+		return -code error "bad option \"$opt\": must be -directory, -join, -nocomplain, -tails, -types, or --"
 	    }
 	    default {
 		break
 	    }
 	}
-	if {$got(--) || $got(-join)} 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)} {
-	if {[catch {
-	    set dir [TranslatePath $slave $virtualdir]
-	    DirInAccessPath $slave $dir
-	} msg]} {
-	    Log $slave $msg
-	    if {!$got(-nocomplain)} {
-		return -code error "permission denied"
-	    } else {
-		return
-	    }
-	}
+	if {$got(--)} break
     }
 
-    # Apply the -join semantics ourselves
-    if {$got(-join)} {
-	set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
+    if {!$got(-directory)} {
+	return -code error {when glob is used in the Safe Base, option "-directory" must be specified}
     }
 
     # Process remaining pattern arguments
-    set firstPattern [llength $cmd]
-    while {$at < [llength $args]} {
-	set opt [lindex $args $at]
-	incr at
-	if {[regexp $dirPartRE $opt -> thedir] && [catch {
-	    set thedir [file join $virtualdir $thedir]
-	    DirInAccessPath $slave [TranslatePath $slave $thedir]
-	} msg]} {
-	    Log $slave $msg
-	    if {$got(-nocomplain)} {
-		continue
-	    } else {
-		return -code error "permission denied"
-	    }
-	}
-	lappend cmd $opt
+    set pattern [lrange $args $at end]
+    set cmd [concat $cmd $pattern]
+
+    # We could let glob raise this error, but this message is more helpful
+    # than our rewritten message "script error".
+    if {[llength $pattern] == 0} {
+	return -code error {wrong # args: should be "glob ?switches? name ?name ...?"}
     }
 
     Log $slave "GLOB = $cmd" NOTICE
 
-    if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} {
-	return
-    }
     if {[catch {
 	::interp invokehidden $slave glob {*}$cmd
     } msg]} {
@@ -778,16 +821,62 @@
     set res {}
     set l [string length $dir]
     foreach p $msg {
+	# Return only results that are in the access path.
+	# Normalize because [dirname foo/..] is "foo".
+	if {[catch {
+	    DirInAccessPath $slave [file dirname [file normalize $p]]
+	}]} {
+	    continue
+	}
 	if {[string equal -length $l $dir $p]} {
 	    set p [string replace $p 0 [expr {$l-1}] $virtualdir]
+	    lappend res $p
+	} else {
+	    # Something's gone wrong: $p has not been normalized and so should
+	    # always begin with $dir.
+	    return -code error "error handling glob results"
 	}
-	lappend res $p
     }
 
+    # A non-empty result list $msg might have had all its members removed.
+    if {($res eq {}) && (!$got(-nocomplain))} {
+	# E.g. {no files matched glob pattern "foo"}
+	return -code error [GlobErrorText $got(-join) $pattern]
+    }
     Log $slave "GLOB @ $res" NOTICE
     return $res
 }
 
+
+# ------------------------------------------------------------------------------
+#  Proc ::safe::GlobErrorText
+# ------------------------------------------------------------------------------
+# Command to return a suitable error message for use by AliasGlob, for the case
+# when there are no matches in the access path.
+#
+# Arguments:
+# gotJoin     - (boolean) whether glob was called with option -join
+# pattern     - list of pattern arguments supplied to glob
+#
+# Return Value: an error message
+# ------------------------------------------------------------------------------
+
+proc ::safe::GlobErrorText {gotJoin pattern} {
+    if {$gotJoin} {
+	set errorPattern [join $pattern /]
+	set numPat 1
+    } else {
+	set errorPattern $pattern
+	set numPat [llength $pattern]
+    }
+    if {$numPat > 1} {
+	set errorWord patterns
+    } else {
+	set errorWord pattern
+    }
+    return "no files matched glob $errorWord \"$errorPattern\""
+}
+
 # AliasSource is the target of the "source" alias in safe interpreters.
 
 proc ::safe::AliasSource {slave args} {
@@ -1107,7 +1196,7 @@
     # 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.
+    # tm_path_slave     : List of TM module paths, as tokens seen by the slave.
     # staticsok         : Value of option -statics
     # nestedok          : Value of option -nested
     # cleanupHook       : Value of option -deleteHook