#6/11/99 # - resumes (due to conio's bugging) # - cooler status bar # - more accurate speed ratio (e.g., 5k/s) # - data connection uses fcopy when possible # - LIST formatted # fabulous! alias ftp ftpalias alias ftpqueue ftpalias proc ftpalias {} { switch -- [event] { ftp { if ![regexp -nocase {^(ftp://)?(([^: /]+):([^@ /]+)@)?([^: /]+)(:([0-9]+))?(/(.*))?$} [raw_args] junk junk junk user pass server junk port file] { echo "[kano] Usage: /[event] \[ftp://\]\[login:pass@\]host\[:port\]\[/file\]\n /ftpqueue - for more ftp stuff" } { open_new_ftp $server $port $user $pass } } ftpqueue { global ftpnext set cmd [string tolower [lindex [args] 0]] set queue [lindex [args] 1] set args [lrange [args] 2 end] if ![info exists ftpnext($queue)] {echo "[kano] no such queue: $queue";set cmd [list]} switch -glob -- $cmd { rem* - del* { if {![isnum $args] || $args > [llength $ftpnext($queue)] || $args < 1} { echo "[kano] invalid queue index: $args" } { set ftpnext($queue) [lreplace $ftpnext($queue) $args $args] /[event] } } {} { echo "[kano] current queues:" foreach i [lsort [array names ftpnext]] { if {[catch {set fs [fconfigure $i -peername]}]} { continue } echo "[kano] queue $i ([lindex $fs 1]:[lindex $fs 2])" set c 0 foreach i $ftpnext($i) { set show [ switch -- [lindex $i 0] { retr {set x "download of [lindex $i 1]"} list {set x "directory listing"} stor {set x "upload of [file tail [lindex $i 1]]"} } ] echo " [expr {$c == 0 ? "current: " : "$c. "}]$show" incr c } } echo "[kano] usage: /[event] rem|move " } } } } complete } proc open_new_ftp {server {port 21} {user anonymous} {pass ""}} { set port [expr {$port == "" ? 21 : $port}] set user [expr {$user == "" ? "anonymous" : $user}] set pass [expr {$pass == "" ? "[my_user]@[my_host]" : $pass}] if [catch {socket -async $server $port} sock] { echo "[kano] Couldn't connect to $server:$port: $sock" } { echo "[kano] Connecting to $server:$port..." global ftpwait ftpsocks lastftp okftp ftpprog ftplogin ftpnext set ftplogin($sock) [list $user $pass] fileevent $sock writable "fileevent $sock writable {};set ftpwait($sock) 1" vwait ftpwait($sock) if [eof $sock] { echo "[kano] Couldn't connect to $server:$port" close $sock } { lappend ftpsocks $sock set okftp($sock) ftp_waiting set lastftp($sock) [list] set ftpnext($sock) [list] fconfigure $sock -blocking 0 -buffering line fileevent $sock readable [list get_ftp_command $sock] /query .ftp:$sock. ftp_show_progress $sock echo "[kano] Connected to $server:$port... waiting for banner" query .ftp:$sock. } } } proc get_ftp_command sock { if [eof $sock] {ftp_kill_connect $sock;return} if [catch {gets $sock line}] {ftp_kill_connect $sock;return} global ftplong waitingftp okftp echo $line query .ftp:$sock. if {[info exists okftp($sock)]} { if {$okftp($sock) != "ftp_waiting"} { vwait okftp($sock) echo "waiting: $okftp($sock)" query .ftp:$sock. if ![info exists okftp($sock)] return } } set exp [expr {![regexp {^([0-9]+)( |-)?(.*)?$} $line junk reply long text]}] if $exp {set reply [list];set text "";set long 0} {set long [expr {$long == "-"}]} set which [expr {[info exists ftplong($sock)]}] set do [list] if {$which} { if {[lindex $ftplong($sock) 0] == $reply && !$long} { lappend ftplong($sock) $text set f $ftplong($sock) unset ftplong($sock) set do {parse_ftp_command $sock [lindex $f 0] [join [lrange $f 1 end] \n]} set msg oldparse } { if {[regexp {^([ ]+)([0-9]+)} $line]} {set line [string trimleft $line]} lappend ftplong($sock) $line set msg oldappend } } { if $long { set msg newappend lappend ftplong($sock) $reply $text } { set msg newparse set do {parse_ftp_command $sock $reply $text} } } # echo "[expr {$exp ? "exped" : "nomatch"}] $msg: $reply: \[[expr {$which ? "longed" : "new"}]\] ([expr {$long ? "long" : "short"}]) $text" eval $do } proc ftp_send_command {sock cmd {arg ""} {display 1}} { global okftp lastftp ftpdata nextftp set ap [list] switch -- [string tolower $cmd] { nlst - list { set ftpdata($sock) [list display $cmd $arg] lappend ap {ftp_handle_data $sock} set arg [file tail $arg] } retr { set save [lindex $arg 0] set saveas [lindex $arg 1] set saveas [expr {$saveas == "" ? "$save" : "$saveas"}] set incr [lindex $arg 2] set incr [expr {$incr == "" ? 0 : $incr}] set ftpdata($sock) [list get $save $saveas $incr] lappend ap {ftp_handle_data $sock} set arg [file tail $save] } stor { set send [lindex $arg 0] set sendas [lindex $arg 1] set sendas [expr {$sendas == "" ? "$send" : "$sendas"}] set ftpdata($sock) [list send $send $sendas] lappend ap {ftp_handle_data $sock} set arg [file tail $sendas] } } if $display {set echo $arg} {set echo [list]} echo ">> [concat $cmd $echo]" query .ftp:$sock. set lastftp($sock) $cmd if [catch {puts $sock [concat $cmd $arg]}] {ftp_kill_connect $sock;return} foreach i $ap {eval $i} vwait nextftp($sock) if {![info exists nextftp($sock)]} {return 0} set okftp($sock) ftp_waiting return $nextftp($sock) } proc ftp_macro {sock type args} { global ftplogin ftp ftpnext if {$type == "FORCE"} {set force 1;set type [lindex $args 0];set args [lrange $args 1 end]} {set force 0} switch -- $type { login { ftp_send_command $sock USER [lindex $ftplogin($sock) 0] } logged { foreach i {PWD} { if ![ftp_send_command $sock $i] return } } pass { ftp_send_command $sock PASS [lindex $ftplogin($sock) 1] 0 } acct { ftp_send_command $sock ACCT [prompt ftp.ka "Enter your account name:"] } list { if !$force { lappend ftpnext($sock) [concat [list $type] $args] if {[llength $ftpnext($sock)] > 1} { if {[llength $ftpnext($sock)] == 2} { echo "[kano] waiting for data transfer to complete..type ABOR to cancel the current transfer, [lindex $ftpnext($sock) 0]" } return } } ftp_send_command $sock PASV ftp_send_command $sock TYPE A ftp_send_command $sock LIST [lindex $args 0] } resume { if !$force { lappend ftpnext($sock) [concat [list $type] $args] if {[llength $ftpnext($sock)] > 1} { if {[llength $ftpnext($sock)] == 2} { echo "[kano] waiting for data transfer to complete..type ABOR to cancel the current transfer: [lindex $ftpnext($sock) 0]" } return } } ftp_send_command $sock PASV ftp_send_command $sock TYPE I set file [lindex $args 0] set size [expr {[file size $file]+1}] ftp_send_command $sock REST $size ftp_send_command $sock RETR [list $file [lindex $args 1] $size] } retr { if !$force { lappend ftpnext($sock) [concat [list $type] $args] if {[llength $ftpnext($sock)] > 1} { if {[llength $ftpnext($sock)] == 2} { echo "[kano] waiting for data transfer to complete..type ABOR to cancel the current transfer, [lindex $ftpnext($sock) 0]" } return } } ftp_send_command $sock PASV ftp_send_command $sock TYPE I ftp_send_command $sock RETR [list [lindex $args 0] [lindex $args 1]] } stor { if !$force { lappend ftpnext($sock) [concat [list $type] $args] if {[llength $ftpnext($sock)] > 1} { if {[llength $ftpnext($sock)] == 2} { echo "[kano] waiting for data transfer to complete..type ABOR to cancel the current transfer, [lindex $ftpnext($sock) 0]" } return } } ftp_send_command $sock PASV ftp_send_command $sock TYPE I ftp_send_command $sock STOR [list [lindex $args 0] [lindex $args 1]] } raw {foreach i $args {ftp_send_command $sock $i}} } } proc ftp_handle_data {sock args} { global ftpport ftpdata okftp ftpwait ftpsize ftpsock ftpchan if {![info exists ftpport($sock)]} { ftp_send_command $sock ABOR return } if [catch {socket -async [lindex $ftpport($sock) 0] [lindex $ftpport($sock) 1]} data] { ftp_send_command $sock ABOR echo "[kano] Error connecting to [join $ftpport($sock) :]: $data" query .ftp:$sock. return } { catch {close ftpsock($sock)} set ftpsock($sock) $data fileevent $data writable "fileevent $data writable {};set ftpwait($sock) 1" vwait ftpwait($sock) if {[eof $data]} { catch {close $data} ftp_send_command $sock ABOR echo "[kano] Couldn't connect to [join $ftpport($sock) ;]" query .ftp:$sock. return } fconfigure $data -blocking 0 switch -- [lindex $ftpdata($sock) 0] { display { fconfigure $data -buffering line fileevent $data readable [list ftp_incoming_data $sock $data] } get { set arg [lindex $ftpdata($sock) 2] fconfigure $data -buffering none -translation [list binary binary] set seek [lindex $ftpdata($sock) 3] if [catch {open $arg [expr {$seek ? "a" : "w"}]} chan] { close $data ftp_send_command $sock ABOR echo "[kano] Couldn't open file $arg: $chan" query .ftp:$sock. } { fconfigure $chan -buffering full -buffersize 1048576 -translation [list binary binary] -blocking 0 set ftpchan($sock) $chan ftp_show_progress $sock if {[info commands fcopy] == ""} { echo "[kano] download the tcl 8.1 hack for better ftp up/download support @ kfile://tcl/" fileevent $data readable [list ftp_incoming_data $sock $data $chan] } { ftp_copy $sock $chan $data 4096 } } } send { set arg [lindex $ftpdata($sock) 1] fconfigure $data -buffering none -translation binary -blocking 0 if [catch {open $arg} chan] { close $data ftp_send_command $sock ABOR echo "[kano] Couldn't access file $arg: $chan" query .ftp:$sock. } { set ftpchan($sock) $chan fconfigure $chan -buffering none -translation binary -blocking 0 ftp_show_progress $sock if {[info commands fcopy] == ""} { echo "[kano] download the tcl 8.1 hack for better ftp up/download support @ kfile://tcl/" fileevent $data writable [list ftp_incoming_data $sock $data $chan] } { ftp_copy $sock $data $chan 4096 } } } } } } proc ftp_copy {sock to from size {er {}}} { fconfigure $to ;#so the stupid thing update ;#doesn't lock xircon. don't ask me. if {[eof $sock] || [eof $from] || [eof $to] || $er != ""} {close $from;close $to;return} ftp_show_progress $sock $size fcopy $from $to -size $size -command [list ftp_copy $sock $to $from] } proc ftp_incoming_data {sock data {chan ""}} { if {[eof $data]} { catch {close $data} catch {close $chan} return } global ftpdata ftpport ftpsize litehelp switch -- [lindex $ftpdata($sock) 0] { display { if [catch {gets $data line} x] { catch {close $data} echo "[kano] Error reading from [join $ftpport($sock) :]: $x" } { set echome 1 #-r-xr-xr-x 1 owner group 126847 Aug 17 1995 clouds.exe switch -- [string tolower [lindex $ftpdata($sock) 1]] { list { if [regexp {^([^ ]+)[ ]+([^ ]+)[ ]+([^ ]+)[ ]+([^ ]+)[ ]+([0-9]+)[ ]+([^ ]+)[ ]+([^ ]+)[ ]+([^ ]+)[ ]+(.*)$} $line junk attr links owner group size junk junk junk file] { lappend ftpsize($sock) $file $size set echome 0 echo "[kano] [format %9s [expr {[string index $attr 0] == "d" ? "dir" : "get"}]://[expr {([llength $ftpsize($sock)]-1) / 2}]]$file ${litehelp}([join [filesize $size no %.2f] {}])" query .ftp:$sock. } } } if $echome {echo "<< $line" query .ftp:$sock.} } } get { if [catch {unsupported0 $data $chan} val] { catch {close $chan} catch {close $data} } { ftp_show_progress $sock $val } update } send { while {![eof $chan]} { if [eof $data] break if [catch {puts -nonewline $data [list]}] continue catch {ftp_show_progress $sock [unsupported0 $chan $data 1024]} update } close $data close $chan } } } proc ftp_show_progress {sock {size ""}} { global ftpprog ftpstart if {$size == ""} { set ftpprog($sock) 0 set ftpstart($sock) [clock seconds] } { scan $size %d size incr ftpprog($sock) $size } } proc ftp_cancel sock { global ftpsock ftpchan catch {close $ftpsock($sock)} catch {close $ftpchan($sock)} } proc ftp_next sock { global ftpnext lastprog catch {unset lastprog($sock)} set ftpnext($sock) [lrange $ftpnext($sock) 1 end] eval ftp_macro $sock FORCE [lindex $ftpnext($sock) 0] } proc parse_ftp_command {sock reply comment} { global lastftp okftp ftpport nextftp ftpsize set lf [string tolower $lastftp($sock)] switch -- $lf { {} { switch -- $reply { 120 {ftp_kill_connect $sock} 220 {ftp_macro $sock login} } } user { switch -- $reply { 230 {set ok 1;ftp_macro $sock logged} 530 {ftp_kill_connect $sock} 331 {set ok 1;ftp_macro $sock pass} 332 {set ok 1;ftp_macro $sock account} } } pass { switch -- $reply { 230 {set ok 1;ftp_macro $sock logged} 202 - 530 {ftp_kill_connect $sock} 332 {set ok 1;ftp_macro $sock account} } } acct { switch -- $reply { 230 {set ok 1;ftp_macro $sock logged} 202 - 530 {ftp_kill_connect $sock} } } cwd - cdup - smnt { switch -- $reply { 250 {set ftpsize($sock) [list];set ok 1} } } rein - quit {} port { switch -- $reply { 200 {set ok 1} } } pasv { switch -- $reply { 227 { set ok 1 regexp {([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)} $comment junk h1 h2 h3 h4 p1 p2 set ip $h1.$h2.$h3.$h4 set port [expr {($p1 << 8) | $p2}] set ftpport($sock) [list $ip $port] echo "[kano] data connection: $ip:$port" query .ftp:$sock. global ftp_dccget if [info exists ftp_dccget] { global ftpsize set f [file tail [lindex $ftp_dccget 1]] set s [expr {[info exists ftpsize($f)] ? $ftpsize($f) : 0}] /ctcp [lindex $ftp_dccget 0] DCC SEND [join [split $f] _] [FALC_ip2long $ip] $port $s unset ftp_dccget } } } } mode - type - stru - allo { switch -- $reply { 200 {set ok 1} } } rest { switch -- $reply { 350 {set ok 1} } } stor - stou - retr - list - nlst - appe { switch -- $reply { 125 - 150 { return } 250 - 226 { global ftpsock ftpchan ftpdata if {[string tolower [lindex $ftpdata($sock) 0]] != "display"} { catch {close $ftpsock($sock)} catch {close $ftpchan($sock)} } set ok 1 ftp_next $sock } 500 - 501 - 421 - 426 - 550 { ftp_cancel $sock ftp_next $sock } } } rnfr - rnto - dele - rmd - mkd - pwd { switch -- $reply { 350 - 250 - 257 {set ok 1} } } abor { switch -- $reply { 225 - 226 {set ok 1;ftp_next $sock} } } syst { switch -- $reply { 215 {set ok 1} } } stat - help { switch -- $reply { 211 - 212 - 213 - 214 {set ok 1} } } site - noop { switch -- $reply { 200 {set ok 1} } } } if [info exists ok] { set nextftp($sock) $ok } {set nextftp($sock) 0} } proc ftp_kill_connect sock { global ftpsocks okftp lastftp ftpwait ftplong nextftp ftpsock ftpchan if ![catch {close $sock}] { echo "[kano] Disconnected" query .ftp:$sock. window set_title "Disconnected [window get_title query .ftp:$sock.]" query .ftp:$sock. } set f [lsearch -exact $ftpsocks $sock] if {$f != -1} {set ftpsocks [lreplace $ftpsocks $f $f]} catch {unset okftp($sock)} catch {unset lastftp($sock)} catch {unset ftpwait($sock)} catch {unset ftplong($sock)} catch {unset nextftp($sock)} catch {unset ftpnext($sock)} catch {unset ftpsize($sock)} catch {close $ftpsock($sock)} catch {close $ftpchan($sock)} catch {unset ftpsock($sock)} catch {unset ftpchan($sock)} } on timer { foreach i $ftpsocks { if ![window exists query .ftp:$i.] {ftp_kill_connect $i} } } alias privmsg ftp_cmd_window proc ftp_cmd_window {} { set ldest [string tolower [lindex [args] 0]] if {[string match .ftp:*. $ldest]} { complete regexp -nocase {^\.ftp:([^.]*)\.$} $ldest junk sock set cmd [string tolower [lindex [split [raw_args]] 1]] set args [lrange [split [raw_args]] 2 end] set arg [join $args] set send0 [list] set send1 [list] switch -- $cmd { stor - send { if {$arg == ""} { set arg [FALC_fileopen -d [get_cookie ftp_last_dir] -M] if {$arg == ""} return elseif {[file exists $arg]} {set arg [list $arg]} foreach i $arg { ftp_macro $sock stor $i } set_cookie ftp_last_dir [file dirname [lindex $arg 0]] return } if [file exists $arg] { ftp_macro $sock stor $arg } { set match [glob -nocomplain $arg] if {$match == ""} { echo "[kano] No such file $arg" } { foreach i $match { ftp_macro $sock stor $i } } } } retr - get { global ftpsize if {[file exists [file tail $arg]]} { set ans [FALC_listbox -t [file tail $arg] -m "Select how you would like to download:\n [file tail $arg]" [list resume overwrite rename]] if {$ans == ""} return switch -- $ans { resume {ftp_macro $sock resume $arg} overwrite {ftp_macro $sock retr $arg} rename { set ans [FALC_fileopen -t [file tail $arg] -d -f "All files|*" -s] if {$ans == ""} return ftp_macro $sock retr $arg $ans } } } { set larg [string tolower $arg] set glob [list] foreach {file size} $ftpsize($sock) { if {[string match $larg [string tolower $file]]} {lappend glob $file} } set glob [killdupe $glob] if {$glob == ""} { ftp_macro $sock retr $arg } { echo "[kano] getting [llength $glob] file[s [llength $glob]]" foreach i $glob {ftp_macro $sock retr $i} } } } list - dir - ls { ftp_macro $sock list $arg } cd { set send0 CWD set send1 $arg } dcc { set nick [lindex $arg 0] set file [join [lrange $arg 1 end]] global ftp_dccget set ftp_dccget $nick ftp_macro $sock retr $nick $file } help { echo "[kano] ftp.ka commands: get|retr , send|stor , cd|cwd , ls|list|dir \[\]" echo "[kano] extended commands:" echo " dcc : sends file directly from the ftp server to a nick" } default { set send0 $cmd set send1 $arg } } if {$send0 != ""} {ftp_send_command $sock $send0 $send1} } } proc ftp_update_progress {array sock op} { upvar $array prog if [catch {fconfigure $sock -peername} sockd] return set title "FTP: [lindex $sockd 1]:[lindex $sockd 2]" global ftpsize ftpdata ftpstart ftpprog ftpnext lastprog if ![info exists lastprog($sock)] {set lastprog($sock) [list [clock seconds] 0]} if {$prog($sock) > 0 && [regexp -nocase {^get|send$} [lindex $ftpdata($sock) 0]]} { set sent [lindex $ftpdata($sock) 3] set sent [expr {$sent == "" ? 0 : $sent}] set realprog [expr {$prog($sock) + $sent}] set fs [filesize $realprog no] set title [concat [format %.2f [lindex $fs 0]] [lindex $fs 1] $title] set lf [lindex $ftpdata($sock) 1] set tsize 0 switch -- [string tolower [lindex $ftpdata($sock) 0]] { send { if {[file exists $lf]} { set tsize [file size $lf] set f 0 } } get { if {[info exists ftpsize($sock)]} { set file [lindex $ftpdata($sock) 1] set f [lsearch -exact $ftpsize($sock) $file] incr f set tsize [lindex $ftpsize($sock) $f] } {set f -1} } } if {$f != -1} { set title [concat ([expr {round((double($realprog) / $tsize) * 100)}]%) $title] if [info exists ftpstart($sock)] { set secs [expr {[clock seconds] - $ftpstart($sock)}] set eff [expr {round((double($secs) / $ftpprog($sock)) *$tsize ) - $secs}] if $secs { set minprog [expr {$prog($sock) - [lindex $lastprog($sock) 1]}] set minsec [expr {[clock seconds] - [lindex $lastprog($sock) 0]}] incr minsec if {$minsec > 60} {set lastprog($sock) [list [clock seconds] $prog($sock)]} set title [concat $title ([mmss $eff] left) ([mmss $secs] passed) at [join [filesize [expr {$minprog / $minsec}] no %.2f] {}]yte/sec] } } if {[llength $ftpnext($sock)] > 1} { set title [concat $title <[expr {[llength $ftpnext($sock)]-1}] left in queue>] } } } window set_title $title query .ftp:$sock. } proc ftp_url_select {} { global ftpsize set url [lindex [args] 0] if {[regexp -nocase {(get|dir)://(.*)} $url junk type file] && [regexp -nocase {^\.ftp:([^.]*)\.$} [window name] junk sock]} { say "[expr {$type == "get" ? "get" : "cd"}] [lindex $ftpsize($sock) [expr {$file*2}]]" complete } } if ![info exists ftpsocks] { set ftpsocks [list] on url_select ftp_url_select } trace vdelete ftpprog w ftp_update_progress trace variable ftpprog w ftp_update_progress on unload { foreach i $ftpsocks { catch {close $i} catch {close $ftpsock($i)} catch {close $ftpchan($i)} } } kaddhelp ftp ";ftp connect." kaddhelp ftpqueue ";ftp dataqueue info" addecho "[kano] the best addon ever, loaded \[ftp.ka r2\] - type /ftp" addname "ftp.1"