namespace eval shellspy::callbacks { package require shellfilter #each member of args - ist not itself a list - and cannot be treated as one. #things like [concat {*}args] will generall break things further down the line proc cmdshellb {args} { shellfilter::log::open callback_cmdb {-syslog 127.0.0.1:514} shellfilter::log::write callback_cmdb "cmdshellb got [llength $args] arguments" shellfilter::log::write callback_cmdb "cmdshellb got '$args'" if {[lindex $args 0] eq "cmdb"} { set curlyparts [lrange $args 1 end] shellfilter::log::write callback_cmdb "cmdshellb curlyparts '$curlyparts'" #we lose grouping info by joining like this.. #set tail [string trim [join $curlyparts " "]] set tailinfo [shellfilter::list_element_info $curlyparts] shellfilter::log::write callback_cmdb "cmdshellb tailinfo '$tailinfo'" #e.g #% set y {{"c:/test/etc"} true {'blah1'} {'blah 2'}} #% lappend y \\\\\\ #{"c:/test/etc"} true {'blah1'} {'blah 2'} \\\\\\ #% foreach i [shellfilter::list_element_info $y] {puts $i} # 0 # wouldbrace 1 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap doublequotes head_tail_chars {{"} {"}} head_tail_names {dq dq} len 13 difflen 2 # 1 # wouldbrace 0 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap not-determined head_tail_chars {t e} head_tail_names {t e} len 4 difflen 0 # 2 # wouldbrace 0 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap singlequotes head_tail_chars {' '} head_tail_names {sqote sqote} len 7 difflen 0 # 3 # wouldbrace 1 wouldescape 0 has_braces 0 has_inner_braces 0 apparentwrap singlequotes head_tail_chars {' '} head_tail_names {sqote sqote} len 8 difflen 2 # 4 # wouldbrace 0 wouldescape 1 has_braces 0 has_inner_braces 0 apparentwrap not-determined head_tail_chars {\\ \\} head_tail_names {\\ \\} len 3 difflen 3 #sample arglist - 7 items #((c:\lua\luajit.exe #C:\Users\sleek\vimfiles\plugged\vim-flog/lua/flog/graph_bin.lua #__START #true #git -C C:/repo/3p/ansi-to-html --literal-pathspecs log --parents --topo-order --no-color --pretty=format:__START\%n\%h\%n\%p\%n\%D\%n\%ad\ \[\%h\]\ \{\%an\}\%d\ \%s --date=iso --no-merges --max-count=2000 -- ) #> #C:\Users\sleek\AppData\Local\Temp\VRR6A67.tmp) #complex method.. lists at levels.. set taillist [list] set level 0 ;#bracket depth.. incr each opening bracket "(" set leveldict [dict create] dict set leveldict 0 [list] #simpler method.. string set output "" dict for {idx info} $tailinfo { set item [lindex $curlyparts $idx] set itemlen [string length $item] if {[dict get $info apparentwrap] eq "brackets"} { set chars [split $item ""] set opening [lsearch -all $chars "("] set closing [lsearch -all $chars ")"] if {[llength $opening] == [llength $closing]} { #dict lappend leveldict 0 $item ;#element individually wrapped in brackets and balanced, pass through append output "$item " } else { #robustness warning: we examine outer brackets only, and are ignoring that things like {((a)(b)} {((a)(b} # are composed of multiple elements for now .. as there should have been a space between the (a) & (b) elements anyway, # in which case we would only see things like {((a)} or {(a} set ltrimmed [string trimleft $item "("] set countleft [expr {$itemlen - [string length $ltrimmed]}] set rtrimmed [string trimright $item ")"] set countright [expr {$itemlen - [string length $rtrimmed]}] #simpler method.. append output "$item " } } else { set lcharname [lindex [dict get $info head_tail_names] 0] set rcharname [lindex [dict get $info head_tail_names] 1] if {$lcharname eq "lbracket"} { set ltrimmed [string trimleft $item "("] set countleft [expr {$itemlen - [string length $ltrimmed]}] set braces [string repeat "(" $countleft] set testlist [list] lappend testlist $ltrimmed set testinfo [shellfilter::list_element_info $testlist] set testelement [dict get $testinfo 0] if {[dict get $testelement wouldbrace]} { #append output "${braces}\"$ltrimmed\" " append output "${braces} $ltrimmed " } else { append output "${braces} $ltrimmed " } } elseif {$rcharname eq "rbracket"} { set rtrimmed [string trimright $item ")" ] set countright [expr {$itemlen - [string length $rtrimmed]}] set braces [string repeat ")" $countright] set testlist [list] lappend testlist $rtrimmed set testinfo [shellfilter::list_element_info $testlist] set testelement [dict get $testinfo 0] if {[dict get $testelement wouldbrace]} { #append output "\"$rtrimmed\"${braces} " if {[string first " " $rtrimmed] > 0} { append output "\"$rtrimmed\" ${braces} " } else { append output "$rtrimmed ${braces} " } } else { append output "${rtrimmed} ${braces} " } } else { set testlist [list] lappend testlist $item set testinfo [shellfilter::list_element_info $testlist] set testelement [dict get $testinfo 0] if {[dict get $testelement wouldbrace]} { #append output "\"$item\" " if {[string first " " $item] > 0} { append output "\"$item\" " } else { append output "$item " } } else { append output "$item " } } } } shellfilter::log::write callback_cmdb "cmdshellb about to parse_cmd_brackets '$output'" #$output now has quoted all items that 'wouldbrace' set curly_list [shellfilter::parse_cmd_brackets $output] if {[llength $curly_list] == 1} { #we expect the whole set of arguments was wrapped in a set of brackets set curly_items [lindex $curly_list 0] } else { #unexpected.. root level of string had multiple bracketed sections #try using the curly_list directly warn? set curly_items $curly_list } #e.g # ((c:\lua\luajit.exe -v) > C:\Users\sleek\AppData\Local\Temp\V7NCBF.tmp) #=> # {{{c:\lua\luajit.exe} -v} > {C:\Users\sleek\AppData\Local\Temp\V7NCBF.tmp}} #what is the proper way to flatten? set comspec [lindex $curly_items 0] if {[llength $comspec] >1} { set commandlist [concat $comspec [lrange $curly_items 1 end]] } else { set commandlist $curly_items } set commandlist [floghack_singlearg callback_cmdb {*}$commandlist] return $commandlist } else { shellfilter::log::write callback_cmdb "first arg: [lindex $args 0] vs 'cmdb'" error "first arg: [lindex $args 0] vs 'cmdb'" #return $args } } proc cmdshell {args} { if {[catch { set args [floghack_singlearg callback_cmdshell {*}$args] } errMsg]} { error "FLOGHACK callback_cmdshell error $errMsg" } #set args [concat [lindex $args 0] [lrange $args 1 end]] return $args } proc cmdshelluc {args} { if {[catch { set args [floghack_singlearg callback_cmdshelluc {*}$args] } errMsg]} { error "FLOGHACK callback_cmdshelluc error $errMsg" } #set args [concat [lindex $args 0] [lrange $args 1 end]] return $args } proc powershell {args} { if {[catch { set args [floghack "callback_powershell" {*}$args] } errMsg]} { error "FLOGHACK callback_powershell error $errMsg" } return $args } proc raw {args} { if {[catch { set args [floghack_singlearg "callback_raw" {*}$args] } errMsg]} { error "FLOGHACK callback_raw error $errMsg" } #set args [concat [split [lindex $args 0]] [lrange $args 1 end]] ;#definitely bad! return $args } #hack for c: drive - extend as necessary #todo - customize! # various hacks may be necessary for home dirs temp files etc! proc sh {args} { if {[catch { set args [floghack callback_sh {*}$args] } errMsg]} { error "FLOGHACK callback_sh error $errMsg" } set final [list] foreach a $args { if {[string match -nocase {*c:/*} $a]} { set a [string map [list {c:/} {/c/}] $a] lappend final $a } elseif {[string match -nocase {*c:\\*} $a]} { set a [string map [list \\ / ] $a] set a [string map [list {c:/} {/c/} {C:/} {/C/}] $a] lappend final $a } else { lappend final $a } } return $final } proc wsl {args} { set args [floghack_singlearg callback_wsl {*}$args] #wsl bash-style /mnt/c paths to be convertd set args [convert_mnt_win {*}$args] #review - seems bad #flatten first element which arrives wrapped #set args [concat [lindex $args 0] [lrange $args 1 end]] return $args } proc bash {args} { set args [floghack callback_bash {*}$args] return [convert_mnt_win {*}$args] } #helper functions proc convert_mnt_win {args} { set final [list] foreach a $args { if {[string match -nocase {*c:\\*} $a]} { set a [string map [list \\ / ] $a] } #bash seems to be strict about lowercase /mnt/c if {[string match -nocase {*c:/*} $a]} { set a [string map [list {c:/} {/mnt/c/} {C:/} {/mnt/c/}] $a] } lappend final $a } shellfilter::log::write callback_convert_mnt_win "convert_mnt_win commandlist '$final'" return $final } #when we get the git command and args together as one element in the commandlist proc floghack_singlearg {logtag args} { #return $args shellfilter::log::write $logtag "floghack_singlearg got $logtag '$args'" set newargs [list] foreach a $args { if {[string match "*pretty=format:__START*" $a]} { set a [string map [list "pretty=format:__" "pretty=format:\"__"] $a] set a [string map [list " --date=" "\" --date="] $a] set a [string map [list \\ ""] $a] ;# a bad idea? lappend newargs $a } else { lappend newargs $a } } shellfilter::log::write $logtag "floghack_singlearg hacked commandlist '$newargs'" return $newargs } proc floghack {logtag args} { #return $args shellfilter::log::write $logtag "floghack got [llength $args] args: '$args'" set indent " " set i 0 foreach a $args { shellfilter::log::write $logtag "floghack ${indent}$i $a" incr i } #Flog/luajit hack #wrong way #if {[string first "pretty=format:__START" $args] > 0} { # set args [string map [list "pretty=format:" "pretty=format:'" " --date=" "' --date=" ] $args] #} set newargs [list] set pretty "" set pstart [lsearch $args "--pretty=format:__START*"] set pafter [lsearch $args "--date=*"] if {$pstart > 0} { set newargs [lrange $args 0 $pstart-1] set parts [lrange $args $pstart $pafter-1] set i 1 foreach p $parts { if {$i == 1} { set pretty [string map [list "format:__" "format:\"__"] $p] #set pretty $p } else { append pretty " $p" } if {$i == [llength $parts]} { append pretty "\"" } incr i } set pretty [string map [list \\ ""] $pretty] lappend newargs $pretty #lappend newargs "--pretty=format:%h" set newargs [concat $newargs [lrange $args $pafter end]] ;#concat of 2 lists.. should be ok } else { set newargs $args } shellfilter::log::write $logtag "floghack hacked commandlist '$newargs'" return $newargs } }