@@ -77,97 +77,126 @@ proc is_Cygwin {} {
7777
7878# #####################################################################
7979# #
80- # # PATH lookup
80+ # # PATH lookup. Sanitize $PATH, assure exec/open use only that
81+
82+ if {[is_Windows]} {
83+ set _path_sep {;}
84+ set _search_exe .exe
85+ } else {
86+ set _path_sep {:}
87+ set _search_exe {}
88+ }
89+
90+ if {[is_Windows]} {
91+ set gitguidir [file dirname [info script]]
92+ regsub -all ";" $gitguidir "\\ ;" gitguidir
93+ set env(PATH) " $gitguidir ;$env(PATH) "
94+ }
8195
8296set _search_path {}
83- proc _which {what args} {
84- global env _search_exe _search_path
97+ set _path_seen [ dict create]
98+ foreach p [ split $env(PATH) $_path_sep ] {
99+ # Keep only absolute paths, getting rid of ., empty, etc.
100+ if {[ file pathtype $p ] ne {absolute}} {
101+ continue
102+ }
103+ # Keep only the first occurence of any duplicates.
104+ set norm_p [ file normalize $p ]
105+ if {[ dict exists $_path_seen $norm_p ] } {
106+ continue
107+ }
108+ dict set _path_seen $norm_p 1
109+ lappend _search_path $norm_p
110+ }
111+ unset _path_seen
85112
86- if {$_search_path eq {}} {
87- if {[is_Windows]} {
88- set gitguidir [file dirname [info script]]
89- regsub -all ";" $gitguidir "\\ ;" gitguidir
90- set env(PATH) " $gitguidir ;$env(PATH) "
91- set _search_path [ split $env(PATH) {;}]
92- # Skip empty `PATH` elements
93- set _search_path [ lsearch -all -inline -not -exact \
94- $_search_path " " ]
95- set _search_exe .exe
113+ set env(PATH) [ join $_search_path $_path_sep ]
114+
115+ if {[ is_Windows] } {
116+ proc _which {what args} {
117+ global _search_exe _search_path
118+
119+ if {[ lsearch -exact $args -script] >= 0} {
120+ set suffix {}
121+ } elseif {[ string match *$_search_exe [string tolower $what ] ]} {
122+ # The search string already has the file extension
123+ set suffix {}
96124 } else {
97- set _search_path [ split $env(PATH) :]
98- set _search_exe {}
125+ set suffix $_search_exe
99126 }
100- }
101-
102- if {[ is_Windows] && [ lsearch -exact $args -script] >= 0} {
103- set suffix {}
104- } else {
105- set suffix $_search_exe
106- }
107127
108- foreach p $_search_path {
109- set p [ file join $p $what$suffix ]
110- if {[ file exists $p ] } {
111- return [ file normalize $p ]
128+ foreach p $_search_path {
129+ set p [ file join $p $what$suffix ]
130+ if {[ file exists $p ] } {
131+ return [ file normalize $p ]
132+ }
112133 }
134+ return {}
113135 }
114- return {}
115- }
116136
117- proc sanitize_command_line {command_line from_index} {
118- set i $from_index
119- while {$i < [ llength $command_line ] } {
120- set cmd [ lindex $command_line $i ]
121- if {[ llength [file split $cmd ] ] < 2} {
122- set fullpath [ _which $cmd ]
123- if {$fullpath eq " " } {
124- throw {NOT-FOUND} " $cmd not found in PATH"
137+ proc sanitize_command_line {command_line from_index} {
138+ set i $from_index
139+ while {$i < [ llength $command_line ] } {
140+ set cmd [ lindex $command_line $i ]
141+ if {[ llength [file split $cmd ] ] < 2} {
142+ set fullpath [ _which $cmd ]
143+ if {$fullpath eq " " } {
144+ throw {NOT-FOUND} " $cmd not found in PATH"
145+ }
146+ lset command_line $i $fullpath
147+ }
148+
149+ # handle piped commands, e.g. `exec A | B`
150+ for {incr i} {$i < [ llength $command_line ] } {incr i} {
151+ if {[ lindex $command_line $i ] eq " |" } {
152+ incr i
153+ break
154+ }
125155 }
126- lset command_line $i $fullpath
127156 }
157+ return $command_line
158+ }
159+
160+ # Override `exec` to avoid unsafe PATH lookup
128161
129- # handle piped commands, e.g. `exec A | B`
130- for {incr i} {$i < [ llength $command_line ] } {incr i} {
131- if {[ lindex $command_line $i ] eq " |" } {
162+ rename exec real_exec
163+
164+ proc exec {args} {
165+ # skip options
166+ for {set i 0} {$i < [ llength $args ] } {incr i} {
167+ set arg [ lindex $args $i ]
168+ if {$arg eq " --" } {
132169 incr i
133170 break
134171 }
172+ if {[ string range $arg 0 0] ne " -" } {
173+ break
174+ }
135175 }
176+ set args [ sanitize_command_line $args $i ]
177+ uplevel 1 real_exec $args
136178 }
137- return $command_line
138- }
139179
140- # Override `exec ` to avoid unsafe PATH lookup
180+ # Override `open ` to avoid unsafe PATH lookup
141181
142- rename exec real_exec
182+ rename open real_open
143183
144- proc exec {args} {
145- # skip options
146- for {set i 0} {$i < [ llength $args ] } {incr i} {
147- set arg [ lindex $args $i ]
148- if {$arg eq " --" } {
149- incr i
150- break
151- }
152- if {[ string range $arg 0 0] ne " -" } {
153- break
184+ proc open {args} {
185+ set arg0 [ lindex $args 0]
186+ if {[ string range $arg0 0 0] eq " |" } {
187+ set command_line [ string trim [string range $arg0 1 end] ]
188+ lset args 0 " | [sanitize_command_line $command_line 0]"
154189 }
190+ uplevel 1 real_open $args
155191 }
156- set args [ sanitize_command_line $args $i ]
157- uplevel 1 real_exec $args
158- }
159192
160- # Override `open` to avoid unsafe PATH lookup
161-
162- rename open real_open
193+ } else {
194+ # On non-Windows platforms, auto_execok, exec, and open are safe, and will
195+ # use the sanitized search path. But, we need _which for these.
163196
164- proc open {args} {
165- set arg0 [ lindex $args 0]
166- if {[ string range $arg0 0 0] eq " |" } {
167- set command_line [ string trim [string range $arg0 1 end] ]
168- lset args 0 " | [sanitize_command_line $command_line 0]"
197+ proc _which {what args} {
198+ return [ lindex [auto_execok $what ] 0]
169199 }
170- uplevel 1 real_open $args
171200}
172201
173202# Wrap exec/open to sanitize arguments
@@ -354,15 +383,37 @@ if {$_trace >= 0} {
354383# branches).
355384set _last_merged_branch {}
356385
357- proc shellpath {} {
358- global _shellpath env
359- if {[ string match @@* $_shellpath ] } {
360- if {[ info exists env(SHELL)] } {
361- return $env(SHELL)
362- } else {
363- return /bin/sh
364- }
386+ # for testing, allow unconfigured _shellpath
387+ if {[ string match @@* $_shellpath ] } {
388+ if {[ info exists env(SHELL)] } {
389+ set _shellpath $env(SHELL)
390+ } else {
391+ set _shellpath /bin/sh
365392 }
393+ }
394+
395+ if {[ is_Windows] } {
396+ set _shellpath [ exec cygpath -m $_shellpath ]
397+ }
398+
399+ if {![ file executable $_shellpath ] || \
400+ !([ file pathtype $_shellpath ] eq {absolute})} {
401+ set errmsg " The defined shell ('$_shellpath ') is not usable, \
402+ it must be an absolute path to an executable."
403+ puts stderr $errmsg
404+
405+ catch {wm withdraw .}
406+ tk_messageBox \
407+ -icon error \
408+ -type ok \
409+ -title " git-gui: configuration error" \
410+ -message $errmsg
411+ exit 1
412+ }
413+
414+
415+ proc shellpath {} {
416+ global _shellpath
366417 return $_shellpath
367418}
368419
@@ -574,32 +625,13 @@ proc _git_cmd {name} {
574625 return $v
575626}
576627
577- # Test a file for a hashbang to identify executable scripts on Windows.
578- proc is_shellscript {filename } {
579- if {![file exists $filename ]} {return 0}
580- set f [safe_open_file $filename r]
581- fconfigure $f -encoding binary
582- set magic [read $f 2]
583- close $f
584- return [expr {$magic eq " #!" }]
585- }
586-
587- # Run a command connected via pipes on stdout.
628+ # Run a shell command connected via pipes on stdout.
588629# This is for use with textconv filters and uses sh -c "..." to allow it to
589- # contain a command with arguments. On windows we must check for shell
590- # scripts specifically otherwise just call the filter command.
630+ # contain a command with arguments. We presume this
631+ # to be a shellscript that the configured shell (/bin/sh by default) knows
632+ # how to run.
591633proc open_cmd_pipe {cmd path} {
592- global env
593- if {![file executable [shellpath]]} {
594- set exe [auto_execok [lindex $cmd 0]]
595- if {[is_shellscript [lindex $exe 0]]} {
596- set run [linsert [auto_execok sh] end -c " $cmd \"\$ 0\" " $path ]
597- } else {
598- set run [concat $exe [lrange $cmd 1 end] $path ]
599- }
600- } else {
601- set run [list [shellpath] -c " $cmd \"\$ 0\" " $path ]
602- }
634+ set run [list [shellpath] -c " $cmd \"\$ 0\" " $path ]
603635 set run [make_arglist_safe $run ]
604636 return [open |$run r]
605637}
@@ -2746,17 +2778,16 @@ if {![is_bare]} {
27462778
27472779if {[is_Windows]} {
27482780 # Use /git-bash.exe if available
2749- set normalized [file normalize $::argv0 ]
2750- regsub "/mingw../libexec/git-core/git-gui$" \
2751- $normalized "/git-bash.exe" cmdLine
2752- if {$cmdLine != $normalized && [file exists $cmdLine ]} {
2753- set cmdLine [list " Git Bash" $cmdLine ]
2781+ set _git_bash [exec cygpath -m /git-bash.exe]
2782+ if {[file executable $_git_bash ]} {
2783+ set _bash_cmdline [list " Git Bash" $_git_bash ]
27542784 } else {
2755- set cmdLine [list " Git Bash" bash --login -l]
2785+ set _bash_cmdline [list " Git Bash" bash --login -l]
27562786 }
27572787 .mbar.repository add command \
27582788 -label [mc " Git Bash" ] \
2759- -command {safe_exec_bg [concat [list [auto_execok start]] $cmdLine ]}
2789+ -command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline ]}
2790+ unset _git_bash
27602791}
27612792
27622793if {[is_Windows] || ![is_bare]} {
0 commit comments